Index: frontend/node_modules/svgo/plugins/_collections.js
===================================================================
--- frontend/node_modules/svgo/plugins/_collections.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/_collections.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2558 @@
+'use strict';
+
+// http://www.w3.org/TR/SVG11/intro.html#Definitions
+exports.elemsGroups = {
+    animation: ['animate', 'animateColor', 'animateMotion', 'animateTransform', 'set'],
+    descriptive: ['desc', 'metadata', 'title'],
+    shape: ['circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect'],
+    structural: ['defs', 'g', 'svg', 'symbol', 'use'],
+    paintServer: ['solidColor', 'linearGradient', 'radialGradient', 'meshGradient', 'pattern', 'hatch'],
+    nonRendering: ['linearGradient', 'radialGradient', 'pattern', 'clipPath', 'mask', 'marker', 'symbol', 'filter', 'solidColor'],
+    container: ['a', 'defs', 'g', 'marker', 'mask', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol', 'foreignObject'],
+    textContent: ['altGlyph', 'altGlyphDef', 'altGlyphItem', 'glyph', 'glyphRef', 'textPath', 'text', 'tref', 'tspan'],
+    textContentChild: ['altGlyph', 'textPath', 'tref', 'tspan'],
+    lightSource: ['feDiffuseLighting', 'feSpecularLighting', 'feDistantLight', 'fePointLight', 'feSpotLight'],
+    filterPrimitive: ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence']
+};
+
+exports.pathElems = ['path', 'glyph', 'missing-glyph'];
+
+// http://www.w3.org/TR/SVG11/intro.html#Definitions
+exports.attrsGroups = {
+    animationAddition: ['additive', 'accumulate'],
+    animationAttributeTarget: ['attributeType', 'attributeName'],
+    animationEvent: ['onbegin', 'onend', 'onrepeat', 'onload'],
+    animationTiming: ['begin', 'dur', 'end', 'min', 'max', 'restart', 'repeatCount', 'repeatDur', 'fill'],
+    animationValue: ['calcMode', 'values', 'keyTimes', 'keySplines', 'from', 'to', 'by'],
+    conditionalProcessing: ['requiredFeatures', 'requiredExtensions', 'systemLanguage'],
+    core: ['id', 'tabindex', 'xml:base', 'xml:lang', 'xml:space'],
+    graphicalEvent: ['onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload'],
+    presentation: [
+        'alignment-baseline',
+        'baseline-shift',
+        'clip',
+        'clip-path',
+        'clip-rule',
+        'color',
+        'color-interpolation',
+        'color-interpolation-filters',
+        'color-profile',
+        'color-rendering',
+        'cursor',
+        'direction',
+        'display',
+        'dominant-baseline',
+        'enable-background',
+        'fill',
+        'fill-opacity',
+        'fill-rule',
+        'filter',
+        'flood-color',
+        'flood-opacity',
+        'font-family',
+        'font-size',
+        'font-size-adjust',
+        'font-stretch',
+        'font-style',
+        'font-variant',
+        'font-weight',
+        'glyph-orientation-horizontal',
+        'glyph-orientation-vertical',
+        'image-rendering',
+        'letter-spacing',
+        'lighting-color',
+        'marker-end',
+        'marker-mid',
+        'marker-start',
+        'mask',
+        'opacity',
+        'overflow',
+        'paint-order',
+        'pointer-events',
+        'shape-rendering',
+        'stop-color',
+        'stop-opacity',
+        'stroke',
+        'stroke-dasharray',
+        'stroke-dashoffset',
+        'stroke-linecap',
+        'stroke-linejoin',
+        'stroke-miterlimit',
+        'stroke-opacity',
+        'stroke-width',
+        'text-anchor',
+        'text-decoration',
+        'text-overflow',
+        'text-rendering',
+        'transform',
+        'unicode-bidi',
+        'vector-effect',
+        'visibility',
+        'word-spacing',
+        'writing-mode'
+    ],
+    xlink: ['xlink:href', 'xlink:show', 'xlink:actuate', 'xlink:type', 'xlink:role', 'xlink:arcrole', 'xlink:title'],
+    documentEvent: ['onunload', 'onabort', 'onerror', 'onresize', 'onscroll', 'onzoom'],
+    filterPrimitive: ['x', 'y', 'width', 'height', 'result'],
+    transferFunction: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset']
+};
+
+exports.attrsGroupsDefaults = {
+    core: {'xml:space': 'preserve'},
+    filterPrimitive: {x: '0', y: '0', width: '100%', height: '100%'},
+    presentation: {
+        clip: 'auto',
+        'clip-path': 'none',
+        'clip-rule': 'nonzero',
+        mask: 'none',
+        opacity: '1',
+        'stop-color': '#000',
+        'stop-opacity': '1',
+        'fill-opacity': '1',
+        'fill-rule': 'nonzero',
+        fill: '#000',
+        stroke: 'none',
+        'stroke-width': '1',
+        'stroke-linecap': 'butt',
+        'stroke-linejoin': 'miter',
+        'stroke-miterlimit': '4',
+        'stroke-dasharray': 'none',
+        'stroke-dashoffset': '0',
+        'stroke-opacity': '1',
+        'paint-order': 'normal',
+        'vector-effect': 'none',
+        display: 'inline',
+        visibility: 'visible',
+        'marker-start': 'none',
+        'marker-mid': 'none',
+        'marker-end': 'none',
+        'color-interpolation': 'sRGB',
+        'color-interpolation-filters': 'linearRGB',
+        'color-rendering': 'auto',
+        'shape-rendering': 'auto',
+        'text-rendering': 'auto',
+        'image-rendering': 'auto',
+        'font-style': 'normal',
+        'font-variant': 'normal',
+        'font-weight': 'normal',
+        'font-stretch': 'normal',
+        'font-size': 'medium',
+        'font-size-adjust': 'none',
+        kerning: 'auto',
+        'letter-spacing': 'normal',
+        'word-spacing': 'normal',
+        'text-decoration': 'none',
+        'text-anchor': 'start',
+        'text-overflow': 'clip',
+        'writing-mode': 'lr-tb',
+        'glyph-orientation-vertical': 'auto',
+        'glyph-orientation-horizontal': '0deg',
+        direction: 'ltr',
+        'unicode-bidi': 'normal',
+        'dominant-baseline': 'auto',
+        'alignment-baseline': 'baseline',
+        'baseline-shift': 'baseline'
+    },
+    transferFunction: {slope: '1', intercept: '0', amplitude: '1', exponent: '1', offset: '0'}
+};
+
+// http://www.w3.org/TR/SVG11/eltindex.html
+exports.elems = {
+    a: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'target'
+        ],
+        defaults: {
+            target: '_self'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    altGlyph: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'dx',
+            'dy',
+            'glyphRef',
+            'format',
+            'rotate'
+        ]
+    },
+    altGlyphDef: {
+        attrsGroups: [
+            'core'
+        ],
+        content: [
+            'glyphRef'
+        ]
+    },
+    altGlyphItem: {
+        attrsGroups: [
+            'core'
+        ],
+        content: [
+            'glyphRef',
+            'altGlyphItem'
+        ]
+    },
+    animate: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationAddition',
+            'animationAttributeTarget',
+            'animationEvent',
+            'animationTiming',
+            'animationValue',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    animateColor: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationEvent',
+            'xlink',
+            'animationAttributeTarget',
+            'animationTiming',
+            'animationValue',
+            'animationAddition',
+            'presentation'
+        ],
+        attrs: [
+            'externalResourcesRequired'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    animateMotion: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationEvent',
+            'xlink',
+            'animationTiming',
+            'animationValue',
+            'animationAddition'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'path',
+            'keyPoints',
+            'rotate',
+            'origin'
+        ],
+        defaults: {
+            'rotate': '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'mpath'
+        ]
+    },
+    animateTransform: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationEvent',
+            'xlink',
+            'animationAttributeTarget',
+            'animationTiming',
+            'animationValue',
+            'animationAddition'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'type'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    circle: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'cx',
+            'cy',
+            'r'
+        ],
+        defaults: {
+            cx: '0',
+            cy: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    clipPath: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'clipPathUnits'
+        ],
+        defaults: {
+            clipPathUnits: 'userSpaceOnUse'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape'
+        ],
+        content: [
+            'text',
+            'use'
+        ]
+    },
+    'color-profile': {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'local',
+            'name',
+            'rendering-intent'
+        ],
+        defaults: {
+            name: 'sRGB',
+            'rendering-intent': 'auto'
+        },
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    cursor: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'x',
+            'y'
+        ],
+        defaults: {
+            x: '0',
+            y: '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    defs: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    desc: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ]
+    },
+    ellipse: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'cx',
+            'cy',
+            'rx',
+            'ry'
+        ],
+        defaults: {
+            cx: '0',
+            cy: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    feBlend: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            // TODO: in - 'If no value is provided and this is the first filter primitive,
+            // then this filter primitive will use SourceGraphic as its input'
+            'in',
+            'in2',
+            'mode'
+        ],
+        defaults: {
+            mode: 'normal'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feColorMatrix: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'type',
+            'values'
+        ],
+        defaults: {
+            type: 'matrix'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feComponentTransfer: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in'
+        ],
+        content: [
+            'feFuncA',
+            'feFuncB',
+            'feFuncG',
+            'feFuncR'
+        ]
+    },
+    feComposite: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'in2',
+            'operator',
+            'k1',
+            'k2',
+            'k3',
+            'k4'
+        ],
+        defaults: {
+            operator: 'over',
+            k1: '0',
+            k2: '0',
+            k3: '0',
+            k4: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feConvolveMatrix: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'order',
+            'kernelMatrix',
+            // TODO: divisor - 'The default value is the sum of all values in kernelMatrix,
+            // with the exception that if the sum is zero, then the divisor is set to 1'
+            'divisor',
+            'bias',
+            // TODO: targetX - 'By default, the convolution matrix is centered in X over each
+            // pixel of the input image (i.e., targetX = floor ( orderX / 2 ))'
+            'targetX',
+            'targetY',
+            'edgeMode',
+            // TODO: kernelUnitLength - 'The first number is the <dx> value. The second number
+            // is the <dy> value. If the <dy> value is not specified, it defaults to the same value as <dx>'
+            'kernelUnitLength',
+            'preserveAlpha'
+        ],
+        defaults: {
+            order: '3',
+            bias: '0',
+            edgeMode: 'duplicate',
+            preserveAlpha: 'false'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feDiffuseLighting: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'surfaceScale',
+            'diffuseConstant',
+            'kernelUnitLength'
+        ],
+        defaults: {
+            surfaceScale: '1',
+            diffuseConstant: '1'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            // TODO: 'exactly one light source element, in any order'
+            'feDistantLight',
+            'fePointLight',
+            'feSpotLight'
+        ]
+    },
+    feDisplacementMap: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'in2',
+            'scale',
+            'xChannelSelector',
+            'yChannelSelector'
+        ],
+        defaults: {
+            scale: '0',
+            xChannelSelector: 'A',
+            yChannelSelector: 'A'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feDistantLight: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'azimuth',
+            'elevation'
+        ],
+        defaults: {
+            azimuth: '0',
+            elevation: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feFlood: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        content: [
+            'animate',
+            'animateColor',
+            'set'
+        ]
+    },
+    feFuncA: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feFuncB: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feFuncG: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feFuncR: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feGaussianBlur: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'stdDeviation'
+        ],
+        defaults: {
+            stdDeviation: '0'
+        },
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feImage: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'preserveAspectRatio',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            preserveAspectRatio: 'xMidYMid meet'
+        },
+        content: [
+            'animate',
+            'animateTransform',
+            'set'
+        ]
+    },
+    feMerge: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        content: [
+            'feMergeNode'
+        ]
+    },
+    feMergeNode: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'in'
+        ],
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feMorphology: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'operator',
+            'radius'
+        ],
+        defaults: {
+            operator: 'erode',
+            radius: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feOffset: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'dx',
+            'dy'
+        ],
+        defaults: {
+            dx: '0',
+            dy: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    fePointLight: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'x',
+            'y',
+            'z'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            z: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feSpecularLighting: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'surfaceScale',
+            'specularConstant',
+            'specularExponent',
+            'kernelUnitLength'
+        ],
+        defaults: {
+            surfaceScale: '1',
+            specularConstant: '1',
+            specularExponent: '1'
+        },
+        contentGroups: [
+            'descriptive',
+            // TODO: exactly one 'light source element'
+            'lightSource'
+        ]
+    },
+    feSpotLight: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'x',
+            'y',
+            'z',
+            'pointsAtX',
+            'pointsAtY',
+            'pointsAtZ',
+            'specularExponent',
+            'limitingConeAngle'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            z: '0',
+            pointsAtX: '0',
+            pointsAtY: '0',
+            pointsAtZ: '0',
+            specularExponent: '1'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feTile: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in'
+        ],
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feTurbulence: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'baseFrequency',
+            'numOctaves',
+            'seed',
+            'stitchTiles',
+            'type'
+        ],
+        defaults: {
+            baseFrequency: '0',
+            numOctaves: '1',
+            seed: '0',
+            stitchTiles: 'noStitch',
+            type: 'turbulence'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    filter: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'width',
+            'height',
+            'filterRes',
+            'filterUnits',
+            'primitiveUnits',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            primitiveUnits: 'userSpaceOnUse',
+            x: '-10%',
+            y: '-10%',
+            width: '120%',
+            height: '120%'
+        },
+        contentGroups: [
+            'descriptive',
+            'filterPrimitive'
+        ],
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    font: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'horiz-origin-x',
+            'horiz-origin-y',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y'
+        ],
+        defaults: {
+            'horiz-origin-x': '0',
+            'horiz-origin-y': '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'font-face',
+            'glyph',
+            'hkern',
+            'missing-glyph',
+            'vkern'
+        ]
+    },
+    'font-face': {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'font-family',
+            'font-style',
+            'font-variant',
+            'font-weight',
+            'font-stretch',
+            'font-size',
+            'unicode-range',
+            'units-per-em',
+            'panose-1',
+            'stemv',
+            'stemh',
+            'slope',
+            'cap-height',
+            'x-height',
+            'accent-height',
+            'ascent',
+            'descent',
+            'widths',
+            'bbox',
+            'ideographic',
+            'alphabetic',
+            'mathematical',
+            'hanging',
+            'v-ideographic',
+            'v-alphabetic',
+            'v-mathematical',
+            'v-hanging',
+            'underline-position',
+            'underline-thickness',
+            'strikethrough-position',
+            'strikethrough-thickness',
+            'overline-position',
+            'overline-thickness'
+        ],
+        defaults: {
+            'font-style': 'all',
+            'font-variant': 'normal',
+            'font-weight': 'all',
+            'font-stretch': 'normal',
+            'unicode-range': 'U+0-10FFFF',
+            'units-per-em': '1000',
+            'panose-1': '0 0 0 0 0 0 0 0 0 0',
+            'slope': '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            // TODO: "at most one 'font-face-src' element"
+            'font-face-src'
+        ]
+    },
+    // TODO: empty content
+    'font-face-format': {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'string'
+        ]
+    },
+    'font-face-name': {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'name'
+        ]
+    },
+    'font-face-src': {
+        attrsGroups: [
+            'core'
+        ],
+        content: [
+            'font-face-name',
+            'font-face-uri'
+        ]
+    },
+    'font-face-uri': {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'href',
+            'xlink:href'
+        ],
+        content: [
+            'font-face-format'
+        ]
+    },
+    foreignObject: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height'
+        ],
+        defaults: {
+            x: 0,
+            y: 0
+        }
+    },
+    g: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    glyph: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y',
+            'unicode',
+            'glyph-name',
+            'orientation',
+            'arabic-form',
+            'lang'
+        ],
+        defaults: {
+            'arabic-form': 'initial'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ],
+    },
+    glyphRef: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    hatch: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'x',
+            'y',
+            'pitch',
+            'rotate',
+            'hatchUnits',
+            'hatchContentUnits',
+            'transform'
+        ],
+        defaults: {
+            hatchUnits: 'objectBoundingBox',
+            hatchContentUnits: 'userSpaceOnUse',
+            x: '0',
+            y: '0',
+            pitch: '0',
+            rotate: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ],
+        content: [
+            'hatchPath'
+        ]
+    },
+    hatchPath: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'offset'
+        ],
+        defaults: {
+            offset: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    hkern: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'u1',
+            'g1',
+            'u2',
+            'g2',
+            'k'
+        ]
+    },
+    image: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'graphicalEvent',
+            'xlink',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'preserveAspectRatio',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            preserveAspectRatio: 'xMidYMid meet'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    line: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x1',
+            'y1',
+            'x2',
+            'y2'
+        ],
+        defaults: {
+            x1: '0',
+            y1: '0',
+            x2: '0',
+            y2: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    linearGradient: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x1',
+            'y1',
+            'x2',
+            'y2',
+            'gradientUnits',
+            'gradientTransform',
+            'spreadMethod',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            x1: '0',
+            y1: '0',
+            x2: '100%',
+            y2: '0',
+            spreadMethod: 'pad'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'animate',
+            'animateTransform',
+            'set',
+            'stop'
+        ]
+    },
+    marker: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'viewBox',
+            'preserveAspectRatio',
+            'refX',
+            'refY',
+            'markerUnits',
+            'markerWidth',
+            'markerHeight',
+            'orient'
+        ],
+        defaults: {
+            markerUnits: 'strokeWidth',
+            refX: '0',
+            refY: '0',
+            markerWidth: '3',
+            markerHeight: '3'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    mask: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'width',
+            'height',
+            'maskUnits',
+            'maskContentUnits'
+        ],
+        defaults: {
+            maskUnits: 'objectBoundingBox',
+            maskContentUnits: 'userSpaceOnUse',
+            x: '-10%',
+            y: '-10%',
+            width: '120%',
+            height: '120%'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    metadata: {
+        attrsGroups: [
+            'core'
+        ]
+    },
+    'missing-glyph': {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    mpath: {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'href',
+            'xlink:href'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    path: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'd',
+            'pathLength'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    pattern: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'viewBox',
+            'preserveAspectRatio',
+            'x',
+            'y',
+            'width',
+            'height',
+            'patternUnits',
+            'patternContentUnits',
+            'patternTransform',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            patternUnits: 'objectBoundingBox',
+            patternContentUnits: 'userSpaceOnUse',
+            x: '0',
+            y: '0',
+            width: '0',
+            height: '0',
+            preserveAspectRatio: 'xMidYMid meet'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'paintServer',
+            'shape',
+            'structural'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    polygon: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'points'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    polyline: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'points'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    radialGradient: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'cx',
+            'cy',
+            'r',
+            'fx',
+            'fy',
+            'fr',
+            'gradientUnits',
+            'gradientTransform',
+            'spreadMethod',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            gradientUnits: 'objectBoundingBox',
+            cx: '50%',
+            cy: '50%',
+            r: '50%'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'animate',
+            'animateTransform',
+            'set',
+            'stop'
+        ]
+    },
+    meshGradient: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'x',
+            'y',
+            'gradientUnits',
+            'transform'
+        ],
+        contentGroups: [
+            'descriptive',
+            'paintServer',
+            'animation',
+        ],
+        content: [
+            'meshRow'
+        ]
+    },
+    meshRow: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'meshPatch'
+        ]
+    },
+    meshPatch: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'stop'
+        ]
+    },
+    rect: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height',
+            'rx',
+            'ry'
+        ],
+        defaults: {
+            x: '0',
+            y: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    script: {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'type',
+            'href',
+            'xlink:href'
+        ]
+    },
+    set: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animation',
+            'xlink',
+            'animationAttributeTarget',
+            'animationTiming',
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'to'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    solidColor: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        contentGroups: [
+            'paintServer'
+        ]
+    },
+    stop: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'offset',
+            'path'
+        ],
+        content: [
+            'animate',
+            'animateColor',
+            'set'
+        ]
+    },
+    style: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'type',
+            'media',
+            'title'
+        ],
+        defaults: {
+            type: 'text/css'
+        }
+    },
+    svg: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'documentEvent',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'x',
+            'y',
+            'width',
+            'height',
+            'viewBox',
+            'preserveAspectRatio',
+            'zoomAndPan',
+            'version',
+            'baseProfile',
+            'contentScriptType',
+            'contentStyleType'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            width: '100%',
+            height: '100%',
+            preserveAspectRatio: 'xMidYMid meet',
+            zoomAndPan: 'magnify',
+            version: '1.1',
+            baseProfile: 'none',
+            contentScriptType: 'application/ecmascript',
+            contentStyleType: 'text/css'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    switch: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape'
+        ],
+        content: [
+            'a',
+            'foreignObject',
+            'g',
+            'image',
+            'svg',
+            'switch',
+            'text',
+            'use'
+        ]
+    },
+    symbol: {
+        attrsGroups: [
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'preserveAspectRatio',
+            'viewBox',
+            'refX',
+            'refY'
+        ],
+        defaults: {
+            refX: 0,
+            refY: 0
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    text: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'lengthAdjust',
+            'x',
+            'y',
+            'dx',
+            'dy',
+            'rotate',
+            'textLength'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            lengthAdjust: 'spacing'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'textContentChild'
+        ],
+        content: [
+            'a'
+        ]
+    },
+    textPath: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'href',
+            'xlink:href',
+            'startOffset',
+            'method',
+            'spacing',
+            'd'
+        ],
+        defaults: {
+            startOffset: '0',
+            method: 'align',
+            spacing: 'exact'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'a',
+            'altGlyph',
+            'animate',
+            'animateColor',
+            'set',
+            'tref',
+            'tspan'
+        ]
+    },
+    title: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ]
+    },
+    tref: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'href',
+            'xlink:href'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'animate',
+            'animateColor',
+            'set'
+        ]
+    },
+    tspan: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'dx',
+            'dy',
+            'rotate',
+            'textLength',
+            'lengthAdjust'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'a',
+            'altGlyph',
+            'animate',
+            'animateColor',
+            'set',
+            'tref',
+            'tspan'
+        ]
+    },
+    use: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            x: '0',
+            y: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    view: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'viewBox',
+            'preserveAspectRatio',
+            'zoomAndPan',
+            'viewTarget'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    vkern: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'u1',
+            'g1',
+            'u2',
+            'g2',
+            'k'
+        ]
+    }
+};
+
+// http://wiki.inkscape.org/wiki/index.php/Inkscape-specific_XML_attributes
+exports.editorNamespaces = [
+    'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
+    'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd',
+    'http://www.inkscape.org/namespaces/inkscape',
+    'http://www.bohemiancoding.com/sketch/ns',
+    'http://ns.adobe.com/AdobeIllustrator/10.0/',
+    'http://ns.adobe.com/Graphs/1.0/',
+    'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/',
+    'http://ns.adobe.com/Variables/1.0/',
+    'http://ns.adobe.com/SaveForWeb/1.0/',
+    'http://ns.adobe.com/Extensibility/1.0/',
+    'http://ns.adobe.com/Flows/1.0/',
+    'http://ns.adobe.com/ImageReplacement/1.0/',
+    'http://ns.adobe.com/GenericCustomNamespace/1.0/',
+    'http://ns.adobe.com/XPath/1.0/',
+    'http://schemas.microsoft.com/visio/2003/SVGExtensions/',
+    'http://taptrix.com/vectorillustrator/svg_extensions',
+    'http://www.figma.com/figma/ns',
+    'http://purl.org/dc/elements/1.1/',
+    'http://creativecommons.org/ns#',
+    'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+    'http://www.serif.com/',
+    'http://www.vector.evaxdesign.sk'
+];
+
+// http://www.w3.org/TR/SVG11/linking.html#processingIRI
+exports.referencesProps = [
+    'clip-path',
+    'color-profile',
+    'fill',
+    'filter',
+    'marker-start',
+    'marker-mid',
+    'marker-end',
+    'mask',
+    'stroke',
+    'style'
+];
+
+// http://www.w3.org/TR/SVG11/propidx.html
+exports.inheritableAttrs = [
+    'clip-rule',
+    'color',
+    'color-interpolation',
+    'color-interpolation-filters',
+    'color-profile',
+    'color-rendering',
+    'cursor',
+    'direction',
+    'dominant-baseline',
+    'fill',
+    'fill-opacity',
+    'fill-rule',
+    'font',
+    'font-family',
+    'font-size',
+    'font-size-adjust',
+    'font-stretch',
+    'font-style',
+    'font-variant',
+    'font-weight',
+    'glyph-orientation-horizontal',
+    'glyph-orientation-vertical',
+    'image-rendering',
+    'letter-spacing',
+    'marker',
+    'marker-end',
+    'marker-mid',
+    'marker-start',
+    'paint-order',
+    'pointer-events',
+    'shape-rendering',
+    'stroke',
+    'stroke-dasharray',
+    'stroke-dashoffset',
+    'stroke-linecap',
+    'stroke-linejoin',
+    'stroke-miterlimit',
+    'stroke-opacity',
+    'stroke-width',
+    'text-anchor',
+    'text-rendering',
+    'transform',
+    'visibility',
+    'word-spacing',
+    'writing-mode'
+];
+
+exports.presentationNonInheritableGroupAttrs = [
+    'display',
+    'clip-path',
+    'filter',
+    'mask',
+    'opacity',
+    'text-decoration',
+    'transform',
+    'unicode-bidi',
+    'visibility'
+];
+
+// http://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
+exports.colorsNames = {
+    'aliceblue': '#f0f8ff',
+    'antiquewhite': '#faebd7',
+    'aqua': '#0ff',
+    'aquamarine': '#7fffd4',
+    'azure': '#f0ffff',
+    'beige': '#f5f5dc',
+    'bisque': '#ffe4c4',
+    'black': '#000',
+    'blanchedalmond': '#ffebcd',
+    'blue': '#00f',
+    'blueviolet': '#8a2be2',
+    'brown': '#a52a2a',
+    'burlywood': '#deb887',
+    'cadetblue': '#5f9ea0',
+    'chartreuse': '#7fff00',
+    'chocolate': '#d2691e',
+    'coral': '#ff7f50',
+    'cornflowerblue': '#6495ed',
+    'cornsilk': '#fff8dc',
+    'crimson': '#dc143c',
+    'cyan': '#0ff',
+    'darkblue': '#00008b',
+    'darkcyan': '#008b8b',
+    'darkgoldenrod': '#b8860b',
+    'darkgray': '#a9a9a9',
+    'darkgreen': '#006400',
+    'darkgrey': '#a9a9a9',
+    'darkkhaki': '#bdb76b',
+    'darkmagenta': '#8b008b',
+    'darkolivegreen': '#556b2f',
+    'darkorange': '#ff8c00',
+    'darkorchid': '#9932cc',
+    'darkred': '#8b0000',
+    'darksalmon': '#e9967a',
+    'darkseagreen': '#8fbc8f',
+    'darkslateblue': '#483d8b',
+    'darkslategray': '#2f4f4f',
+    'darkslategrey': '#2f4f4f',
+    'darkturquoise': '#00ced1',
+    'darkviolet': '#9400d3',
+    'deeppink': '#ff1493',
+    'deepskyblue': '#00bfff',
+    'dimgray': '#696969',
+    'dimgrey': '#696969',
+    'dodgerblue': '#1e90ff',
+    'firebrick': '#b22222',
+    'floralwhite': '#fffaf0',
+    'forestgreen': '#228b22',
+    'fuchsia': '#f0f',
+    'gainsboro': '#dcdcdc',
+    'ghostwhite': '#f8f8ff',
+    'gold': '#ffd700',
+    'goldenrod': '#daa520',
+    'gray': '#808080',
+    'green': '#008000',
+    'greenyellow': '#adff2f',
+    'grey': '#808080',
+    'honeydew': '#f0fff0',
+    'hotpink': '#ff69b4',
+    'indianred': '#cd5c5c',
+    'indigo': '#4b0082',
+    'ivory': '#fffff0',
+    'khaki': '#f0e68c',
+    'lavender': '#e6e6fa',
+    'lavenderblush': '#fff0f5',
+    'lawngreen': '#7cfc00',
+    'lemonchiffon': '#fffacd',
+    'lightblue': '#add8e6',
+    'lightcoral': '#f08080',
+    'lightcyan': '#e0ffff',
+    'lightgoldenrodyellow': '#fafad2',
+    'lightgray': '#d3d3d3',
+    'lightgreen': '#90ee90',
+    'lightgrey': '#d3d3d3',
+    'lightpink': '#ffb6c1',
+    'lightsalmon': '#ffa07a',
+    'lightseagreen': '#20b2aa',
+    'lightskyblue': '#87cefa',
+    'lightslategray': '#789',
+    'lightslategrey': '#789',
+    'lightsteelblue': '#b0c4de',
+    'lightyellow': '#ffffe0',
+    'lime': '#0f0',
+    'limegreen': '#32cd32',
+    'linen': '#faf0e6',
+    'magenta': '#f0f',
+    'maroon': '#800000',
+    'mediumaquamarine': '#66cdaa',
+    'mediumblue': '#0000cd',
+    'mediumorchid': '#ba55d3',
+    'mediumpurple': '#9370db',
+    'mediumseagreen': '#3cb371',
+    'mediumslateblue': '#7b68ee',
+    'mediumspringgreen': '#00fa9a',
+    'mediumturquoise': '#48d1cc',
+    'mediumvioletred': '#c71585',
+    'midnightblue': '#191970',
+    'mintcream': '#f5fffa',
+    'mistyrose': '#ffe4e1',
+    'moccasin': '#ffe4b5',
+    'navajowhite': '#ffdead',
+    'navy': '#000080',
+    'oldlace': '#fdf5e6',
+    'olive': '#808000',
+    'olivedrab': '#6b8e23',
+    'orange': '#ffa500',
+    'orangered': '#ff4500',
+    'orchid': '#da70d6',
+    'palegoldenrod': '#eee8aa',
+    'palegreen': '#98fb98',
+    'paleturquoise': '#afeeee',
+    'palevioletred': '#db7093',
+    'papayawhip': '#ffefd5',
+    'peachpuff': '#ffdab9',
+    'peru': '#cd853f',
+    'pink': '#ffc0cb',
+    'plum': '#dda0dd',
+    'powderblue': '#b0e0e6',
+    'purple': '#800080',
+    'rebeccapurple': '#639',
+    'red': '#f00',
+    'rosybrown': '#bc8f8f',
+    'royalblue': '#4169e1',
+    'saddlebrown': '#8b4513',
+    'salmon': '#fa8072',
+    'sandybrown': '#f4a460',
+    'seagreen': '#2e8b57',
+    'seashell': '#fff5ee',
+    'sienna': '#a0522d',
+    'silver': '#c0c0c0',
+    'skyblue': '#87ceeb',
+    'slateblue': '#6a5acd',
+    'slategray': '#708090',
+    'slategrey': '#708090',
+    'snow': '#fffafa',
+    'springgreen': '#00ff7f',
+    'steelblue': '#4682b4',
+    'tan': '#d2b48c',
+    'teal': '#008080',
+    'thistle': '#d8bfd8',
+    'tomato': '#ff6347',
+    'turquoise': '#40e0d0',
+    'violet': '#ee82ee',
+    'wheat': '#f5deb3',
+    'white': '#fff',
+    'whitesmoke': '#f5f5f5',
+    'yellow': '#ff0',
+    'yellowgreen': '#9acd32'
+};
+
+exports.colorsShortNames = {
+  '#f0ffff': 'azure',
+  '#f5f5dc': 'beige',
+  '#ffe4c4': 'bisque',
+  '#a52a2a': 'brown',
+  '#ff7f50': 'coral',
+  '#ffd700': 'gold',
+  '#808080': 'gray',
+  '#008000': 'green',
+  '#4b0082': 'indigo',
+  '#fffff0': 'ivory',
+  '#f0e68c': 'khaki',
+  '#faf0e6': 'linen',
+  '#800000': 'maroon',
+  '#000080': 'navy',
+  '#808000': 'olive',
+  '#ffa500': 'orange',
+  '#da70d6': 'orchid',
+  '#cd853f': 'peru',
+  '#ffc0cb': 'pink',
+  '#dda0dd': 'plum',
+  '#800080': 'purple',
+  '#f00': 'red',
+  '#ff0000': 'red',
+  '#fa8072': 'salmon',
+  '#a0522d': 'sienna',
+  '#c0c0c0': 'silver',
+  '#fffafa': 'snow',
+  '#d2b48c': 'tan',
+  '#008080': 'teal',
+  '#ff6347': 'tomato',
+  '#ee82ee': 'violet',
+  '#f5deb3': 'wheat'
+};
+
+// http://www.w3.org/TR/SVG11/single-page.html#types-DataTypeColor
+exports.colorsProps = [
+    'color', 'fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color'
+];
Index: frontend/node_modules/svgo/plugins/_path.js
===================================================================
--- frontend/node_modules/svgo/plugins/_path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/_path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,988 @@
+/* global a2c */
+'use strict';
+
+var rNumber = String.raw`[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?\s*`,
+    rCommaWsp = String.raw`(?:\s,?\s*|,\s*)`,
+    rNumberCommaWsp = `(${rNumber})` + rCommaWsp,
+    rFlagCommaWsp = `([01])${rCommaWsp}?`,
+    rCoordinatePair = String.raw`(${rNumber})${rCommaWsp}?(${rNumber})`,
+    rArcSeq = (rNumberCommaWsp + '?').repeat(2) + rNumberCommaWsp + rFlagCommaWsp.repeat(2) + rCoordinatePair;
+
+var regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/,
+    regCoordinateSequence = new RegExp(rNumber, 'g'),
+    regArcArgumentSequence = new RegExp(rArcSeq, 'g'),
+    regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/,
+    transform2js = require('./_transforms').transform2js,
+    transformsMultiply = require('./_transforms').transformsMultiply,
+    transformArc = require('./_transforms').transformArc,
+    collections = require('./_collections.js'),
+    referencesProps = collections.referencesProps,
+    defaultStrokeWidth = collections.attrsGroupsDefaults.presentation['stroke-width'],
+    cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
+    removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
+    prevCtrlPoint;
+
+/**
+ * Convert path string to JS representation.
+ *
+ * @param {String} pathString input string
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+exports.path2js = function(path) {
+    if (path.pathJS) return path.pathJS;
+
+    var paramsLength = { // Number of parameters of every path command
+            H: 1, V: 1, M: 2, L: 2, T: 2, Q: 4, S: 4, C: 6, A: 7,
+            h: 1, v: 1, m: 2, l: 2, t: 2, q: 4, s: 4, c: 6, a: 7
+        },
+        pathData = [],   // JS representation of the path data
+        instruction, // current instruction context
+        startMoveto = false;
+
+    // splitting path string into array like ['M', '10 50', 'L', '20 30']
+    path.attr('d').value.split(regPathInstructions).forEach(function(data) {
+        if (!data) return;
+        if (!startMoveto) {
+            if (data == 'M' || data == 'm') {
+                startMoveto = true;
+            } else return;
+        }
+
+        // instruction item
+        if (regPathInstructions.test(data)) {
+            instruction = data;
+
+            // z - instruction w/o data
+            if (instruction == 'Z' || instruction == 'z') {
+                pathData.push({
+                    instruction: 'z'
+                });
+            }
+        // data item
+        } else {
+            /* jshint boss: true */
+            if (instruction == 'A' || instruction == 'a') {
+                var newData = [];
+                for (var args; (args = regArcArgumentSequence.exec(data));) {
+                    for (var i = 1; i < args.length; i++) {
+                        newData.push(args[i]);
+                    }
+                }
+                data = newData;
+            } else {
+                data = data.match(regCoordinateSequence);
+            }
+            if (!data) return;
+
+            data = data.map(Number);
+            // Subsequent moveto pairs of coordinates are threated as implicit lineto commands
+            // http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
+            if (instruction == 'M' || instruction == 'm') {
+                pathData.push({
+                    instruction: pathData.length == 0 ? 'M' : instruction,
+                    data: data.splice(0, 2)
+                });
+                instruction = instruction == 'M' ? 'L' : 'l';
+            }
+
+            for (var pair = paramsLength[instruction]; data.length;) {
+                pathData.push({
+                    instruction: instruction,
+                    data: data.splice(0, pair)
+                });
+            }
+        }
+    });
+
+    // First moveto is actually absolute. Subsequent coordinates were separated above.
+    if (pathData.length && pathData[0].instruction == 'm') {
+        pathData[0].instruction = 'M';
+    }
+    path.pathJS = pathData;
+
+    return pathData;
+};
+
+/**
+ * Convert relative Path data to absolute.
+ *
+ * @param {Array} data input data
+ * @return {Array} output data
+ */
+var relative2absolute = exports.relative2absolute = function(data) {
+    var currentPoint = [0, 0],
+        subpathPoint = [0, 0],
+        i;
+
+    return data.map(function(item) {
+
+        var instruction = item.instruction,
+            itemData = item.data && item.data.slice();
+
+        if (instruction == 'M') {
+
+            set(currentPoint, itemData);
+            set(subpathPoint, itemData);
+
+        } else if ('mlcsqt'.indexOf(instruction) > -1) {
+
+            for (i = 0; i < itemData.length; i++) {
+                itemData[i] += currentPoint[i % 2];
+            }
+            set(currentPoint, itemData);
+
+            if (instruction == 'm') {
+                set(subpathPoint, itemData);
+            }
+
+        } else if (instruction == 'a') {
+
+            itemData[5] += currentPoint[0];
+            itemData[6] += currentPoint[1];
+            set(currentPoint, itemData);
+
+        } else if (instruction == 'h') {
+
+            itemData[0] += currentPoint[0];
+            currentPoint[0] = itemData[0];
+
+        } else if (instruction == 'v') {
+
+            itemData[0] += currentPoint[1];
+            currentPoint[1] = itemData[0];
+
+        } else if ('MZLCSQTA'.indexOf(instruction) > -1) {
+
+            set(currentPoint, itemData);
+
+        } else if (instruction == 'H') {
+
+            currentPoint[0] = itemData[0];
+
+        } else if (instruction == 'V') {
+
+            currentPoint[1] = itemData[0];
+
+        } else if (instruction == 'z') {
+
+            set(currentPoint, subpathPoint);
+
+        }
+
+        return instruction == 'z' ?
+            { instruction: 'z' } :
+            {
+                instruction: instruction.toUpperCase(),
+                data: itemData
+            };
+
+    });
+};
+
+/**
+ * Apply transformation(s) to the Path data.
+ *
+ * @param {Object} elem current element
+ * @param {Array} path input path data
+ * @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width)
+ * @return {Array} output path data
+ */
+exports.applyTransforms = function(elem, path, params) {
+    // if there are no 'stroke' attr and references to other objects such as
+    // gradiends or clip-path which are also subjects to transform.
+    if (!elem.hasAttr('transform') || !elem.attr('transform').value ||
+        elem.someAttr(function(attr) {
+            return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url(');
+        }))
+        return path;
+
+    var matrix = transformsMultiply(transform2js(elem.attr('transform').value)),
+        stroke = elem.computedAttr('stroke'),
+        id = elem.computedAttr('id'),
+        transformPrecision = params.transformPrecision,
+        newPoint, scale;
+
+    if (stroke && stroke != 'none') {
+        if (!params.applyTransformsStroked ||
+            (matrix.data[0] != matrix.data[3] || matrix.data[1] != -matrix.data[2]) &&
+            (matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2]))
+            return path;
+
+        // "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
+        if (id) {
+            var idElem = elem,
+                hasStrokeWidth = false;
+
+            do {
+                if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true;
+            } while (!idElem.hasAttr('id', id) && !hasStrokeWidth && (idElem = idElem.parentNode));
+
+            if (!hasStrokeWidth) return path;
+        }
+
+        scale = +Math.sqrt(matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]).toFixed(transformPrecision);
+
+        if (scale !== 1) {
+            var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
+
+            if (!elem.hasAttr('vector-effect') || elem.attr('vector-effect').value !== 'non-scaling-stroke') {
+                if (elem.hasAttr('stroke-width')) {
+                    elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value.trim()
+                        .replace(regNumericValues, function(num) {
+                            return removeLeadingZero(num * scale);
+                        });
+                } else {
+                    elem.addAttr({
+                        name: 'stroke-width',
+                        prefix: '',
+                        local: 'stroke-width',
+                        value: strokeWidth.replace(regNumericValues, function(num) {
+                            return removeLeadingZero(num * scale);
+                        })
+                    });
+                }
+            }
+        }
+    } else if (id) { // Stroke and stroke-width can be redefined with <use>
+        return path;
+    }
+
+    path.forEach(function(pathItem) {
+
+        if (pathItem.data) {
+
+            // h -> l
+            if (pathItem.instruction === 'h') {
+
+                pathItem.instruction = 'l';
+                pathItem.data[1] = 0;
+
+            // v -> l
+            } else if (pathItem.instruction === 'v') {
+
+                pathItem.instruction = 'l';
+                pathItem.data[1] = pathItem.data[0];
+                pathItem.data[0] = 0;
+
+            }
+
+            // if there is a translate() transform
+            if (pathItem.instruction === 'M' &&
+                (matrix.data[4] !== 0 ||
+                matrix.data[5] !== 0)
+            ) {
+
+                // then apply it only to the first absoluted M
+                newPoint = transformPoint(matrix.data, pathItem.data[0], pathItem.data[1]);
+                set(pathItem.data, newPoint);
+                set(pathItem.coords, newPoint);
+
+                // clear translate() data from transform matrix
+                matrix.data[4] = 0;
+                matrix.data[5] = 0;
+
+            } else {
+
+                if (pathItem.instruction == 'a') {
+
+                    transformArc(pathItem.data, matrix.data);
+
+                    // reduce number of digits in rotation angle
+                    if (Math.abs(pathItem.data[2]) > 80) {
+                        var a = pathItem.data[0],
+                            rotation = pathItem.data[2];
+                        pathItem.data[0] = pathItem.data[1];
+                        pathItem.data[1] = a;
+                        pathItem.data[2] = rotation + (rotation > 0 ? -90 : 90);
+                    }
+
+                    newPoint = transformPoint(matrix.data, pathItem.data[5], pathItem.data[6]);
+                    pathItem.data[5] = newPoint[0];
+                    pathItem.data[6] = newPoint[1];
+
+                } else {
+
+                    for (var i = 0; i < pathItem.data.length; i += 2) {
+                        newPoint = transformPoint(matrix.data, pathItem.data[i], pathItem.data[i + 1]);
+                        pathItem.data[i] = newPoint[0];
+                        pathItem.data[i + 1] = newPoint[1];
+                    }
+                }
+
+                pathItem.coords[0] = pathItem.base[0] + pathItem.data[pathItem.data.length - 2];
+                pathItem.coords[1] = pathItem.base[1] + pathItem.data[pathItem.data.length - 1];
+
+            }
+
+        }
+
+    });
+
+    // remove transform attr
+    elem.removeAttr('transform');
+
+    return path;
+};
+
+/**
+ * Apply transform 3x3 matrix to x-y point.
+ *
+ * @param {Array} matrix transform 3x3 matrix
+ * @param {Array} point x-y point
+ * @return {Array} point with new coordinates
+ */
+function transformPoint(matrix, x, y) {
+
+    return [
+        matrix[0] * x + matrix[2] * y + matrix[4],
+        matrix[1] * x + matrix[3] * y + matrix[5]
+    ];
+
+}
+
+/**
+ * Compute Cubic Bézie bounding box.
+ *
+ * @see http://processingjs.nihongoresources.com/bezierinfo/
+ *
+ * @param {Float} xa
+ * @param {Float} ya
+ * @param {Float} xb
+ * @param {Float} yb
+ * @param {Float} xc
+ * @param {Float} yc
+ * @param {Float} xd
+ * @param {Float} yd
+ *
+ * @return {Object}
+ */
+exports.computeCubicBoundingBox = function(xa, ya, xb, yb, xc, yc, xd, yd) {
+
+    var minx = Number.POSITIVE_INFINITY,
+        miny = Number.POSITIVE_INFINITY,
+        maxx = Number.NEGATIVE_INFINITY,
+        maxy = Number.NEGATIVE_INFINITY,
+        ts,
+        t,
+        x,
+        y,
+        i;
+
+    // X
+    if (xa < minx) { minx = xa; }
+    if (xa > maxx) { maxx = xa; }
+    if (xd < minx) { minx= xd; }
+    if (xd > maxx) { maxx = xd; }
+
+    ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
+
+    for (i = 0; i < ts.length; i++) {
+
+        t = ts[i];
+
+        if (t >= 0 && t <= 1) {
+            x = computeCubicBaseValue(t, xa, xb, xc, xd);
+            // y = computeCubicBaseValue(t, ya, yb, yc, yd);
+
+            if (x < minx) { minx = x; }
+            if (x > maxx) { maxx = x; }
+        }
+
+    }
+
+    // Y
+    if (ya < miny) { miny = ya; }
+    if (ya > maxy) { maxy = ya; }
+    if (yd < miny) { miny = yd; }
+    if (yd > maxy) { maxy = yd; }
+
+    ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
+
+    for (i = 0; i < ts.length; i++) {
+
+        t = ts[i];
+
+        if (t >= 0 && t <= 1) {
+            // x = computeCubicBaseValue(t, xa, xb, xc, xd);
+            y = computeCubicBaseValue(t, ya, yb, yc, yd);
+
+            if (y < miny) { miny = y; }
+            if (y > maxy) { maxy = y; }
+        }
+
+    }
+
+    return {
+        minx: minx,
+        miny: miny,
+        maxx: maxx,
+        maxy: maxy
+    };
+
+};
+
+// compute the value for the cubic bezier function at time=t
+function computeCubicBaseValue(t, a, b, c, d) {
+
+    var mt = 1 - t;
+
+    return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d;
+
+}
+
+// compute the value for the first derivative of the cubic bezier function at time=t
+function computeCubicFirstDerivativeRoots(a, b, c, d) {
+
+    var result = [-1, -1],
+        tl = -a + 2 * b - c,
+        tr = -Math.sqrt(-a * (c - d) + b * b - b * (c + d) + c * c),
+        dn = -a + 3 * b - 3 * c + d;
+
+    if (dn !== 0) {
+        result[0] = (tl + tr) / dn;
+        result[1] = (tl - tr) / dn;
+    }
+
+    return result;
+
+}
+
+/**
+ * Compute Quadratic Bézier bounding box.
+ *
+ * @see http://processingjs.nihongoresources.com/bezierinfo/
+ *
+ * @param {Float} xa
+ * @param {Float} ya
+ * @param {Float} xb
+ * @param {Float} yb
+ * @param {Float} xc
+ * @param {Float} yc
+ *
+ * @return {Object}
+ */
+exports.computeQuadraticBoundingBox = function(xa, ya, xb, yb, xc, yc) {
+
+    var minx = Number.POSITIVE_INFINITY,
+        miny = Number.POSITIVE_INFINITY,
+        maxx = Number.NEGATIVE_INFINITY,
+        maxy = Number.NEGATIVE_INFINITY,
+        t,
+        x,
+        y;
+
+    // X
+    if (xa < minx) { minx = xa; }
+    if (xa > maxx) { maxx = xa; }
+    if (xc < minx) { minx = xc; }
+    if (xc > maxx) { maxx = xc; }
+
+    t = computeQuadraticFirstDerivativeRoot(xa, xb, xc);
+
+    if (t >= 0 && t <= 1) {
+        x = computeQuadraticBaseValue(t, xa, xb, xc);
+        // y = computeQuadraticBaseValue(t, ya, yb, yc);
+
+        if (x < minx) { minx = x; }
+        if (x > maxx) { maxx = x; }
+    }
+
+    // Y
+    if (ya < miny) { miny = ya; }
+    if (ya > maxy) { maxy = ya; }
+    if (yc < miny) { miny = yc; }
+    if (yc > maxy) { maxy = yc; }
+
+    t = computeQuadraticFirstDerivativeRoot(ya, yb, yc);
+
+    if (t >= 0 && t <=1 ) {
+        // x = computeQuadraticBaseValue(t, xa, xb, xc);
+        y = computeQuadraticBaseValue(t, ya, yb, yc);
+
+        if (y < miny) { miny = y; }
+        if (y > maxy) { maxy = y ; }
+
+    }
+
+    return {
+        minx: minx,
+        miny: miny,
+        maxx: maxx,
+        maxy: maxy
+    };
+
+};
+
+// compute the value for the quadratic bezier function at time=t
+function computeQuadraticBaseValue(t, a, b, c) {
+
+    var mt = 1 - t;
+
+    return mt * mt * a + 2 * mt * t * b + t * t * c;
+
+}
+
+// compute the value for the first derivative of the quadratic bezier function at time=t
+function computeQuadraticFirstDerivativeRoot(a, b, c) {
+
+    var t = -1,
+        denominator = a - 2 * b + c;
+
+    if (denominator !== 0) {
+        t = (a - b) / denominator;
+    }
+
+    return t;
+
+}
+
+/**
+ * Convert path array to string.
+ *
+ * @param {Array} path input path data
+ * @param {Object} params plugin params
+ * @return {String} output path string
+ */
+exports.js2path = function(path, data, params) {
+
+    path.pathJS = data;
+
+    if (params.collapseRepeated) {
+        data = collapseRepeated(data);
+    }
+
+    path.attr('d').value = data.reduce(function(pathString, item) {
+        var strData = '';
+        if (item.data) {
+            strData = cleanupOutData(item.data, params, item.instruction);
+        }
+        return pathString += item.instruction + strData;
+    }, '');
+
+};
+
+/**
+ * Collapse repeated instructions data
+ *
+ * @param {Array} path input path data
+ * @return {Array} output path data
+ */
+function collapseRepeated(data) {
+
+    var prev,
+        prevIndex;
+
+    // copy an array and modifieds item to keep original data untouched
+    data = data.reduce(function(newPath, item) {
+        if (
+            prev && item.data &&
+            item.instruction == prev.instruction
+        ) {
+            // concat previous data with current
+            if (item.instruction != 'M') {
+                prev = newPath[prevIndex] = {
+                    instruction: prev.instruction,
+                    data: prev.data.concat(item.data),
+                    coords: item.coords,
+                    base: prev.base
+                };
+            } else {
+                prev.data = item.data;
+                prev.coords = item.coords;
+            }
+        } else {
+            newPath.push(item);
+            prev = item;
+            prevIndex = newPath.length - 1;
+        }
+
+        return newPath;
+    }, []);
+
+    return data;
+
+}
+
+function set(dest, source) {
+    dest[0] = source[source.length - 2];
+    dest[1] = source[source.length - 1];
+    return dest;
+}
+
+/**
+ * Checks if two paths have an intersection by checking convex hulls
+ * collision using Gilbert-Johnson-Keerthi distance algorithm
+ * http://entropyinteractive.com/2011/04/gjk-algorithm/
+ *
+ * @param {Array} path1 JS path representation
+ * @param {Array} path2 JS path representation
+ * @return {Boolean}
+ */
+exports.intersects = function(path1, path2) {
+    if (path1.length < 3 || path2.length < 3) return false; // nothing to fill
+
+    // Collect points of every subpath.
+    var points1 = relative2absolute(path1).reduce(gatherPoints, []),
+        points2 = relative2absolute(path2).reduce(gatherPoints, []);
+
+    // Axis-aligned bounding box check.
+    if (points1.maxX <= points2.minX || points2.maxX <= points1.minX ||
+        points1.maxY <= points2.minY || points2.maxY <= points1.minY ||
+        points1.every(function (set1) {
+            return points2.every(function (set2) {
+                return set1[set1.maxX][0] <= set2[set2.minX][0] ||
+                    set2[set2.maxX][0] <= set1[set1.minX][0] ||
+                    set1[set1.maxY][1] <= set2[set2.minY][1] ||
+                    set2[set2.maxY][1] <= set1[set1.minY][1];
+            });
+        })
+    ) return false;
+
+    // Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
+    var hullNest1 = points1.map(convexHull),
+        hullNest2 = points2.map(convexHull);
+
+    // Check intersection of every subpath of the first path with every subpath of the second.
+    return hullNest1.some(function(hull1) {
+        if (hull1.length < 3) return false;
+
+        return hullNest2.some(function(hull2) {
+            if (hull2.length < 3) return false;
+
+            var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
+                direction = minus(simplex[0]); // set the direction to point towards the origin
+
+            var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
+            while (true) {
+                if (iterations-- == 0) {
+                    console.error('Error: infinite loop while processing mergePaths plugin.');
+                    return true; // true is the safe value that means “do nothing with paths”
+                }
+                // add a new point
+                simplex.push(getSupport(hull1, hull2, direction));
+                // see if the new point was on the correct side of the origin
+                if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
+                // process the simplex
+                if (processSimplex(simplex, direction)) return true;
+            }
+        });
+    });
+
+    function getSupport(a, b, direction) {
+        return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));
+    }
+
+    // Computes farthest polygon point in particular direction.
+    // Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in.
+    // Since we're working on convex hull, the dot product is increasing until we find the farthest point.
+    function supportPoint(polygon, direction) {
+        var index = direction[1] >= 0 ?
+                direction[0] < 0 ? polygon.maxY : polygon.maxX :
+                direction[0] < 0 ? polygon.minX : polygon.minY,
+            max = -Infinity,
+            value;
+        while ((value = dot(polygon[index], direction)) > max) {
+            max = value;
+            index = ++index % polygon.length;
+        }
+        return polygon[(index || polygon.length) - 1];
+    }
+};
+
+function processSimplex(simplex, direction) {
+    /* jshint -W004 */
+
+    // we only need to handle to 1-simplex and 2-simplex
+    if (simplex.length == 2) { // 1-simplex
+        var a = simplex[1],
+            b = simplex[0],
+            AO = minus(simplex[1]),
+            AB = sub(b, a);
+        // AO is in the same direction as AB
+        if (dot(AO, AB) > 0) {
+            // get the vector perpendicular to AB facing O
+            set(direction, orth(AB, a));
+        } else {
+            set(direction, AO);
+            // only A remains in the simplex
+            simplex.shift();
+        }
+    } else { // 2-simplex
+        var a = simplex[2], // [a, b, c] = simplex
+            b = simplex[1],
+            c = simplex[0],
+            AB = sub(b, a),
+            AC = sub(c, a),
+            AO = minus(a),
+            ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
+            ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
+
+        if (dot(ACB, AO) > 0) {
+            if (dot(AB, AO) > 0) { // region 4
+                set(direction, ACB);
+                simplex.shift(); // simplex = [b, a]
+            } else { // region 5
+                set(direction, AO);
+                simplex.splice(0, 2); // simplex = [a]
+            }
+        } else if (dot(ABC, AO) > 0) {
+            if (dot(AC, AO) > 0) { // region 6
+                set(direction, ABC);
+                simplex.splice(1, 1); // simplex = [c, a]
+            } else { // region 5 (again)
+                set(direction, AO);
+                simplex.splice(0, 2); // simplex = [a]
+            }
+        } else // region 7
+            return true;
+    }
+    return false;
+}
+
+function minus(v) {
+    return [-v[0], -v[1]];
+}
+
+function sub(v1, v2) {
+    return [v1[0] - v2[0], v1[1] - v2[1]];
+}
+
+function dot(v1, v2) {
+    return v1[0] * v2[0] + v1[1] * v2[1];
+}
+
+function orth(v, from) {
+    var o = [-v[1], v[0]];
+    return dot(o, minus(from)) < 0 ? minus(o) : o;
+}
+
+function gatherPoints(points, item, index, path) {
+
+    var subPath = points.length && points[points.length - 1],
+        prev = index && path[index - 1],
+        basePoint = subPath.length && subPath[subPath.length - 1],
+        data = item.data,
+        ctrlPoint = basePoint;
+
+    switch (item.instruction) {
+        case 'M':
+            points.push(subPath = []);
+            break;
+        case 'H':
+            addPoint(subPath, [data[0], basePoint[1]]);
+            break;
+        case 'V':
+            addPoint(subPath, [basePoint[0], data[0]]);
+            break;
+        case 'Q':
+            addPoint(subPath, data.slice(0, 2));
+            prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
+            break;
+        case 'T':
+            if (prev.instruction == 'Q' || prev.instruction == 'T') {
+                ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
+                addPoint(subPath, ctrlPoint);
+                prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]];
+            }
+            break;
+        case 'C':
+            // Approximate quibic Bezier curve with middle points between control points
+            addPoint(subPath, [.5 * (basePoint[0] + data[0]), .5 * (basePoint[1] + data[1])]);
+            addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
+            addPoint(subPath, [.5 * (data[2] + data[4]), .5 * (data[3] + data[5])]);
+            prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
+            break;
+        case 'S':
+            if (prev.instruction == 'C' || prev.instruction == 'S') {
+                addPoint(subPath, [basePoint[0] + .5 * prevCtrlPoint[0], basePoint[1] + .5 * prevCtrlPoint[1]]);
+                ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
+            }
+            addPoint(subPath, [.5 * (ctrlPoint[0] + data[0]), .5 * (ctrlPoint[1]+ data[1])]);
+            addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
+            prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
+            break;
+        case 'A':
+            // Convert the arc to bezier curves and use the same approximation
+            var curves = a2c.apply(0, basePoint.concat(data));
+            for (var cData; (cData = curves.splice(0,6).map(toAbsolute)).length;) {
+                addPoint(subPath, [.5 * (basePoint[0] + cData[0]), .5 * (basePoint[1] + cData[1])]);
+                addPoint(subPath, [.5 * (cData[0] + cData[2]), .5 * (cData[1] + cData[3])]);
+                addPoint(subPath, [.5 * (cData[2] + cData[4]), .5 * (cData[3] + cData[5])]);
+                if (curves.length) addPoint(subPath, basePoint = cData.slice(-2));
+            }
+            break;
+    }
+    // Save final command coordinates
+    if (data && data.length >= 2) addPoint(subPath, data.slice(-2));
+    return points;
+
+    function toAbsolute(n, i) { return n + basePoint[i % 2] }
+
+    // Writes data about the extreme points on each axle
+    function addPoint(path, point) {
+        if (!path.length || point[1] > path[path.maxY][1]) {
+            path.maxY = path.length;
+            points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1];
+        }
+        if (!path.length || point[0] > path[path.maxX][0]) {
+            path.maxX = path.length;
+            points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0];
+        }
+        if (!path.length || point[1] < path[path.minY][1]) {
+            path.minY = path.length;
+            points.minY = points.length ? Math.min(point[1], points.minY) : point[1];
+        }
+        if (!path.length || point[0] < path[path.minX][0]) {
+            path.minX = path.length;
+            points.minX = points.length ? Math.min(point[0], points.minX) : point[0];
+        }
+        path.push(point);
+    }
+}
+
+/**
+ * Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm.
+ * http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
+ *
+ * @param points An array of [X, Y] coordinates
+ */
+function convexHull(points) {
+    /* jshint -W004 */
+
+    points.sort(function(a, b) {
+        return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
+    });
+
+    var lower = [],
+        minY = 0,
+        bottom = 0;
+    for (var i = 0; i < points.length; i++) {
+        while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
+            lower.pop();
+        }
+        if (points[i][1] < points[minY][1]) {
+            minY = i;
+            bottom = lower.length;
+        }
+        lower.push(points[i]);
+    }
+
+    var upper = [],
+        maxY = points.length - 1,
+        top = 0;
+    for (var i = points.length; i--;) {
+        while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
+            upper.pop();
+        }
+        if (points[i][1] > points[maxY][1]) {
+            maxY = i;
+            top = upper.length;
+        }
+        upper.push(points[i]);
+    }
+
+    // last points are equal to starting points of the other part
+    upper.pop();
+    lower.pop();
+
+    var hull = lower.concat(upper);
+
+    hull.minX = 0; // by sorting
+    hull.maxX = lower.length;
+    hull.minY = bottom;
+    hull.maxY = (lower.length + top) % hull.length;
+
+    return hull;
+}
+
+function cross(o, a, b) {
+    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
+}
+
+/* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
+ * Thanks to Dmitry Baranovskiy for his great work!
+ */
+
+// jshint ignore: start
+function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+    // for more information of where this Math came from visit:
+    // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+    var _120 = Math.PI * 120 / 180,
+        rad = Math.PI / 180 * (+angle || 0),
+        res = [],
+        rotateX = function(x, y, rad) { return x * Math.cos(rad) - y * Math.sin(rad) },
+        rotateY = function(x, y, rad) { return x * Math.sin(rad) + y * Math.cos(rad) };
+    if (!recursive) {
+        x1 = rotateX(x1, y1, -rad);
+        y1 = rotateY(x1, y1, -rad);
+        x2 = rotateX(x2, y2, -rad);
+        y2 = rotateY(x2, y2, -rad);
+        var x = (x1 - x2) / 2,
+            y = (y1 - y2) / 2;
+        var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+        if (h > 1) {
+            h = Math.sqrt(h);
+            rx = h * rx;
+            ry = h * ry;
+        }
+        var rx2 = rx * rx,
+            ry2 = ry * ry,
+            k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+            cx = k * rx * y / ry + (x1 + x2) / 2,
+            cy = k * -ry * x / rx + (y1 + y2) / 2,
+            f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
+            f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
+
+        f1 = x1 < cx ? Math.PI - f1 : f1;
+        f2 = x2 < cx ? Math.PI - f2 : f2;
+        f1 < 0 && (f1 = Math.PI * 2 + f1);
+        f2 < 0 && (f2 = Math.PI * 2 + f2);
+        if (sweep_flag && f1 > f2) {
+            f1 = f1 - Math.PI * 2;
+        }
+        if (!sweep_flag && f2 > f1) {
+            f2 = f2 - Math.PI * 2;
+        }
+    } else {
+        f1 = recursive[0];
+        f2 = recursive[1];
+        cx = recursive[2];
+        cy = recursive[3];
+    }
+    var df = f2 - f1;
+    if (Math.abs(df) > _120) {
+        var f2old = f2,
+            x2old = x2,
+            y2old = y2;
+        f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+        x2 = cx + rx * Math.cos(f2);
+        y2 = cy + ry * Math.sin(f2);
+        res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+    }
+    df = f2 - f1;
+    var c1 = Math.cos(f1),
+        s1 = Math.sin(f1),
+        c2 = Math.cos(f2),
+        s2 = Math.sin(f2),
+        t = Math.tan(df / 4),
+        hx = 4 / 3 * rx * t,
+        hy = 4 / 3 * ry * t,
+        m = [
+            - hx * s1, hy * c1,
+            x2 + hx * s2 - x1, y2 - hy * c2 - y1,
+            x2 - x1, y2 - y1
+        ];
+    if (recursive) {
+        return m.concat(res);
+    } else {
+        res = m.concat(res);
+        var newres = [];
+        for (var i = 0, n = res.length; i < n; i++) {
+            newres[i] = i % 2 ? rotateY(res[i - 1], res[i], rad) : rotateX(res[i], res[i + 1], rad);
+        }
+        return newres;
+    }
+}
+// jshint ignore: end
Index: frontend/node_modules/svgo/plugins/_transforms.js
===================================================================
--- frontend/node_modules/svgo/plugins/_transforms.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/_transforms.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,310 @@
+'use strict';
+
+var regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/,
+    regTransformSplit = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/,
+    regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
+
+/**
+ * Convert transform string to JS representation.
+ *
+ * @param {String} transformString input string
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+exports.transform2js = function(transformString) {
+
+        // JS representation of the transform data
+    var transforms = [],
+        // current transform context
+        current;
+
+    // split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
+    transformString.split(regTransformSplit).forEach(function(item) {
+        /*jshint -W084 */
+        var num;
+
+        if (item) {
+            // if item is a translate function
+            if (regTransformTypes.test(item)) {
+                // then collect it and change current context
+                transforms.push(current = { name: item });
+            // else if item is data
+            } else {
+                // then split it into [10, 50] and collect as context.data
+                while (num = regNumericValues.exec(item)) {
+                    num = Number(num);
+                    if (current.data)
+                        current.data.push(num);
+                    else
+                        current.data = [num];
+                }
+            }
+        }
+    });
+
+    // return empty array if broken transform (no data)
+    return current && current.data ? transforms : [];
+};
+
+/**
+ * Multiply transforms into one.
+ *
+ * @param {Array} input transforms array
+ * @return {Array} output matrix array
+ */
+exports.transformsMultiply = function(transforms) {
+
+    // convert transforms objects to the matrices
+    transforms = transforms.map(function(transform) {
+        if (transform.name === 'matrix') {
+            return transform.data;
+        }
+        return transformToMatrix(transform);
+    });
+
+    // multiply all matrices into one
+    transforms = {
+        name: 'matrix',
+        data: transforms.length > 0 ? transforms.reduce(multiplyTransformMatrices) : []
+    };
+
+    return transforms;
+
+};
+
+/**
+ * Do math like a schoolgirl.
+ *
+ * @type {Object}
+ */
+var mth = exports.mth = {
+
+    rad: function(deg) {
+        return deg * Math.PI / 180;
+    },
+
+    deg: function(rad) {
+        return rad * 180 / Math.PI;
+    },
+
+    cos: function(deg) {
+        return Math.cos(this.rad(deg));
+    },
+
+    acos: function(val, floatPrecision) {
+        return +(this.deg(Math.acos(val)).toFixed(floatPrecision));
+    },
+
+    sin: function(deg) {
+        return Math.sin(this.rad(deg));
+    },
+
+    asin: function(val, floatPrecision) {
+        return +(this.deg(Math.asin(val)).toFixed(floatPrecision));
+    },
+
+    tan: function(deg) {
+        return Math.tan(this.rad(deg));
+    },
+
+    atan: function(val, floatPrecision) {
+        return +(this.deg(Math.atan(val)).toFixed(floatPrecision));
+    }
+
+};
+
+/**
+ * Decompose matrix into simple transforms. See
+ * http://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
+ *
+ * @param {Object} data matrix transform object
+ * @return {Object|Array} transforms array or original transform object
+ */
+exports.matrixToTransform = function(transform, params) {
+    var floatPrecision = params.floatPrecision,
+        data = transform.data,
+        transforms = [],
+        sx = +Math.hypot(data[0], data[1]).toFixed(params.transformPrecision),
+        sy = +((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(params.transformPrecision),
+        colsSum = data[0] * data[2] + data[1] * data[3],
+        rowsSum = data[0] * data[1] + data[2] * data[3],
+        scaleBefore = rowsSum != 0 || sx == sy;
+
+    // [..., ..., ..., ..., tx, ty] → translate(tx, ty)
+    if (data[4] || data[5]) {
+        transforms.push({ name: 'translate', data: data.slice(4, data[5] ? 6 : 5) });
+    }
+
+    // [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy)
+    if (!data[1] && data[2]) {
+        transforms.push({ name: 'skewX', data: [mth.atan(data[2] / sy, floatPrecision)] });
+
+    // [sx, sx·tan(a), 0, sy, 0, 0] → skewY(a)·scale(sx, sy)
+    } else if (data[1] && !data[2]) {
+        transforms.push({ name: 'skewY', data: [mth.atan(data[1] / data[0], floatPrecision)] });
+        sx = data[0];
+        sy = data[3];
+
+    // [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or
+    // [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore)
+    } else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) {
+        if (!scaleBefore) {
+            sx = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
+            sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], data[3]);
+            transforms.push({ name: 'scale', data: [sx, sy] });
+        }
+        var angle = Math.min(Math.max(-1, data[0] / sx), 1),
+            rotate = [mth.acos(angle, floatPrecision) * ((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1)];
+
+        if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
+
+        if (rowsSum && colsSum) transforms.push({
+            name: 'skewX',
+            data: [mth.atan(colsSum / (sx * sx), floatPrecision)]
+        });
+
+        // rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point)
+        if (rotate[0] && (data[4] || data[5])) {
+            transforms.shift();
+            var cos = data[0] / sx,
+                sin = data[1] / (scaleBefore ? sx : sy),
+                x = data[4] * (scaleBefore || sy),
+                y = data[5] * (scaleBefore || sx),
+                denom = (Math.pow(1 - cos, 2) + Math.pow(sin, 2)) * (scaleBefore || sx * sy);
+            rotate.push(((1 - cos) * x - sin * y) / denom);
+            rotate.push(((1 - cos) * y + sin * x) / denom);
+        }
+
+    // Too many transformations, return original matrix if it isn't just a scale/translate
+    } else if (data[1] || data[2]) {
+        return transform;
+    }
+
+    if (scaleBefore && (sx != 1 || sy != 1) || !transforms.length) transforms.push({
+        name: 'scale',
+        data: sx == sy ? [sx] : [sx, sy]
+    });
+
+    return transforms;
+};
+
+/**
+ * Convert transform to the matrix data.
+ *
+ * @param {Object} transform transform object
+ * @return {Array} matrix data
+ */
+function transformToMatrix(transform) {
+
+    if (transform.name === 'matrix') return transform.data;
+
+    var matrix;
+
+    switch (transform.name) {
+        case 'translate':
+            // [1, 0, 0, 1, tx, ty]
+            matrix = [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
+            break;
+        case 'scale':
+            // [sx, 0, 0, sy, 0, 0]
+            matrix = [transform.data[0], 0, 0, transform.data[1] || transform.data[0], 0, 0];
+            break;
+        case 'rotate':
+            // [cos(a), sin(a), -sin(a), cos(a), x, y]
+            var cos = mth.cos(transform.data[0]),
+                sin = mth.sin(transform.data[0]),
+                cx = transform.data[1] || 0,
+                cy = transform.data[2] || 0;
+
+            matrix = [cos, sin, -sin, cos, (1 - cos) * cx + sin * cy, (1 - cos) * cy - sin * cx];
+            break;
+        case 'skewX':
+            // [1, 0, tan(a), 1, 0, 0]
+            matrix = [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
+            break;
+        case 'skewY':
+            // [1, tan(a), 0, 1, 0, 0]
+            matrix = [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
+            break;
+    }
+
+    return matrix;
+
+}
+
+/**
+ * Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it
+ * by the transformation matrix and use a singular value decomposition to represent in a form
+ * rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
+ * SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
+ *
+ * @param {Array} arc [a, b, rotation in deg]
+ * @param {Array} transform transformation matrix
+ * @return {Array} arc transformed input arc
+ */
+exports.transformArc = function(arc, transform) {
+
+    var a = arc[0],
+        b = arc[1],
+        rot = arc[2] * Math.PI / 180,
+        cos = Math.cos(rot),
+        sin = Math.sin(rot),
+        h = Math.pow(arc[5] * cos + arc[6] * sin, 2) / (4 * a * a) +
+            Math.pow(arc[6] * cos - arc[5] * sin, 2) / (4 * b * b);
+    if (h > 1) {
+        h = Math.sqrt(h);
+        a *= h;
+        b *= h;
+    }
+    var ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0],
+        m = multiplyTransformMatrices(transform, ellipse),
+        // Decompose the new ellipse matrix
+        lastCol = m[2] * m[2] + m[3] * m[3],
+        squareSum = m[0] * m[0] + m[1] * m[1] + lastCol,
+        root = Math.hypot(m[0] - m[3], m[1] + m[2]) * Math.hypot(m[0] + m[3], m[1] - m[2]);
+
+    if (!root) { // circle
+        arc[0] = arc[1] = Math.sqrt(squareSum / 2);
+        arc[2] = 0;
+    } else {
+        var majorAxisSqr = (squareSum + root) / 2,
+            minorAxisSqr = (squareSum - root) / 2,
+            major = Math.abs(majorAxisSqr - lastCol) > 1e-6,
+            sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol,
+            rowsSum = m[0] * m[2] + m[1] * m[3],
+            term1 = m[0] * sub + m[2] * rowsSum,
+            term2 = m[1] * sub + m[3] * rowsSum;
+        arc[0] = Math.sqrt(majorAxisSqr);
+        arc[1] = Math.sqrt(minorAxisSqr);
+        arc[2] = ((major ? term2 < 0 : term1 > 0) ? -1 : 1) *
+            Math.acos((major ? term1 : term2) / Math.hypot(term1, term2)) * 180 / Math.PI;
+    }
+
+    if ((transform[0] < 0) !== (transform[3] < 0)) {
+        // Flip the sweep flag if coordinates are being flipped horizontally XOR vertically
+        arc[4] = 1 - arc[4];
+    }
+
+    return arc;
+
+};
+
+/**
+ * Multiply transformation matrices.
+ *
+ * @param {Array} a matrix A data
+ * @param {Array} b matrix B data
+ * @return {Array} result
+ */
+function multiplyTransformMatrices(a, b) {
+
+    return [
+        a[0] * b[0] + a[2] * b[1],
+        a[1] * b[0] + a[3] * b[1],
+        a[0] * b[2] + a[2] * b[3],
+        a[1] * b[2] + a[3] * b[3],
+        a[0] * b[4] + a[2] * b[5] + a[4],
+        a[1] * b[4] + a[3] * b[5] + a[5]
+    ];
+
+}
Index: frontend/node_modules/svgo/plugins/addAttributesToSVGElement.js
===================================================================
--- frontend/node_modules/svgo/plugins/addAttributesToSVGElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/addAttributesToSVGElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,82 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = false;
+
+exports.description = 'adds attributes to an outer <svg> element';
+
+var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
+It should have a list of "attributes" or one "attribute".
+Config example:
+
+plugins:
+- addAttributesToSVGElement:
+    attribute: "mySvg"
+
+plugins:
+- addAttributesToSVGElement:
+    attributes: ["mySvg", "size-big"]
+
+plugins:
+- addAttributesToSVGElement:
+    attributes:
+        - focusable: false
+        - data-image: icon`;
+
+/**
+ * Add attributes to an outer <svg> element. Example config:
+ *
+ * plugins:
+ * - addAttributesToSVGElement:
+ *     attribute: 'data-icon'
+ *
+ * plugins:
+ * - addAttributesToSVGElement:
+ *     attributes: ['data-icon', 'data-disabled']
+ *
+ * plugins:
+ * - addAttributesToSVGElement:
+ *     attributes:
+ *         - focusable: false
+ *         - data-image: icon
+ *
+ * @author April Arcus
+ */
+exports.fn = function(data, params) {
+    if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
+        console.error(ENOCLS);
+        return data;
+    }
+
+    var attributes = params.attributes || [ params.attribute ],
+        svg = data.content[0];
+
+    if (svg.isElem('svg')) {
+        attributes.forEach(function (attribute) {
+            if (typeof attribute === 'string') {
+                if (!svg.hasAttr(attribute)) {
+                    svg.addAttr({
+                        name: attribute,
+                        prefix: '',
+                        local: attribute
+                    });
+                }
+            } else if (typeof attribute === 'object') {
+                Object.keys(attribute).forEach(function (key) {
+                    if (!svg.hasAttr(key)) {
+                        svg.addAttr({
+                            name: key,
+                            value: attribute[key],
+                            prefix: '',
+                            local: key
+                        });
+                    }
+                });
+            }
+        });
+    }
+
+    return data;
+
+};
Index: frontend/node_modules/svgo/plugins/addClassesToSVGElement.js
===================================================================
--- frontend/node_modules/svgo/plugins/addClassesToSVGElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/addClassesToSVGElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,50 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = false;
+
+exports.description = 'adds classnames to an outer <svg> element';
+
+var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
+It should have a list of classes in "classNames" or one "className".
+Config example:
+
+plugins:
+- addClassesToSVGElement:
+    className: "mySvg"
+
+plugins:
+- addClassesToSVGElement:
+    classNames: ["mySvg", "size-big"]
+`;
+
+/**
+ * Add classnames to an outer <svg> element. Example config:
+ *
+ * plugins:
+ * - addClassesToSVGElement:
+ *     className: 'mySvg'
+ *
+ * plugins:
+ * - addClassesToSVGElement:
+ *     classNames: ['mySvg', 'size-big']
+ *
+ * @author April Arcus
+ */
+exports.fn = function(data, params) {
+    if (!params || !(Array.isArray(params.classNames) && params.classNames.some(String) || params.className)) {
+        console.error(ENOCLS);
+        return data;
+    }
+
+    var classNames = params.classNames || [ params.className ],
+        svg = data.content[0];
+
+    if (svg.isElem('svg')) {
+        svg.class.add.apply(svg.class, classNames);
+    }
+
+    return data;
+
+};
Index: frontend/node_modules/svgo/plugins/cleanupAttrs.js
===================================================================
--- frontend/node_modules/svgo/plugins/cleanupAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/cleanupAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'cleanups attributes from newlines, trailing and repeating spaces';
+
+exports.params = {
+    newlines: true,
+    trim: true,
+    spaces: true
+};
+
+var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g,
+    regNewlines = /\r?\n/g,
+    regSpaces = /\s{2,}/g;
+
+/**
+ * Cleanup attributes values from newlines, trailing and repeating spaces.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.isElem()) {
+
+        item.eachAttr(function(attr) {
+
+            if (params.newlines) {
+                // new line which requires a space instead of themselve
+                attr.value = attr.value.replace(regNewlinesNeedSpace, function(match, p1, p2) {
+                    return p1 + ' ' + p2;
+                });
+
+                // simple new line
+                attr.value = attr.value.replace(regNewlines, '');
+            }
+
+            if (params.trim) {
+                attr.value = attr.value.trim();
+            }
+
+            if (params.spaces) {
+                attr.value = attr.value.replace(regSpaces, ' ');
+            }
+
+        });
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/cleanupEnableBackground.js
===================================================================
--- frontend/node_modules/svgo/plugins/cleanupEnableBackground.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/cleanupEnableBackground.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,84 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'remove or cleanup enable-background attribute when possible';
+
+/**
+ * Remove or cleanup enable-background attr which coincides with a width/height box.
+ *
+ * @see http://www.w3.org/TR/SVG/filters.html#EnableBackgroundProperty
+ *
+ * @example
+ * <svg width="100" height="50" enable-background="new 0 0 100 50">
+ *             ⬇
+ * <svg width="100" height="50">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(data) {
+
+    var regEnableBackground = /^new\s0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/,
+        hasFilter = false,
+        elems = ['svg', 'mask', 'pattern'];
+
+    function checkEnableBackground(item) {
+        if (
+            item.isElem(elems) &&
+            item.hasAttr('enable-background') &&
+            item.hasAttr('width') &&
+            item.hasAttr('height')
+        ) {
+
+            var match = item.attr('enable-background').value.match(regEnableBackground);
+
+            if (match) {
+                if (
+                    item.attr('width').value === match[1] &&
+                    item.attr('height').value === match[3]
+                ) {
+                    if (item.isElem('svg')) {
+                        item.removeAttr('enable-background');
+                    } else {
+                        item.attr('enable-background').value = 'new';
+                    }
+                }
+            }
+
+        }
+    }
+
+    function checkForFilter(item) {
+        if (item.isElem('filter')) {
+            hasFilter = true;
+        }
+    }
+
+    function monkeys(items, fn) {
+        items.content.forEach(function(item) {
+            fn(item);
+
+            if (item.content) {
+                monkeys(item, fn);
+            }
+        });
+        return items;
+    }
+
+    var firstStep = monkeys(data, function(item) {
+        checkEnableBackground(item);
+        if (!hasFilter) {
+            checkForFilter(item);
+        }
+    });
+
+    return hasFilter ? firstStep : monkeys(firstStep, function(item) {
+            //we don't need 'enable-background' if we have no filters
+            item.removeAttr('enable-background');
+        });
+};
Index: frontend/node_modules/svgo/plugins/cleanupIDs.js
===================================================================
--- frontend/node_modules/svgo/plugins/cleanupIDs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/cleanupIDs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,209 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'removes unused IDs and minifies used';
+
+exports.params = {
+    remove: true,
+    minify: true,
+    prefix: '',
+    preserve: [],
+    preservePrefixes: [],
+    force: false
+};
+
+var referencesProps = new Set(require('./_collections').referencesProps),
+    regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
+    regReferencesHref = /^#(.+?)$/,
+    regReferencesBegin = /(\w+)\./,
+    styleOrScript = ['style', 'script'],
+    generateIDchars = [
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
+    ],
+    maxIDindex = generateIDchars.length - 1;
+
+/**
+ * Remove unused and minify used IDs
+ * (only if there are no any <style> or <script>).
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(data, params) {
+    var currentID,
+        currentIDstring,
+        IDs = new Map(),
+        referencesIDs = new Map(),
+        hasStyleOrScript = false,
+        preserveIDs = new Set(Array.isArray(params.preserve) ? params.preserve : params.preserve ? [params.preserve] : []),
+        preserveIDPrefixes = new Set(Array.isArray(params.preservePrefixes) ? params.preservePrefixes : (params.preservePrefixes ? [params.preservePrefixes] : [])),
+        idValuePrefix = '#',
+        idValuePostfix = '.';
+
+    /**
+     * Bananas!
+     *
+     * @param {Array} items input items
+     * @return {Array} output items
+     */
+    function monkeys(items) {
+        for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
+            var item = items.content[i];
+
+            // quit if <style> or <script> present ('force' param prevents quitting)
+            if (!params.force) {
+                if (item.isElem(styleOrScript)) {
+                    hasStyleOrScript = true;
+                    continue;
+                }
+                // Don't remove IDs if the whole SVG consists only of defs.
+                if (item.isElem('defs') && item.parentNode.isElem('svg')) {
+                    var hasDefsOnly = true;
+                    for (var j = i + 1; j < items.content.length; j++) {
+                        if (items.content[j].isElem()) {
+                            hasDefsOnly = false;
+                            break;
+                        }
+                    }
+                    if (hasDefsOnly) {
+                        break;
+                    }
+                }
+            }
+            // …and don't remove any ID if yes
+            if (item.isElem()) {
+                item.eachAttr(function(attr) {
+                    var key, match;
+
+                    // save IDs
+                    if (attr.name === 'id') {
+                        key = attr.value;
+                        if (IDs.has(key)) {
+                            item.removeAttr('id'); // remove repeated id
+                        } else {
+                            IDs.set(key, item);
+                        }
+                        return;
+                    }
+                    // save references
+                    if (referencesProps.has(attr.name) && (match = attr.value.match(regReferencesUrl))) {
+                        key = match[2]; // url() reference
+                    } else if (
+                        attr.local === 'href' && (match = attr.value.match(regReferencesHref)) ||
+                        attr.name === 'begin' && (match = attr.value.match(regReferencesBegin))
+                    ) {
+                        key = match[1]; // href reference
+                    }
+                    if (key) {
+                        var ref = referencesIDs.get(key) || [];
+                        ref.push(attr);
+                        referencesIDs.set(key, ref);
+                    }
+                });
+            }
+            // go deeper
+            if (item.content) {
+                monkeys(item);
+            }
+        }
+        return items;
+    }
+
+    data = monkeys(data);
+
+    if (hasStyleOrScript) {
+        return data;
+    }
+
+    const idPreserved = id => preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id);
+
+    for (var ref of referencesIDs) {
+        var key = ref[0];
+
+        if (IDs.has(key)) {
+            // replace referenced IDs with the minified ones
+            if (params.minify && !idPreserved(key)) {
+                do {
+                    currentIDstring = getIDstring(currentID = generateID(currentID), params);
+                } while (idPreserved(currentIDstring));
+
+                IDs.get(key).attr('id').value = currentIDstring;
+
+                for (var attr of ref[1]) {
+                    attr.value = attr.value.includes(idValuePrefix) ?
+                        attr.value.replace(idValuePrefix + key, idValuePrefix + currentIDstring) :
+                        attr.value.replace(key + idValuePostfix, currentIDstring + idValuePostfix);
+                }
+            }
+            // don't remove referenced IDs
+            IDs.delete(key);
+        }
+    }
+    // remove non-referenced IDs attributes from elements
+    if (params.remove) {
+        for(var keyElem of IDs) {
+            if (!idPreserved(keyElem[0])) {
+                keyElem[1].removeAttr('id');
+            }
+        }
+    }
+    return data;
+};
+
+/**
+ * Check if an ID starts with any one of a list of strings.
+ *
+ * @param {Array} of prefix strings
+ * @param {String} current ID
+ * @return {Boolean} if currentID starts with one of the strings in prefixArray
+ */
+function idMatchesPrefix(prefixArray, currentID) {
+    if (!currentID) return false;
+
+    for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true;
+    return false;
+}
+
+/**
+ * Generate unique minimal ID.
+ *
+ * @param {Array} [currentID] current ID
+ * @return {Array} generated ID array
+ */
+function generateID(currentID) {
+    if (!currentID) return [0];
+
+    currentID[currentID.length - 1]++;
+
+    for(var i = currentID.length - 1; i > 0; i--) {
+        if (currentID[i] > maxIDindex) {
+            currentID[i] = 0;
+
+            if (currentID[i - 1] !== undefined) {
+                currentID[i - 1]++;
+            }
+        }
+    }
+    if (currentID[0] > maxIDindex) {
+        currentID[0] = 0;
+        currentID.unshift(0);
+    }
+    return currentID;
+}
+
+/**
+ * Get string from generated ID array.
+ *
+ * @param {Array} arr input ID array
+ * @return {String} output ID string
+ */
+function getIDstring(arr, params) {
+    var str = params.prefix;
+    return str + arr.map(i => generateIDchars[i]).join('');
+}
Index: frontend/node_modules/svgo/plugins/cleanupListOfValues.js
===================================================================
--- frontend/node_modules/svgo/plugins/cleanupListOfValues.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/cleanupListOfValues.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,139 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'rounds list of values to the fixed precision';
+
+exports.params = {
+    floatPrecision: 3,
+    leadingZero: true,
+    defaultPx: true,
+    convertToPx: true
+};
+
+var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
+    regSeparator = /\s+,?\s*|,\s*/,
+    removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
+    absoluteLengths = { // relative to px
+        cm: 96/2.54,
+        mm: 96/25.4,
+        in: 96,
+        pt: 4/3,
+        pc: 16
+    };
+
+/**
+ * Round list of values to the fixed precision.
+ *
+ * @example
+ * <svg viewBox="0 0 200.28423 200.28423" enable-background="new 0 0 200.28423 200.28423">
+ *         ⬇
+ * <svg viewBox="0 0 200.284 200.284" enable-background="new 0 0 200.284 200.284">
+ *
+ *
+ * <polygon points="208.250977 77.1308594 223.069336 ... "/>
+ *         ⬇
+ * <polygon points="208.251 77.131 223.069 ... "/>
+ *
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author kiyopikko
+ */
+exports.fn = function(item, params) {
+
+
+    if ( item.hasAttr('points') ) {
+        roundValues(item.attrs.points);
+    }
+
+    if ( item.hasAttr('enable-background') ) {
+        roundValues(item.attrs['enable-background']);
+    }
+
+    if ( item.hasAttr('viewBox') ) {
+        roundValues(item.attrs.viewBox);
+    }
+
+    if ( item.hasAttr('stroke-dasharray') ) {
+        roundValues(item.attrs['stroke-dasharray']);
+    }
+
+    if ( item.hasAttr('dx') ) {
+        roundValues(item.attrs.dx);
+    }
+
+    if ( item.hasAttr('dy') ) {
+        roundValues(item.attrs.dy);
+    }
+
+    if ( item.hasAttr('x') ) {
+        roundValues(item.attrs.x);
+    }
+
+    if ( item.hasAttr('y') ) {
+        roundValues(item.attrs.y);
+    }
+
+
+    function roundValues($prop){
+
+        var num, units,
+            match,
+            matchNew,
+            lists = $prop.value,
+            listsArr = lists.split(regSeparator),
+            roundedListArr = [],
+            roundedList;
+
+        listsArr.forEach(function(elem){
+
+            match = elem.match(regNumericValues);
+            matchNew = elem.match(/new/);
+
+             // if attribute value matches regNumericValues
+            if (match) {
+                // round it to the fixed precision
+                num = +(+match[1]).toFixed(params.floatPrecision),
+                units = match[3] || '';
+
+                // convert absolute values to pixels
+                if (params.convertToPx && units && (units in absoluteLengths)) {
+                    var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision);
+
+                    if (String(pxNum).length < match[0].length)
+                        num = pxNum,
+                        units = 'px';
+                }
+
+                 // and remove leading zero
+                if (params.leadingZero) {
+                    num = removeLeadingZero(num);
+                }
+
+                // remove default 'px' units
+                if (params.defaultPx && units === 'px') {
+                    units = '';
+                }
+
+                roundedListArr.push(num+units);
+            }
+            // if attribute value is "new"(only enable-background).
+            else if (matchNew) {
+                roundedListArr.push('new');
+            } else if (elem) {
+                roundedListArr.push(elem);
+            }
+
+        });
+
+        roundedList = roundedListArr.join(' ');
+        $prop.value = roundedList;
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/cleanupNumericValues.js
===================================================================
--- frontend/node_modules/svgo/plugins/cleanupNumericValues.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/cleanupNumericValues.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,88 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'rounds numeric values to the fixed precision, removes default ‘px’ units';
+
+exports.params = {
+    floatPrecision: 3,
+    leadingZero: true,
+    defaultPx: true,
+    convertToPx: true
+};
+
+var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
+    removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
+    absoluteLengths = { // relative to px
+        cm: 96/2.54,
+        mm: 96/25.4,
+        in: 96,
+        pt: 4/3,
+        pc: 16
+    };
+
+/**
+ * Round numeric values to the fixed precision,
+ * remove default 'px' units.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.isElem()) {
+
+        var floatPrecision = params.floatPrecision;
+
+        if (item.hasAttr('viewBox')) {
+            var nums = item.attr('viewBox').value.split(/\s,?\s*|,\s*/g);
+            item.attr('viewBox').value = nums.map(function(value) {
+                var num = +value;
+                return isNaN(num) ? value : +num.toFixed(floatPrecision);
+            }).join(' ');
+        }
+
+        item.eachAttr(function(attr) {
+            // The `version` attribute is a text string and cannot be rounded
+            if (attr.name === 'version') { return }
+
+            var match = attr.value.match(regNumericValues);
+
+            // if attribute value matches regNumericValues
+            if (match) {
+                // round it to the fixed precision
+                var num = +(+match[1]).toFixed(floatPrecision),
+                    units = match[3] || '';
+
+                // convert absolute values to pixels
+                if (params.convertToPx && units && (units in absoluteLengths)) {
+                    var pxNum = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision);
+
+                    if (String(pxNum).length < match[0].length) {
+                        num = pxNum;
+                        units = 'px';
+                    }
+                }
+
+                // and remove leading zero
+                if (params.leadingZero) {
+                    num = removeLeadingZero(num);
+                }
+
+                // remove default 'px' units
+                if (params.defaultPx && units === 'px') {
+                    units = '';
+                }
+
+                attr.value = num + units;
+            }
+        });
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/collapseGroups.js
===================================================================
--- frontend/node_modules/svgo/plugins/collapseGroups.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/collapseGroups.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,87 @@
+'use strict';
+
+exports.type = 'perItemReverse';
+
+exports.active = true;
+
+exports.description = 'collapses useless groups';
+
+var collections = require('./_collections'),
+    attrsInheritable = collections.inheritableAttrs,
+    animationElems = collections.elemsGroups.animation;
+
+function hasAnimatedAttr(item) {
+    /* jshint validthis:true */
+    return item.isElem(animationElems) && item.hasAttr('attributeName', this) ||
+        !item.isEmpty() && item.content.some(hasAnimatedAttr, this);
+}
+
+/*
+ * Collapse useless groups.
+ *
+ * @example
+ * <g>
+ *     <g attr1="val1">
+ *         <path d="..."/>
+ *     </g>
+ * </g>
+ *         ⬇
+ * <g>
+ *     <g>
+ *         <path attr1="val1" d="..."/>
+ *     </g>
+ * </g>
+ *         ⬇
+ * <path attr1="val1" d="..."/>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    // non-empty elements
+    if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) {
+        item.content.forEach(function(g, i) {
+            // non-empty groups
+            if (g.isElem('g') && !g.isEmpty()) {
+                // move group attibutes to the single content element
+                if (g.hasAttr() && g.content.length === 1) {
+                    var inner = g.content[0];
+
+                    if (inner.isElem() && !inner.hasAttr('id') && !g.hasAttr('filter') &&
+                        !(g.hasAttr('class') && inner.hasAttr('class')) && (
+                            !g.hasAttr('clip-path') && !g.hasAttr('mask') ||
+                            inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform')
+                        )
+                    ) {
+                        g.eachAttr(function(attr) {
+                            if (g.content.some(hasAnimatedAttr, attr.name)) return;
+
+                            if (!inner.hasAttr(attr.name)) {
+                                inner.addAttr(attr);
+                            } else if (attr.name == 'transform') {
+                                inner.attr(attr.name).value = attr.value + ' ' + inner.attr(attr.name).value;
+                            } else if (inner.hasAttr(attr.name, 'inherit')) {
+                                inner.attr(attr.name).value = attr.value;
+                            } else if (
+                                attrsInheritable.indexOf(attr.name) < 0 &&
+                                !inner.hasAttr(attr.name, attr.value)
+                            ) {
+                                return;
+                            }
+
+                            g.removeAttr(attr.name);
+                        });
+                    }
+                }
+
+                // collapse groups without attributes
+                if (!g.hasAttr() && !g.content.some(function(item) { return item.isElem(animationElems) })) {
+                    item.spliceContent(i, 1, g.content);
+                }
+            }
+        });
+    }
+};
Index: frontend/node_modules/svgo/plugins/convertColors.js
===================================================================
--- frontend/node_modules/svgo/plugins/convertColors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/convertColors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,130 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts colors: rgb() to #rrggbb and #rrggbb to #rgb';
+
+exports.params = {
+    currentColor: false,
+    names2hex: true,
+    rgb2hex: true,
+    shorthex: true,
+    shortname: true
+};
+
+var collections = require('./_collections'),
+    rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)',
+    rComma = '\\s*,\\s*',
+    regRGB = new RegExp('^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'),
+    regHEX = /^\#(([a-fA-F0-9])\2){3}$/,
+    none = /\bnone\b/i;
+
+/**
+ * Convert different colors formats in element attributes to hex.
+ *
+ * @see http://www.w3.org/TR/SVG/types.html#DataTypeColor
+ * @see http://www.w3.org/TR/SVG/single-page.html#types-ColorKeywords
+ *
+ * @example
+ * Convert color name keyword to long hex:
+ * fuchsia ➡ #ff00ff
+ *
+ * Convert rgb() to long hex:
+ * rgb(255, 0, 255) ➡ #ff00ff
+ * rgb(50%, 100, 100%) ➡ #7f64ff
+ *
+ * Convert long hex to short hex:
+ * #aabbcc ➡ #abc
+ *
+ * Convert hex to short name
+ * #000080 ➡ navy
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.elem) {
+
+        item.eachAttr(function(attr) {
+
+            if (collections.colorsProps.indexOf(attr.name) > -1) {
+
+                var val = attr.value,
+                    match;
+
+                // Convert colors to currentColor
+                if (params.currentColor) {
+                    if (typeof params.currentColor === 'string') {
+                        match = val === params.currentColor;
+                    } else if (params.currentColor.exec) {
+                        match = params.currentColor.exec(val);
+                    } else {
+                        match = !val.match(none);
+                    }
+                    if (match) {
+                        val = 'currentColor';
+                    }
+                }
+
+                // Convert color name keyword to long hex
+                if (params.names2hex && val.toLowerCase() in collections.colorsNames) {
+                    val = collections.colorsNames[val.toLowerCase()];
+                }
+
+                // Convert rgb() to long hex
+                if (params.rgb2hex && (match = val.match(regRGB))) {
+                    match = match.slice(1, 4).map(function(m) {
+                        if (m.indexOf('%') > -1)
+                            m = Math.round(parseFloat(m) * 2.55);
+
+                        return Math.max(0, Math.min(m, 255));
+                    });
+
+                    val = rgb2hex(match);
+                }
+
+                // Convert long hex to short hex
+                if (params.shorthex && (match = val.match(regHEX))) {
+                    val = '#' + match[0][1] + match[0][3] + match[0][5];
+                }
+
+                // Convert hex to short name
+                if (params.shortname) {
+                    var lowerVal = val.toLowerCase();
+                    if (lowerVal in collections.colorsShortNames) {
+                        val = collections.colorsShortNames[lowerVal];
+                    }
+                }
+
+                attr.value = val;
+
+            }
+
+        });
+
+    }
+
+};
+
+/**
+ * Convert [r, g, b] to #rrggbb.
+ *
+ * @see https://gist.github.com/983535
+ *
+ * @example
+ * rgb2hex([255, 255, 255]) // '#ffffff'
+ *
+ * @param {Array} rgb [r, g, b]
+ * @return {String} #rrggbb
+ *
+ * @author Jed Schmidt
+ */
+function rgb2hex(rgb) {
+    return '#' + ('00000' + (rgb[0] << 16 | rgb[1] << 8 | rgb[2]).toString(16)).slice(-6).toUpperCase();
+}
Index: frontend/node_modules/svgo/plugins/convertEllipseToCircle.js
===================================================================
--- frontend/node_modules/svgo/plugins/convertEllipseToCircle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/convertEllipseToCircle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,39 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts non-eccentric <ellipse>s to <circle>s';
+
+/**
+ * Converts non-eccentric <ellipse>s to <circle>s.
+ *
+ * @see http://www.w3.org/TR/SVG/shapes.html
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Taylor Hunt
+ */
+exports.fn = function(item) {
+    if (item.isElem('ellipse')) {
+      var rx = item.attr('rx').value || 0;
+      var ry = item.attr('ry').value || 0;
+
+      if (rx === ry ||
+          rx === 'auto' || ry === 'auto' // SVG2
+         ) {
+        var radius = rx !== 'auto' ? rx : ry;
+        item.renameElem('circle');
+        item.removeAttr(['rx', 'ry']);
+        item.addAttr({
+            name: 'r',
+            value: radius,
+            prefix: '',
+            local: 'r',
+          });
+      }
+  }
+  return;
+};
Index: frontend/node_modules/svgo/plugins/convertPathData.js
===================================================================
--- frontend/node_modules/svgo/plugins/convertPathData.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/convertPathData.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,971 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'optimizes path data: writes in shorter form, applies transformations';
+
+exports.params = {
+    applyTransforms: true,
+    applyTransformsStroked: true,
+    makeArcs: {
+        threshold: 2.5, // coefficient of rounding error
+        tolerance: 0.5  // percentage of radius
+    },
+    straightCurves: true,
+    lineShorthands: true,
+    curveSmoothShorthands: true,
+    floatPrecision: 3,
+    transformPrecision: 5,
+    removeUseless: true,
+    collapseRepeated: true,
+    utilizeAbsolute: true,
+    leadingZero: true,
+    negativeExtraSpace: true,
+    noSpaceAfterFlags: true,
+    forceAbsolutePath: false
+};
+
+var pathElems = require('./_collections.js').pathElems,
+    path2js = require('./_path.js').path2js,
+    js2path = require('./_path.js').js2path,
+    applyTransforms = require('./_path.js').applyTransforms,
+    cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
+    roundData,
+    precision,
+    error,
+    arcThreshold,
+    arcTolerance,
+    hasMarkerMid,
+    hasStrokeLinecap;
+
+/**
+ * Convert absolute Path to relative,
+ * collapse repeated instructions,
+ * detect and convert Lineto shorthands,
+ * remove useless instructions like "l0,0",
+ * trim useless delimiters and leading zeros,
+ * decrease accuracy of floating-point numbers.
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathData
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.isElem(pathElems) && item.hasAttr('d')) {
+
+        precision = params.floatPrecision;
+        error = precision !== false ? +Math.pow(.1, precision).toFixed(precision) : 1e-2;
+        roundData = precision > 0 && precision < 20 ? strongRound : round;
+        if (params.makeArcs) {
+            arcThreshold = params.makeArcs.threshold;
+            arcTolerance = params.makeArcs.tolerance;
+        }
+        hasMarkerMid = item.hasAttr('marker-mid');
+
+        var stroke = item.computedAttr('stroke'),
+            strokeLinecap = item.computedAttr('stroke');
+        hasStrokeLinecap = stroke && stroke != 'none' && strokeLinecap && strokeLinecap != 'butt';
+
+        var data = path2js(item);
+
+        // TODO: get rid of functions returns
+        if (data.length) {
+            convertToRelative(data);
+
+            if (params.applyTransforms) {
+                data = applyTransforms(item, data, params);
+            }
+
+            data = filters(data, params);
+
+            if (params.utilizeAbsolute) {
+                data = convertToMixed(data, params);
+            }
+
+            js2path(item, data, params);
+        }
+
+    }
+
+};
+
+/**
+ * Convert absolute path data coordinates to relative.
+ *
+ * @param {Array} path input path data
+ * @param {Object} params plugin params
+ * @return {Array} output path data
+ */
+function convertToRelative(path) {
+
+    var point = [0, 0],
+        subpathPoint = [0, 0],
+        baseItem;
+
+    path.forEach(function(item, index) {
+
+        var instruction = item.instruction,
+            data = item.data;
+
+        // data !== !z
+        if (data) {
+
+            // already relative
+            // recalculate current point
+            if ('mcslqta'.indexOf(instruction) > -1) {
+
+                point[0] += data[data.length - 2];
+                point[1] += data[data.length - 1];
+
+                if (instruction === 'm') {
+                    subpathPoint[0] = point[0];
+                    subpathPoint[1] = point[1];
+                    baseItem = item;
+                }
+
+            } else if (instruction === 'h') {
+
+                point[0] += data[0];
+
+            } else if (instruction === 'v') {
+
+                point[1] += data[0];
+
+            }
+
+            // convert absolute path data coordinates to relative
+            // if "M" was not transformed from "m"
+            // M → m
+            if (instruction === 'M') {
+
+                if (index > 0) instruction = 'm';
+
+                data[0] -= point[0];
+                data[1] -= point[1];
+
+                subpathPoint[0] = point[0] += data[0];
+                subpathPoint[1] = point[1] += data[1];
+
+                baseItem = item;
+
+            }
+
+            // L → l
+            // T → t
+            else if ('LT'.indexOf(instruction) > -1) {
+
+                instruction = instruction.toLowerCase();
+
+                // x y
+                // 0 1
+                data[0] -= point[0];
+                data[1] -= point[1];
+
+                point[0] += data[0];
+                point[1] += data[1];
+
+            // C → c
+            } else if (instruction === 'C') {
+
+                instruction = 'c';
+
+                // x1 y1 x2 y2 x y
+                // 0  1  2  3  4 5
+                data[0] -= point[0];
+                data[1] -= point[1];
+                data[2] -= point[0];
+                data[3] -= point[1];
+                data[4] -= point[0];
+                data[5] -= point[1];
+
+                point[0] += data[4];
+                point[1] += data[5];
+
+            // S → s
+            // Q → q
+            } else if ('SQ'.indexOf(instruction) > -1) {
+
+                instruction = instruction.toLowerCase();
+
+                // x1 y1 x y
+                // 0  1  2 3
+                data[0] -= point[0];
+                data[1] -= point[1];
+                data[2] -= point[0];
+                data[3] -= point[1];
+
+                point[0] += data[2];
+                point[1] += data[3];
+
+            // A → a
+            } else if (instruction === 'A') {
+
+                instruction = 'a';
+
+                // rx ry x-axis-rotation large-arc-flag sweep-flag x y
+                // 0  1  2               3              4          5 6
+                data[5] -= point[0];
+                data[6] -= point[1];
+
+                point[0] += data[5];
+                point[1] += data[6];
+
+            // H → h
+            } else if (instruction === 'H') {
+
+                instruction = 'h';
+
+                data[0] -= point[0];
+
+                point[0] += data[0];
+
+            // V → v
+            } else if (instruction === 'V') {
+
+                instruction = 'v';
+
+                data[0] -= point[1];
+
+                point[1] += data[0];
+
+            }
+
+            item.instruction = instruction;
+            item.data = data;
+
+            // store absolute coordinates for later use
+            item.coords = point.slice(-2);
+
+        }
+
+        // !data === z, reset current point
+        else if (instruction == 'z') {
+            if (baseItem) {
+                item.coords = baseItem.coords;
+            }
+            point[0] = subpathPoint[0];
+            point[1] = subpathPoint[1];
+        }
+
+        item.base = index > 0 ? path[index - 1].coords : [0, 0];
+
+    });
+
+    return path;
+
+}
+
+/**
+ * Main filters loop.
+ *
+ * @param {Array} path input path data
+ * @param {Object} params plugin params
+ * @return {Array} output path data
+ */
+function filters(path, params) {
+
+    var stringify = data2Path.bind(null, params),
+        relSubpoint = [0, 0],
+        pathBase = [0, 0],
+        prev = {};
+
+    path = path.filter(function(item, index, path) {
+
+        var instruction = item.instruction,
+            data = item.data,
+            next = path[index + 1];
+
+        if (data) {
+
+            var sdata = data,
+                circle;
+
+            if (instruction === 's') {
+                sdata = [0, 0].concat(data);
+
+                if ('cs'.indexOf(prev.instruction) > -1) {
+                    var pdata = prev.data,
+                        n = pdata.length;
+
+                    // (-x, -y) of the prev tangent point relative to the current point
+                    sdata[0] = pdata[n - 2] - pdata[n - 4];
+                    sdata[1] = pdata[n - 1] - pdata[n - 3];
+                }
+
+            }
+
+            // convert curves to arcs if possible
+            if (
+                params.makeArcs &&
+                (instruction == 'c' || instruction == 's') &&
+                isConvex(sdata) &&
+                (circle = findCircle(sdata))
+            ) {
+                var r = roundData([circle.radius])[0],
+                    angle = findArcAngle(sdata, circle),
+                    sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
+                    arc = {
+                        instruction: 'a',
+                        data: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
+                        coords: item.coords.slice(),
+                        base: item.base
+                    },
+                    output = [arc],
+                    // relative coordinates to adjust the found circle
+                    relCenter = [circle.center[0] - sdata[4], circle.center[1] - sdata[5]],
+                    relCircle = { center: relCenter, radius: circle.radius },
+                    arcCurves = [item],
+                    hasPrev = 0,
+                    suffix = '',
+                    nextLonghand;
+
+                if (
+                    prev.instruction == 'c' && isConvex(prev.data) && isArcPrev(prev.data, circle) ||
+                    prev.instruction == 'a' && prev.sdata && isArcPrev(prev.sdata, circle)
+                ) {
+                    arcCurves.unshift(prev);
+                    arc.base = prev.base;
+                    arc.data[5] = arc.coords[0] - arc.base[0];
+                    arc.data[6] = arc.coords[1] - arc.base[1];
+                    var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
+                    var prevAngle = findArcAngle(prevData,
+                        {
+                            center: [prevData[4] + circle.center[0], prevData[5] + circle.center[1]],
+                            radius: circle.radius
+                        }
+                    );
+                    angle += prevAngle;
+                    if (angle > Math.PI) arc.data[3] = 1;
+                    hasPrev = 1;
+                }
+
+                // check if next curves are fitting the arc
+                for (var j = index; (next = path[++j]) && ~'cs'.indexOf(next.instruction);) {
+                    var nextData = next.data;
+                    if (next.instruction == 's') {
+                        nextLonghand = makeLonghand({instruction: 's', data: next.data.slice() },
+                            path[j - 1].data);
+                        nextData = nextLonghand.data;
+                        nextLonghand.data = nextData.slice(0, 2);
+                        suffix = stringify([nextLonghand]);
+                    }
+                    if (isConvex(nextData) && isArc(nextData, relCircle)) {
+                        angle += findArcAngle(nextData, relCircle);
+                        if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
+                        if (angle > Math.PI) arc.data[3] = 1;
+                        arcCurves.push(next);
+                        if (2 * Math.PI - angle > 1e-3) { // less than 360°
+                            arc.coords = next.coords;
+                            arc.data[5] = arc.coords[0] - arc.base[0];
+                            arc.data[6] = arc.coords[1] - arc.base[1];
+                        } else {
+                            // full circle, make a half-circle arc and add a second one
+                            arc.data[5] = 2 * (relCircle.center[0] - nextData[4]);
+                            arc.data[6] = 2 * (relCircle.center[1] - nextData[5]);
+                            arc.coords = [arc.base[0] + arc.data[5], arc.base[1] + arc.data[6]];
+                            arc = {
+                                instruction: 'a',
+                                data: [r, r, 0, 0, sweep,
+                                    next.coords[0] - arc.coords[0], next.coords[1] - arc.coords[1]],
+                                coords: next.coords,
+                                base: arc.coords
+                            };
+                            output.push(arc);
+                            j++;
+                            break;
+                        }
+                        relCenter[0] -= nextData[4];
+                        relCenter[1] -= nextData[5];
+                    } else break;
+                }
+
+                if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
+                    if (path[j] && path[j].instruction == 's') {
+                        makeLonghand(path[j], path[j - 1].data);
+                    }
+                    if (hasPrev) {
+                        var prevArc = output.shift();
+                        roundData(prevArc.data);
+                        relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2];
+                        relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1];
+                        prev.instruction = 'a';
+                        prev.data = prevArc.data;
+                        item.base = prev.coords = prevArc.coords;
+                    }
+                    arc = output.shift();
+                    if (arcCurves.length == 1) {
+                        item.sdata = sdata.slice(); // preserve curve data for future checks
+                    } else if (arcCurves.length - 1 - hasPrev > 0) {
+                        // filter out consumed next items
+                        path.splice.apply(path, [index + 1, arcCurves.length - 1 - hasPrev].concat(output));
+                    }
+                    if (!arc) return false;
+                    instruction = 'a';
+                    data = arc.data;
+                    item.coords = arc.coords;
+                }
+            }
+
+            // Rounding relative coordinates, taking in account accummulating error
+            // to get closer to absolute coordinates. Sum of rounded value remains same:
+            // l .25 3 .25 2 .25 3 .25 2 -> l .3 3 .2 2 .3 3 .2 2
+            if (precision !== false) {
+                if ('mltqsc'.indexOf(instruction) > -1) {
+                    for (var i = data.length; i--;) {
+                        data[i] += item.base[i % 2] - relSubpoint[i % 2];
+                    }
+                } else if (instruction == 'h') {
+                    data[0] += item.base[0] - relSubpoint[0];
+                } else if (instruction == 'v') {
+                    data[0] += item.base[1] - relSubpoint[1];
+                } else if (instruction == 'a') {
+                    data[5] += item.base[0] - relSubpoint[0];
+                    data[6] += item.base[1] - relSubpoint[1];
+                }
+                roundData(data);
+
+                if      (instruction == 'h') relSubpoint[0] += data[0];
+                else if (instruction == 'v') relSubpoint[1] += data[0];
+                else {
+                    relSubpoint[0] += data[data.length - 2];
+                    relSubpoint[1] += data[data.length - 1];
+                }
+                roundData(relSubpoint);
+
+                if (instruction.toLowerCase() == 'm') {
+                    pathBase[0] = relSubpoint[0];
+                    pathBase[1] = relSubpoint[1];
+                }
+            }
+
+            // convert straight curves into lines segments
+            if (params.straightCurves) {
+
+                if (
+                    instruction === 'c' &&
+                    isCurveStraightLine(data) ||
+                    instruction === 's' &&
+                    isCurveStraightLine(sdata)
+                ) {
+                    if (next && next.instruction == 's')
+                        makeLonghand(next, data); // fix up next curve
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+
+                else if (
+                    instruction === 'q' &&
+                    isCurveStraightLine(data)
+                ) {
+                    if (next && next.instruction == 't')
+                        makeLonghand(next, data); // fix up next curve
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+
+                else if (
+                    instruction === 't' &&
+                    prev.instruction !== 'q' &&
+                    prev.instruction !== 't'
+                ) {
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+
+                else if (
+                    instruction === 'a' &&
+                    (data[0] === 0 || data[1] === 0)
+                ) {
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+            }
+
+            // horizontal and vertical line shorthands
+            // l 50 0 → h 50
+            // l 0 50 → v 50
+            if (
+                params.lineShorthands &&
+                instruction === 'l'
+            ) {
+                if (data[1] === 0) {
+                    instruction = 'h';
+                    data.pop();
+                } else if (data[0] === 0) {
+                    instruction = 'v';
+                    data.shift();
+                }
+            }
+
+            // collapse repeated commands
+            // h 20 h 30 -> h 50
+            if (
+                params.collapseRepeated &&
+                !hasMarkerMid &&
+                ('mhv'.indexOf(instruction) > -1) &&
+                prev.instruction &&
+                instruction == prev.instruction.toLowerCase() &&
+                (
+                    (instruction != 'h' && instruction != 'v') ||
+                    (prev.data[0] >= 0) == (item.data[0] >= 0)
+            )) {
+                prev.data[0] += data[0];
+                if (instruction != 'h' && instruction != 'v') {
+                    prev.data[1] += data[1];
+                }
+                prev.coords = item.coords;
+                path[index] = prev;
+                return false;
+            }
+
+            // convert curves into smooth shorthands
+            if (params.curveSmoothShorthands && prev.instruction) {
+
+                // curveto
+                if (instruction === 'c') {
+
+                    // c + c → c + s
+                    if (
+                        prev.instruction === 'c' &&
+                        data[0] === -(prev.data[2] - prev.data[4]) &&
+                        data[1] === -(prev.data[3] - prev.data[5])
+                    ) {
+                        instruction = 's';
+                        data = data.slice(2);
+                    }
+
+                    // s + c → s + s
+                    else if (
+                        prev.instruction === 's' &&
+                        data[0] === -(prev.data[0] - prev.data[2]) &&
+                        data[1] === -(prev.data[1] - prev.data[3])
+                    ) {
+                        instruction = 's';
+                        data = data.slice(2);
+                    }
+
+                    // [^cs] + c → [^cs] + s
+                    else if (
+                        'cs'.indexOf(prev.instruction) === -1 &&
+                        data[0] === 0 &&
+                        data[1] === 0
+                    ) {
+                        instruction = 's';
+                        data = data.slice(2);
+                    }
+
+                }
+
+                // quadratic Bézier curveto
+                else if (instruction === 'q') {
+
+                    // q + q → q + t
+                    if (
+                        prev.instruction === 'q' &&
+                        data[0] === (prev.data[2] - prev.data[0]) &&
+                        data[1] === (prev.data[3] - prev.data[1])
+                    ) {
+                        instruction = 't';
+                        data = data.slice(2);
+                    }
+
+                    // t + q → t + t
+                    else if (
+                        prev.instruction === 't' &&
+                        data[2] === prev.data[0] &&
+                        data[3] === prev.data[1]
+                    ) {
+                        instruction = 't';
+                        data = data.slice(2);
+                    }
+
+                }
+
+            }
+
+            // remove useless non-first path segments
+            if (params.removeUseless && !hasStrokeLinecap) {
+
+                // l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0
+                if (
+                    (
+                     'lhvqtcs'.indexOf(instruction) > -1
+                    ) &&
+                    data.every(function(i) { return i === 0; })
+                ) {
+                    path[index] = prev;
+                    return false;
+                }
+
+                // a 25,25 -30 0,1 0,0
+                if (
+                    instruction === 'a' &&
+                    data[5] === 0 &&
+                    data[6] === 0
+                ) {
+                    path[index] = prev;
+                    return false;
+                }
+
+            }
+
+            item.instruction = instruction;
+            item.data = data;
+
+            prev = item;
+
+        } else {
+
+            // z resets coordinates
+            relSubpoint[0] = pathBase[0];
+            relSubpoint[1] = pathBase[1];
+            if (prev.instruction == 'z') return false;
+            prev = item;
+
+        }
+
+        return true;
+
+    });
+
+    return path;
+
+}
+
+/**
+ * Writes data in shortest form using absolute or relative coordinates.
+ *
+ * @param {Array} data input path data
+ * @return {Boolean} output
+ */
+function convertToMixed(path, params) {
+
+    var prev = path[0];
+
+    path = path.filter(function(item, index) {
+
+        if (index == 0) return true;
+        if (!item.data) {
+            prev = item;
+            return true;
+        }
+
+        var instruction = item.instruction,
+            data = item.data,
+            adata = data && data.slice(0);
+
+        if ('mltqsc'.indexOf(instruction) > -1) {
+            for (var i = adata.length; i--;) {
+                adata[i] += item.base[i % 2];
+            }
+        } else if (instruction == 'h') {
+                adata[0] += item.base[0];
+        } else if (instruction == 'v') {
+                adata[0] += item.base[1];
+        } else if (instruction == 'a') {
+                adata[5] += item.base[0];
+                adata[6] += item.base[1];
+        }
+
+        roundData(adata);
+
+        var absoluteDataStr = cleanupOutData(adata, params),
+            relativeDataStr = cleanupOutData(data, params);
+
+        // Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.
+        // v-20 -> V0
+        // Don't convert if it fits following previous instruction.
+        // l20 30-10-50 instead of l20 30L20 30
+        if (
+            params.forceAbsolutePath || (
+            absoluteDataStr.length < relativeDataStr.length &&
+            !(
+                params.negativeExtraSpace &&
+                instruction == prev.instruction &&
+                prev.instruction.charCodeAt(0) > 96 &&
+                absoluteDataStr.length == relativeDataStr.length - 1 &&
+                (data[0] < 0 || /^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1)
+            ))
+        ) {
+            item.instruction = instruction.toUpperCase();
+            item.data = adata;
+        }
+
+        prev = item;
+
+        return true;
+
+    });
+
+    return path;
+
+}
+
+/**
+ * Checks if curve is convex. Control points of such a curve must form
+ * a convex quadrilateral with diagonals crosspoint inside of it.
+ *
+ * @param {Array} data input path data
+ * @return {Boolean} output
+ */
+function isConvex(data) {
+
+    var center = getIntersection([0, 0, data[2], data[3], data[0], data[1], data[4], data[5]]);
+
+    return center &&
+        (data[2] < center[0] == center[0] < 0) &&
+        (data[3] < center[1] == center[1] < 0) &&
+        (data[4] < center[0] == center[0] < data[0]) &&
+        (data[5] < center[1] == center[1] < data[1]);
+
+}
+
+/**
+ * Computes lines equations by two points and returns their intersection point.
+ *
+ * @param {Array} coords 8 numbers for 4 pairs of coordinates (x,y)
+ * @return {Array|undefined} output coordinate of lines' crosspoint
+ */
+function getIntersection(coords) {
+
+        // Prev line equation parameters.
+    var a1 = coords[1] - coords[3], // y1 - y2
+        b1 = coords[2] - coords[0], // x2 - x1
+        c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1
+
+        // Next line equation parameters
+        a2 = coords[5] - coords[7], // y1 - y2
+        b2 = coords[6] - coords[4], // x2 - x1
+        c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1
+        denom = (a1 * b2 - a2 * b1);
+
+    if (!denom) return; // parallel lines havn't an intersection
+
+    var cross = [
+            (b1 * c2 - b2 * c1) / denom,
+            (a1 * c2 - a2 * c1) / -denom
+        ];
+    if (
+        !isNaN(cross[0]) && !isNaN(cross[1]) &&
+        isFinite(cross[0]) && isFinite(cross[1])
+    ) {
+        return cross;
+    }
+
+}
+
+/**
+ * Decrease accuracy of floating-point numbers
+ * in path data keeping a specified number of decimals.
+ * Smart rounds values like 2.3491 to 2.35 instead of 2.349.
+ * Doesn't apply "smartness" if the number precision fits already.
+ *
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function strongRound(data) {
+    for (var i = data.length; i-- > 0;) {
+        if (data[i].toFixed(precision) != data[i]) {
+            var rounded = +data[i].toFixed(precision - 1);
+            data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= error ?
+                +data[i].toFixed(precision) :
+                rounded;
+        }
+    }
+    return data;
+}
+
+/**
+ * Simple rounding function if precision is 0.
+ *
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function round(data) {
+    for (var i = data.length; i-- > 0;) {
+        data[i] = Math.round(data[i]);
+    }
+    return data;
+}
+
+/**
+ * Checks if a curve is a straight line by measuring distance
+ * from middle points to the line formed by end points.
+ *
+ * @param {Array} xs array of curve points x-coordinates
+ * @param {Array} ys array of curve points y-coordinates
+ * @return {Boolean}
+ */
+
+function isCurveStraightLine(data) {
+
+    // Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points.
+    var i = data.length - 2,
+        a = -data[i + 1], // y1 − y2 (y1 = 0)
+        b = data[i],      // x2 − x1 (x1 = 0)
+        d = 1 / (a * a + b * b); // same part for all points
+
+    if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case
+
+    // Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²))
+    while ((i -= 2) >= 0) {
+        if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
+            return false;
+    }
+
+    return true;
+
+}
+
+/**
+ * Converts next curve from shorthand to full form using the current curve data.
+ *
+ * @param {Object} item curve to convert
+ * @param {Array} data current curve data
+ */
+
+function makeLonghand(item, data) {
+    switch (item.instruction) {
+        case 's': item.instruction = 'c'; break;
+        case 't': item.instruction = 'q'; break;
+    }
+    item.data.unshift(data[data.length - 2] - data[data.length - 4], data[data.length - 1] - data[data.length - 3]);
+    return item;
+}
+
+/**
+ * Returns distance between two points
+ *
+ * @param {Array} point1 first point coordinates
+ * @param {Array} point2 second point coordinates
+ * @return {Number} distance
+ */
+
+function getDistance(point1, point2) {
+    return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
+}
+
+/**
+ * Returns coordinates of the curve point corresponding to the certain t
+ * a·(1 - t)³·p1 + b·(1 - t)²·t·p2 + c·(1 - t)·t²·p3 + d·t³·p4,
+ * where pN are control points and p1 is zero due to relative coordinates.
+ *
+ * @param {Array} curve array of curve points coordinates
+ * @param {Number} t parametric position from 0 to 1
+ * @return {Array} Point coordinates
+ */
+
+function getCubicBezierPoint(curve, t) {
+    var sqrT = t * t,
+        cubT = sqrT * t,
+        mt = 1 - t,
+        sqrMt = mt * mt;
+
+    return [
+        3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4],
+        3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5]
+    ];
+}
+
+/**
+ * Finds circle by 3 points of the curve and checks if the curve fits the found circle.
+ *
+ * @param {Array} curve
+ * @return {Object|undefined} circle
+ */
+
+function findCircle(curve) {
+    var midPoint = getCubicBezierPoint(curve, 1/2),
+        m1 = [midPoint[0] / 2, midPoint[1] / 2],
+        m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
+        center = getIntersection([
+            m1[0], m1[1],
+            m1[0] + m1[1], m1[1] - m1[0],
+            m2[0], m2[1],
+            m2[0] + (m2[1] - midPoint[1]), m2[1] - (m2[0] - midPoint[0])
+        ]),
+        radius = center && getDistance([0, 0], center),
+        tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100);
+
+    if (center && radius < 1e15 &&
+        [1/4, 3/4].every(function(point) {
+        return Math.abs(getDistance(getCubicBezierPoint(curve, point), center) - radius) <= tolerance;
+    }))
+        return { center: center, radius: radius};
+}
+
+/**
+ * Checks if a curve fits the given circle.
+ *
+ * @param {Object} circle
+ * @param {Array} curve
+ * @return {Boolean}
+ */
+
+function isArc(curve, circle) {
+    var tolerance = Math.min(arcThreshold * error, arcTolerance * circle.radius / 100);
+
+    return [0, 1/4, 1/2, 3/4, 1].every(function(point) {
+        return Math.abs(getDistance(getCubicBezierPoint(curve, point), circle.center) - circle.radius) <= tolerance;
+    });
+}
+
+/**
+ * Checks if a previous curve fits the given circle.
+ *
+ * @param {Object} circle
+ * @param {Array} curve
+ * @return {Boolean}
+ */
+
+function isArcPrev(curve, circle) {
+    return isArc(curve, {
+        center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
+        radius: circle.radius
+    });
+}
+
+/**
+ * Finds angle of a curve fitting the given arc.
+
+ * @param {Array} curve
+ * @param {Object} relCircle
+ * @return {Number} angle
+ */
+
+function findArcAngle(curve, relCircle) {
+    var x1 = -relCircle.center[0],
+        y1 = -relCircle.center[1],
+        x2 = curve[4] - relCircle.center[0],
+        y2 = curve[5] - relCircle.center[1];
+
+    return Math.acos(
+            (x1 * x2 + y1 * y2) /
+            Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+        );
+}
+
+/**
+ * Converts given path data to string.
+ *
+ * @param {Object} params
+ * @param {Array} pathData
+ * @return {String}
+ */
+
+function data2Path(params, pathData) {
+    return pathData.reduce(function(pathString, item) {
+        var strData = '';
+        if (item.data) {
+            strData = cleanupOutData(roundData(item.data.slice()), params);
+        }
+        return pathString + item.instruction + strData;
+    }, '');
+}
Index: frontend/node_modules/svgo/plugins/convertShapeToPath.js
===================================================================
--- frontend/node_modules/svgo/plugins/convertShapeToPath.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/convertShapeToPath.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,149 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts basic shapes to more compact path form';
+
+exports.params = {
+    convertArcs: false
+};
+
+var none = { value: 0 },
+    regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
+
+/**
+ * Converts basic shape to more compact path.
+ * It also allows further optimizations like
+ * combining paths with similar attributes.
+ *
+ * @see http://www.w3.org/TR/SVG/shapes.html
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Lev Solntsev
+ */
+exports.fn = function(item, params) {
+    var convertArcs = params && params.convertArcs;
+
+    if (
+        item.isElem('rect') &&
+        item.hasAttr('width') &&
+        item.hasAttr('height') &&
+        !item.hasAttr('rx') &&
+        !item.hasAttr('ry')
+    ) {
+
+        var x = +(item.attr('x') || none).value,
+            y = +(item.attr('y') || none).value,
+            width  = +item.attr('width').value,
+            height = +item.attr('height').value;
+
+            // Values like '100%' compute to NaN, thus running after
+            // cleanupNumericValues when 'px' units has already been removed.
+            // TODO: Calculate sizes from % and non-px units if possible.
+        if (isNaN(x - y + width - height)) return;
+
+        var pathData =
+            'M' + x + ' ' + y +
+            'H' + (x + width) +
+            'V' + (y + height) +
+            'H' + x +
+            'z';
+
+        item.addAttr({
+                name: 'd',
+                value: pathData,
+                prefix: '',
+                local: 'd'
+            });
+
+        item.renameElem('path')
+            .removeAttr(['x', 'y', 'width', 'height']);
+
+    } else if (item.isElem('line')) {
+
+        var x1 = +(item.attr('x1') || none).value,
+            y1 = +(item.attr('y1') || none).value,
+            x2 = +(item.attr('x2') || none).value,
+            y2 = +(item.attr('y2') || none).value;
+        if (isNaN(x1 - y1 + x2 - y2)) return;
+
+        item.addAttr({
+                name: 'd',
+                value: 'M' + x1 + ' ' + y1 + 'L' + x2 + ' ' + y2,
+                prefix: '',
+                local: 'd'
+            });
+
+        item.renameElem('path')
+            .removeAttr(['x1', 'y1', 'x2', 'y2']);
+
+    } else if ((
+            item.isElem('polyline') ||
+            item.isElem('polygon')
+        ) &&
+        item.hasAttr('points')
+    ) {
+
+        var coords = (item.attr('points').value.match(regNumber) || []).map(Number);
+        if (coords.length < 4) return false;
+
+        item.addAttr({
+                name: 'd',
+                value: 'M' + coords.slice(0,2).join(' ') +
+                       'L' + coords.slice(2).join(' ') +
+                       (item.isElem('polygon') ? 'z' : ''),
+                prefix: '',
+                local: 'd'
+            });
+
+        item.renameElem('path')
+            .removeAttr('points');
+    } else if (item.isElem('circle') && convertArcs) {
+
+        var cx = +(item.attr('cx') || none).value;
+        var cy = +(item.attr('cy') || none).value;
+        var r = +(item.attr('r') || none).value;
+        if (isNaN(cx - cy + r)) {
+            return;
+        }
+        var cPathData =
+            'M' + cx  + ' ' + (cy - r) +
+                'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy + r) +
+                'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy - r) +
+                'Z';
+        item.addAttr({
+                name: 'd',
+                value: cPathData,
+                prefix: '',
+                local: 'd',
+        });
+        item.renameElem('path').removeAttr(['cx', 'cy', 'r']);
+
+    } else if (item.isElem('ellipse') && convertArcs) {
+
+        var ecx = +(item.attr('cx') || none).value;
+        var ecy = +(item.attr('cy') || none).value;
+        var rx = +(item.attr('rx') || none).value;
+        var ry = +(item.attr('ry') || none).value;
+        if (isNaN(ecx - ecy + rx - ry)) {
+            return;
+        }
+        var ePathData =
+            'M' + ecx + ' ' + (ecy - ry) +
+                'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy + ry) +
+                'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy - ry) +
+                'Z';
+        item.addAttr({
+                name: 'd',
+                value: ePathData,
+                prefix: '',
+                local: 'd',
+        });
+        item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']);
+    }
+};
Index: frontend/node_modules/svgo/plugins/convertStyleToAttrs.js
===================================================================
--- frontend/node_modules/svgo/plugins/convertStyleToAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/convertStyleToAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,125 @@
+/* jshint quotmark: false */
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts style to attributes';
+
+exports.params = {
+    keepImportant: false
+};
+
+var stylingProps = require('./_collections').attrsGroups.presentation,
+    rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)',                 // Like \" or \2051. Code points consume one space.
+    rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*',          // attribute name like ‘fill’
+    rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
+    rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)',       // string in double quotes: "smth"
+    rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'),
+
+    // Parentheses, E.g.: url(data:image/png;base64,iVBO...).
+    // ':' and ';' inside of it should be threated as is. (Just like in strings.)
+    rParenthesis = '\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
+
+    // The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
+    rValue = '\\s*(' + g('[^!\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')',
+
+    // End of declaration. Spaces outside of capturing groups help to do natural trimming.
+    rDeclEnd = '\\s*(?:;\\s*|$)',
+
+    // Important rule
+    rImportant = '(\\s*!important(?![-(\w]))?',
+
+    // Final RegExp to parse CSS declarations.
+    regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rImportant + rDeclEnd, 'ig'),
+
+    // Comments expression. Honors escape sequences and strings.
+    regStripComments = new RegExp(g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), 'ig');
+
+/**
+ * Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect.
+ *
+ * @example
+ * <g style="fill:#000; color: #fff;">
+ *             ⬇
+ * <g fill="#000" color="#fff">
+ *
+ * @example
+ * <g style="fill:#000; color: #fff; -webkit-blah: blah">
+ *             ⬇
+ * <g fill="#000" color="#fff" style="-webkit-blah: blah">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+    /* jshint boss: true */
+
+    if (item.elem && item.hasAttr('style')) {
+            // ['opacity: 1', 'color: #000']
+        var styleValue = item.attr('style').value,
+            styles = [],
+            attrs = {};
+
+        // Strip CSS comments preserving escape sequences and strings.
+        styleValue = styleValue.replace(regStripComments, function(match) {
+            return match[0] == '/' ? '' :
+                match[0] == '\\' && /[-g-z]/i.test(match[1]) ? match[1] : match;
+        });
+
+        regDeclarationBlock.lastIndex = 0;
+        for (var rule; rule = regDeclarationBlock.exec(styleValue);) {
+            if (!params.keepImportant || !rule[3]) {
+                styles.push([rule[1], rule[2]]);
+            }
+        }
+
+        if (styles.length) {
+
+            styles = styles.filter(function(style) {
+                if (style[0]) {
+                    var prop = style[0].toLowerCase(),
+                        val = style[1];
+
+                    if (rQuotedString.test(val)) {
+                        val = val.slice(1, -1);
+                    }
+
+                    if (stylingProps.indexOf(prop) > -1) {
+
+                        attrs[prop] = {
+                            name: prop,
+                            value: val,
+                            local: prop,
+                            prefix: ''
+                        };
+
+                        return false;
+                    }
+                }
+
+                return true;
+            });
+
+            Object.assign(item.attrs, attrs);
+
+            if (styles.length) {
+                item.attr('style').value = styles
+                    .map(function(declaration) { return declaration.join(':') })
+                    .join(';');
+            } else {
+                item.removeAttr('style');
+            }
+
+        }
+
+    }
+
+};
+
+function g() {
+    return '(?:' + Array.prototype.join.call(arguments, '|') + ')';
+}
Index: frontend/node_modules/svgo/plugins/convertTransform.js
===================================================================
--- frontend/node_modules/svgo/plugins/convertTransform.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/convertTransform.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,363 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'collapses multiple transformations and optimizes it';
+
+exports.params = {
+    convertToShorts: true,
+    // degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default
+    floatPrecision: 3,
+    transformPrecision: 5,
+    matrixToTransform: true,
+    shortTranslate: true,
+    shortScale: true,
+    shortRotate: true,
+    removeUseless: true,
+    collapseIntoOne: true,
+    leadingZero: true,
+    negativeExtraSpace: false
+};
+
+var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
+    transform2js = require('./_transforms.js').transform2js,
+    transformsMultiply = require('./_transforms.js').transformsMultiply,
+    matrixToTransform = require('./_transforms.js').matrixToTransform,
+    degRound,
+    floatRound,
+    transformRound;
+
+/**
+ * Convert matrices to the short aliases,
+ * convert long translate, scale or rotate transform notations to the shorts ones,
+ * convert transforms to the matrices and multiply them all into one,
+ * remove useless transforms.
+ *
+ * @see http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.elem) {
+
+        // transform
+        if (item.hasAttr('transform')) {
+            convertTransform(item, 'transform', params);
+        }
+
+        // gradientTransform
+        if (item.hasAttr('gradientTransform')) {
+            convertTransform(item, 'gradientTransform', params);
+        }
+
+        // patternTransform
+        if (item.hasAttr('patternTransform')) {
+            convertTransform(item, 'patternTransform', params);
+        }
+
+    }
+
+};
+
+/**
+ * Main function.
+ *
+ * @param {Object} item input item
+ * @param {String} attrName attribute name
+ * @param {Object} params plugin params
+ */
+function convertTransform(item, attrName, params) {
+    var data = transform2js(item.attr(attrName).value);
+    params = definePrecision(data, params);
+
+    if (params.collapseIntoOne && data.length > 1) {
+        data = [transformsMultiply(data)];
+    }
+
+    if (params.convertToShorts) {
+        data = convertToShorts(data, params);
+    } else {
+        data.forEach(roundTransform);
+    }
+
+    if (params.removeUseless) {
+        data = removeUseless(data);
+    }
+
+    if (data.length) {
+        item.attr(attrName).value = js2transform(data, params);
+    } else {
+        item.removeAttr(attrName);
+    }
+}
+
+/**
+ * Defines precision to work with certain parts.
+ * transformPrecision - for scale and four first matrix parameters (needs a better precision due to multiplying),
+ * floatPrecision - for translate including two last matrix and rotate parameters,
+ * degPrecision - for rotate and skew. By default it's equal to (rougly)
+ * transformPrecision - 2 or floatPrecision whichever is lower. Can be set in params.
+ *
+ * @param {Array} transforms input array
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+function definePrecision(data, params) {
+    /* jshint validthis: true */
+    var matrixData = data.reduce(getMatrixData, []),
+        significantDigits = params.transformPrecision;
+
+    // Clone params so it don't affect other elements transformations.
+    params = Object.assign({}, params);
+
+    // Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
+    if (matrixData.length) {
+        params.transformPrecision = Math.min(params.transformPrecision,
+            Math.max.apply(Math, matrixData.map(floatDigits)) || params.transformPrecision);
+
+        significantDigits = Math.max.apply(Math, matrixData.map(function(n) {
+            return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5
+        }));
+    }
+    // No sense in angle precision more then number of significant digits in matrix.
+    if (!('degPrecision' in params)) {
+        params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2));
+    }
+
+    floatRound = params.floatPrecision >= 1 && params.floatPrecision < 20 ?
+        smartRound.bind(this, params.floatPrecision) :
+        round;
+    degRound = params.degPrecision >= 1 && params.floatPrecision < 20 ?
+        smartRound.bind(this, params.degPrecision) :
+        round;
+    transformRound = params.transformPrecision >= 1 && params.floatPrecision < 20 ?
+        smartRound.bind(this, params.transformPrecision) :
+        round;
+
+    return params;
+}
+
+/**
+ * Gathers four first matrix parameters.
+ *
+ * @param {Array} a array of data
+ * @param {Object} transform
+ * @return {Array} output array
+ */
+function getMatrixData(a, b) {
+    return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a;
+}
+
+/**
+ * Returns number of digits after the point. 0.125 → 3
+ */
+function floatDigits(n) {
+    return (n = String(n)).slice(n.indexOf('.')).length - 1;
+}
+
+/**
+ * Convert transforms to the shorthand alternatives.
+ *
+ * @param {Array} transforms input array
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+function convertToShorts(transforms, params) {
+
+    for(var i = 0; i < transforms.length; i++) {
+
+        var transform = transforms[i];
+
+        // convert matrix to the short aliases
+        if (
+            params.matrixToTransform &&
+            transform.name === 'matrix'
+        ) {
+            var decomposed = matrixToTransform(transform, params);
+            if (decomposed != transform &&
+                js2transform(decomposed, params).length <= js2transform([transform], params).length) {
+
+                transforms.splice.apply(transforms, [i, 1].concat(decomposed));
+            }
+            transform = transforms[i];
+        }
+
+        // fixed-point numbers
+        // 12.754997 → 12.755
+        roundTransform(transform);
+
+        // convert long translate transform notation to the shorts one
+        // translate(10 0) → translate(10)
+        if (
+            params.shortTranslate &&
+            transform.name === 'translate' &&
+            transform.data.length === 2 &&
+            !transform.data[1]
+        ) {
+            transform.data.pop();
+        }
+
+        // convert long scale transform notation to the shorts one
+        // scale(2 2) → scale(2)
+        if (
+            params.shortScale &&
+            transform.name === 'scale' &&
+            transform.data.length === 2 &&
+            transform.data[0] === transform.data[1]
+        ) {
+            transform.data.pop();
+        }
+
+        // convert long rotate transform notation to the short one
+        // translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy)
+        if (
+            params.shortRotate &&
+            transforms[i - 2] &&
+            transforms[i - 2].name === 'translate' &&
+            transforms[i - 1].name === 'rotate' &&
+            transforms[i].name === 'translate' &&
+            transforms[i - 2].data[0] === -transforms[i].data[0] &&
+            transforms[i - 2].data[1] === -transforms[i].data[1]
+        ) {
+            transforms.splice(i - 2, 3, {
+                name: 'rotate',
+                data: [
+                    transforms[i - 1].data[0],
+                    transforms[i - 2].data[0],
+                    transforms[i - 2].data[1]
+                ]
+            });
+
+            // splice compensation
+            i -= 2;
+
+            transform = transforms[i];
+        }
+
+    }
+
+    return transforms;
+
+}
+
+/**
+ * Remove useless transforms.
+ *
+ * @param {Array} transforms input array
+ * @return {Array} output array
+ */
+function removeUseless(transforms) {
+
+    return transforms.filter(function(transform) {
+
+        // translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)
+        if (
+            ['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
+            (transform.data.length == 1 || transform.name == 'rotate') &&
+            !transform.data[0] ||
+
+            // translate(0, 0)
+            transform.name == 'translate' &&
+            !transform.data[0] &&
+            !transform.data[1] ||
+
+            // scale(1)
+            transform.name == 'scale' &&
+            transform.data[0] == 1 &&
+            (transform.data.length < 2 || transform.data[1] == 1) ||
+
+            // matrix(1 0 0 1 0 0)
+            transform.name == 'matrix' &&
+            transform.data[0] == 1 &&
+            transform.data[3] == 1 &&
+            !(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5])
+        ) {
+            return false;
+        }
+
+        return true;
+
+    });
+
+}
+
+/**
+ * Convert transforms JS representation to string.
+ *
+ * @param {Array} transformJS JS representation array
+ * @param {Object} params plugin params
+ * @return {String} output string
+ */
+function js2transform(transformJS, params) {
+
+    var transformString = '';
+
+    // collect output value string
+    transformJS.forEach(function(transform) {
+        roundTransform(transform);
+        transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')';
+    });
+
+    return transformString;
+
+}
+
+function roundTransform(transform) {
+    switch (transform.name) {
+        case 'translate':
+            transform.data = floatRound(transform.data);
+            break;
+        case 'rotate':
+            transform.data = degRound(transform.data.slice(0, 1)).concat(floatRound(transform.data.slice(1)));
+            break;
+        case 'skewX':
+        case 'skewY':
+            transform.data = degRound(transform.data);
+            break;
+        case 'scale':
+            transform.data = transformRound(transform.data);
+            break;
+        case 'matrix':
+            transform.data = transformRound(transform.data.slice(0, 4)).concat(floatRound(transform.data.slice(4)));
+            break;
+    }
+    return transform;
+}
+
+/**
+ * Rounds numbers in array.
+ *
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function round(data) {
+    return data.map(Math.round);
+}
+
+/**
+ * Decrease accuracy of floating-point numbers
+ * in transforms keeping a specified number of decimals.
+ * Smart rounds values like 2.349 to 2.35.
+ *
+ * @param {Number} fixed number of decimals
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function smartRound(precision, data) {
+    for (var i = data.length, tolerance = +Math.pow(.1, precision).toFixed(precision); i--;) {
+        if (data[i].toFixed(precision) != data[i]) {
+            var rounded = +data[i].toFixed(precision - 1);
+            data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance ?
+                +data[i].toFixed(precision) :
+                rounded;
+        }
+    }
+    return data;
+}
Index: frontend/node_modules/svgo/plugins/inlineStyles.js
===================================================================
--- frontend/node_modules/svgo/plugins/inlineStyles.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/inlineStyles.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,245 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.params = {
+    onlyMatchedOnce: true,
+    removeMatchedSelectors: true,
+    useMqs: ['', 'screen'],
+    usePseudos: ['']
+};
+
+exports.description = 'inline styles (additional options)';
+
+
+var csstree = require('css-tree'),
+    cssTools = require('../lib/css-tools');
+
+/**
+ * Moves + merges styles from style elements to element styles
+ *
+ * Options
+ *   onlyMatchedOnce (default: true)
+ *     inline only selectors that match once
+ *
+ *   removeMatchedSelectors (default: true)
+ *     clean up matched selectors,
+ *     leave selectors that hadn't matched
+ *
+ *   useMqs (default: ['', 'screen'])
+ *     what media queries to be used
+ *     empty string element for styles outside media queries
+ *
+ *   usePseudos (default: [''])
+ *     what pseudo-classes/-elements to be used
+ *     empty string element for all non-pseudo-classes and/or -elements
+ *
+ * @param {Object} document document element
+ * @param {Object} opts plugin params
+ *
+ * @author strarsis <strarsis@gmail.com>
+ */
+exports.fn = function(document, opts) {
+
+    // collect <style/>s
+    var styleEls = document.querySelectorAll('style');
+
+    //no <styles/>s, nothing to do
+    if (styleEls === null) {
+        return document;
+    }
+
+    var styles = [],
+        selectors = [];
+
+    for (var styleEl of styleEls) {
+        if (styleEl.isEmpty() || styleEl.closestElem('foreignObject')) {
+            // skip empty <style/>s or <foreignObject> content.
+            continue;
+        }
+
+        var cssStr = cssTools.getCssStr(styleEl);
+
+        // collect <style/>s and their css ast
+        var cssAst = {};
+        try {
+            cssAst = csstree.parse(cssStr, {
+                parseValue: false,
+                parseCustomProperty: false
+            });
+        } catch (parseError) {
+            // console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
+            continue;
+        }
+
+        styles.push({
+            styleEl: styleEl,
+            cssAst: cssAst
+        });
+
+        selectors = selectors.concat(cssTools.flattenToSelectors(cssAst));
+    }
+
+
+    // filter for mediaqueries to be used or without any mediaquery
+    var selectorsMq = cssTools.filterByMqs(selectors, opts.useMqs);
+
+
+    // filter for pseudo elements to be used
+    var selectorsPseudo = cssTools.filterByPseudos(selectorsMq, opts.usePseudos);
+
+    // remove PseudoClass from its SimpleSelector for proper matching
+    cssTools.cleanPseudos(selectorsPseudo);
+
+
+    // stable sort selectors
+    var sortedSelectors = cssTools.sortSelectors(selectorsPseudo).reverse();
+
+
+    var selector,
+        selectedEl;
+
+    // match selectors
+    for (selector of sortedSelectors) {
+        var selectorStr = csstree.generate(selector.item.data),
+            selectedEls = null;
+
+        try {
+            selectedEls = document.querySelectorAll(selectorStr);
+        } catch (selectError) {
+            if (selectError.constructor === SyntaxError) {
+                // console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
+                continue;
+            }
+            throw selectError;
+        }
+
+        if (selectedEls === null) {
+            // nothing selected
+            continue;
+        }
+
+        selector.selectedEls = selectedEls;
+    }
+
+
+    // apply <style/> styles to matched elements
+    for (selector of sortedSelectors) {
+        if(!selector.selectedEls) {
+            continue;
+        }
+
+        if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
+            // skip selectors that match more than once if option onlyMatchedOnce is enabled
+            continue;
+        }
+
+        // apply <style/> to matched elements
+        for (selectedEl of selector.selectedEls) {
+            if (selector.rule === null) {
+                continue;
+            }
+
+            // merge declarations
+            csstree.walk(selector.rule, {visit: 'Declaration', enter: function(styleCsstreeDeclaration) {
+
+                // existing inline styles have higher priority
+                // no inline styles, external styles,                                    external styles used
+                // inline styles,    external styles same   priority as inline styles,   inline   styles used
+                // inline styles,    external styles higher priority than inline styles, external styles used
+                var styleDeclaration = cssTools.csstreeToStyleDeclaration(styleCsstreeDeclaration);
+                if (selectedEl.style.getPropertyValue(styleDeclaration.name) !== null &&
+                    selectedEl.style.getPropertyPriority(styleDeclaration.name) >= styleDeclaration.priority) {
+                    return;
+                }
+                selectedEl.style.setProperty(styleDeclaration.name, styleDeclaration.value, styleDeclaration.priority);
+            }});
+        }
+
+        if (opts.removeMatchedSelectors && selector.selectedEls !== null && selector.selectedEls.length > 0) {
+            // clean up matching simple selectors if option removeMatchedSelectors is enabled
+            selector.rule.prelude.children.remove(selector.item);
+        }
+    }
+
+
+    if (!opts.removeMatchedSelectors) {
+        return document; // no further processing required
+    }
+
+
+    // clean up matched class + ID attribute values
+    for (selector of sortedSelectors) {
+        if(!selector.selectedEls) {
+            continue;
+        }
+
+        if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
+            // skip selectors that match more than once if option onlyMatchedOnce is enabled
+            continue;
+        }
+
+        for (selectedEl of selector.selectedEls) {
+            // class
+            var firstSubSelector = selector.item.data.children.first();
+            if(firstSubSelector.type === 'ClassSelector') {
+                selectedEl.class.remove(firstSubSelector.name);
+            }
+            // clean up now empty class attributes
+            if(typeof selectedEl.class.item(0) === 'undefined') {
+                selectedEl.removeAttr('class');
+            }
+
+            // ID
+            if(firstSubSelector.type === 'IdSelector') {
+                selectedEl.removeAttr('id', firstSubSelector.name);
+            }
+        }
+    }
+
+
+    // clean up now empty elements
+    for (var style of styles) {
+        csstree.walk(style.cssAst, {visit: 'Rule', enter: function(node, item, list) {
+            // clean up <style/> atrules without any rulesets left
+            if (node.type === 'Atrule' &&
+                // only Atrules containing rulesets
+                node.block !== null &&
+                node.block.children.isEmpty()) {
+                list.remove(item);
+                return;
+            }
+
+            // clean up <style/> rulesets without any css selectors left
+            if (node.type === 'Rule' &&
+                node.prelude.children.isEmpty()) {
+                list.remove(item);
+            }
+        }});
+
+
+        if (style.cssAst.children.isEmpty()) {
+            // clean up now emtpy <style/>s
+            var styleParentEl = style.styleEl.parentNode;
+            styleParentEl.spliceContent(styleParentEl.content.indexOf(style.styleEl), 1);
+
+            if (styleParentEl.elem === 'defs' &&
+                styleParentEl.content.length === 0) {
+                // also clean up now empty <def/>s
+                var defsParentEl = styleParentEl.parentNode;
+                defsParentEl.spliceContent(defsParentEl.content.indexOf(styleParentEl), 1);
+            }
+
+            continue;
+        }
+
+
+        // update existing, left over <style>s
+        cssTools.setCssStr(style.styleEl, csstree.generate(style.cssAst));
+    }
+
+
+    return document;
+};
Index: frontend/node_modules/svgo/plugins/mergePaths.js
===================================================================
--- frontend/node_modules/svgo/plugins/mergePaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/mergePaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,73 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'merges multiple paths in one if possible';
+
+exports.params = {
+    collapseRepeated: true,
+    force: false,
+    leadingZero: true,
+    negativeExtraSpace: true,
+    noSpaceAfterFlags: true
+};
+
+var path2js = require('./_path.js').path2js,
+    js2path = require('./_path.js').js2path,
+    intersects = require('./_path.js').intersects;
+
+/**
+ * Merge multiple Paths into one.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich, Lev Solntsev
+ */
+exports.fn = function(item, params) {
+
+    if (!item.isElem() || item.isEmpty()) return;
+
+    var prevContentItem = null,
+        prevContentItemKeys = null;
+
+    item.content = item.content.filter(function(contentItem) {
+
+        if (prevContentItem &&
+            prevContentItem.isElem('path') &&
+            prevContentItem.isEmpty() &&
+            prevContentItem.hasAttr('d') &&
+            contentItem.isElem('path') &&
+            contentItem.isEmpty() &&
+            contentItem.hasAttr('d')
+        ) {
+
+            if (!prevContentItemKeys) {
+                prevContentItemKeys = Object.keys(prevContentItem.attrs);
+            }
+
+            var contentItemAttrs = Object.keys(contentItem.attrs),
+                equalData = prevContentItemKeys.length == contentItemAttrs.length &&
+                    contentItemAttrs.every(function(key) {
+                        return key == 'd' ||
+                            prevContentItem.hasAttr(key) &&
+                            prevContentItem.attr(key).value == contentItem.attr(key).value;
+                    }),
+                prevPathJS = path2js(prevContentItem),
+                curPathJS = path2js(contentItem);
+
+            if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {
+                js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
+                return false;
+            }
+        }
+
+        prevContentItem = contentItem;
+        prevContentItemKeys = null;
+        return true;
+
+    });
+
+};
Index: frontend/node_modules/svgo/plugins/minifyStyles.js
===================================================================
--- frontend/node_modules/svgo/plugins/minifyStyles.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/minifyStyles.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,160 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'minifies styles and removes unused styles based on usage data';
+
+exports.params = {
+    // ... CSSO options goes here
+
+    // additional 
+    usage: {
+        force: false,  // force to use usage data even if it unsafe (document contains <script> or on* attributes)
+        ids: true,
+        classes: true,
+        tags: true
+    }
+};
+
+var csso = require('csso');
+
+/**
+ * Minifies styles (<style> element + style attribute) using CSSO
+ *
+ * @author strarsis <strarsis@gmail.com>
+ */
+exports.fn = function(ast, options) {
+    options = options || {};
+
+    var minifyOptionsForStylesheet = cloneObject(options);
+    var minifyOptionsForAttribute = cloneObject(options);
+    var elems = findStyleElems(ast);
+
+    minifyOptionsForStylesheet.usage = collectUsageData(ast, options);
+    minifyOptionsForAttribute.usage = null;
+
+    elems.forEach(function(elem) {
+        if (elem.isElem('style')) {
+            // <style> element
+            var styleCss = elem.content[0].text || elem.content[0].cdata || [];
+            var DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text';
+
+            elem.content[0][DATA] = csso.minify(styleCss, minifyOptionsForStylesheet).css;
+        } else {
+            // style attribute
+            var elemStyle = elem.attr('style').value;
+
+            elem.attr('style').value = csso.minifyBlock(elemStyle, minifyOptionsForAttribute).css;
+        }
+    });
+
+    return ast;
+};
+
+function cloneObject(obj) {
+    var result = {};
+
+    for (var key in obj) {
+        result[key] = obj[key];
+    }
+
+    return result;
+}
+
+function findStyleElems(ast) {
+
+    function walk(items, styles) {
+        for (var i = 0; i < items.content.length; i++) {
+            var item = items.content[i];
+
+            // go deeper
+            if (item.content) {
+                walk(item, styles);
+            }
+
+            if (item.isElem('style') && !item.isEmpty()) {
+                styles.push(item);
+            } else if (item.isElem() && item.hasAttr('style')) {
+                styles.push(item);
+            }
+        }
+
+        return styles;
+    }
+
+    return walk(ast, []);
+}
+
+function shouldFilter(options, name) {
+    if ('usage' in options === false) {
+        return true;
+    }
+
+    if (options.usage && name in options.usage === false) {
+        return true;
+    }
+
+    return Boolean(options.usage && options.usage[name]);
+}
+
+function collectUsageData(ast, options) {
+
+    function walk(items, usageData) {
+        for (var i = 0; i < items.content.length; i++) {
+            var item = items.content[i];
+
+            // go deeper
+            if (item.content) {
+                walk(item, usageData);
+            }
+
+            if (item.isElem('script')) {
+                safe = false;
+            }
+
+            if (item.isElem()) {
+                usageData.tags[item.elem] = true;
+
+                if (item.hasAttr('id')) {
+                    usageData.ids[item.attr('id').value] = true;
+                }
+
+                if (item.hasAttr('class')) {
+                    item.attr('class').value.replace(/^\s+|\s+$/g, '').split(/\s+/).forEach(function(className) {
+                        usageData.classes[className] = true;
+                    });
+                }
+
+                if (item.attrs && Object.keys(item.attrs).some(function(name) { return /^on/i.test(name); })) {
+                    safe = false;
+                }
+            }
+        }
+
+        return usageData;
+    }
+
+    var safe = true;
+    var usageData = {};
+    var hasData = false;
+    var rawData = walk(ast, {
+        ids: Object.create(null),
+        classes: Object.create(null),
+        tags: Object.create(null)
+    });
+
+    if (!safe && options.usage && options.usage.force) {
+        safe = true;
+    }
+
+    for (var key in rawData) {
+        if (shouldFilter(options, key)) {
+            usageData[key] = Object.keys(rawData[key]);
+            hasData = true;
+        }
+    }
+
+    return safe && hasData ? usageData : null;
+}
Index: frontend/node_modules/svgo/plugins/moveElemsAttrsToGroup.js
===================================================================
--- frontend/node_modules/svgo/plugins/moveElemsAttrsToGroup.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/moveElemsAttrsToGroup.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,126 @@
+'use strict';
+
+exports.type = 'perItemReverse';
+
+exports.active = true;
+
+exports.description = 'moves elements attributes to the existing group wrapper';
+
+var inheritableAttrs = require('./_collections').inheritableAttrs,
+    pathElems = require('./_collections.js').pathElems;
+
+/**
+ * Collapse content's intersected and inheritable
+ * attributes to the existing group wrapper.
+ *
+ * @example
+ * <g attr1="val1">
+ *     <g attr2="val2">
+ *         text
+ *     </g>
+ *     <circle attr2="val2" attr3="val3"/>
+ * </g>
+ *              ⬇
+ * <g attr1="val1" attr2="val2">
+ *     <g>
+ *         text
+ *     </g>
+ *    <circle attr3="val3"/>
+ * </g>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('g') && !item.isEmpty() && item.content.length > 1) {
+
+        var intersection = {},
+            hasTransform = false,
+            hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'),
+            intersected = item.content.every(function(inner) {
+                if (inner.isElem() && inner.hasAttr()) {
+                    // don't mess with possible styles (hack until CSS parsing is implemented)
+                    if (inner.hasAttr('class')) return false;
+                    if (!Object.keys(intersection).length) {
+                        intersection = inner.attrs;
+                    } else {
+                        intersection = intersectInheritableAttrs(intersection, inner.attrs);
+
+                        if (!intersection) return false;
+                    }
+
+                    return true;
+                }
+            }),
+            allPath = item.content.every(function(inner) {
+                return inner.isElem(pathElems);
+            });
+
+        if (intersected) {
+
+            item.content.forEach(function(g) {
+
+                for (var name in intersection) {
+
+                    if (!allPath && !hasClip || name !== 'transform') {
+
+                        g.removeAttr(name);
+
+                        if (name === 'transform') {
+                            if (!hasTransform) {
+                                if (item.hasAttr('transform')) {
+                                    item.attr('transform').value += ' ' + intersection[name].value;
+                                } else {
+                                    item.addAttr(intersection[name]);
+                                }
+
+                                hasTransform = true;
+                            }
+                        } else {
+                            item.addAttr(intersection[name]);
+                        }
+
+                    }
+                }
+
+            });
+
+        }
+
+    }
+
+};
+
+/**
+ * Intersect inheritable attributes.
+ *
+ * @param {Object} a first attrs object
+ * @param {Object} b second attrs object
+ *
+ * @return {Object} intersected attrs object
+ */
+function intersectInheritableAttrs(a, b) {
+
+    var c = {};
+
+    for (var n in a) {
+        if (
+            b.hasOwnProperty(n) &&
+            inheritableAttrs.indexOf(n) > -1 &&
+            a[n].name === b[n].name &&
+            a[n].value === b[n].value &&
+            a[n].prefix === b[n].prefix &&
+            a[n].local === b[n].local
+        ) {
+            c[n] = a[n];
+        }
+    }
+
+    if (!Object.keys(c).length) return false;
+
+    return c;
+
+}
Index: frontend/node_modules/svgo/plugins/moveGroupAttrsToElems.js
===================================================================
--- frontend/node_modules/svgo/plugins/moveGroupAttrsToElems.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/moveGroupAttrsToElems.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,63 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'moves some group attributes to the content elements';
+
+var collections = require('./_collections.js'),
+    pathElems = collections.pathElems.concat(['g', 'text']),
+    referencesProps = collections.referencesProps;
+
+/**
+ * Move group attrs to the content elements.
+ *
+ * @example
+ * <g transform="scale(2)">
+ *     <path transform="rotate(45)" d="M0,0 L10,20"/>
+ *     <path transform="translate(10, 20)" d="M0,10 L20,30"/>
+ * </g>
+ *                          ⬇
+ * <g>
+ *     <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/>
+ *     <path transform="scale(2) translate(10, 20)" d="M0,10 L20,30"/>
+ * </g>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    // move group transform attr to content's pathElems
+    if (
+        item.isElem('g') &&
+        item.hasAttr('transform') &&
+        !item.isEmpty() &&
+        !item.someAttr(function(attr) {
+            return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url(');
+        }) &&
+        item.content.every(function(inner) {
+            return inner.isElem(pathElems) && !inner.hasAttr('id');
+        })
+    ) {
+        item.content.forEach(function(inner) {
+            var attr = item.attr('transform');
+            if (inner.hasAttr('transform')) {
+                inner.attr('transform').value = attr.value + ' ' + inner.attr('transform').value;
+            } else {
+                inner.addAttr({
+                    'name': attr.name,
+                    'local': attr.local,
+                    'prefix': attr.prefix,
+                    'value': attr.value
+                });
+            }
+        });
+
+        item.removeAttr('transform');
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/prefixIds.js
===================================================================
--- frontend/node_modules/svgo/plugins/prefixIds.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/prefixIds.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,243 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.params = {
+    delim: '__',
+    prefixIds: true,
+    prefixClassNames: true,
+};
+
+exports.description = 'prefix IDs';
+
+
+var path = require('path'),
+    csstree = require('css-tree'),
+    unquote = require('unquote'),
+    collections = require('./_collections.js'),
+    referencesProps = collections.referencesProps,
+    rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
+    addPrefix = null;
+
+
+// Escapes a string for being used as ID
+var escapeIdentifierName = function(str) {
+    return str.replace(/[\. ]/g, '_');
+};
+
+// Matches an #ID value, captures the ID name
+var matchId = function(urlVal) {
+    var idUrlMatches = urlVal.match(rxId);
+    if (idUrlMatches === null) {
+        return false;
+    }
+    return idUrlMatches[1];
+};
+
+// Matches an url(...) value, captures the URL
+var matchUrl = function(val) {
+    var urlMatches = /url\((.*?)\)/gi.exec(val);
+    if (urlMatches === null) {
+        return false;
+    }
+    return urlMatches[1];
+};
+
+// Checks if attribute is empty
+var attrNotEmpty = function(attr) {
+    return (attr && attr.value && attr.value.length > 0);
+};
+
+// prefixes an #ID
+var prefixId = function(val) {
+    var idName = matchId(val);
+    if (!idName) {
+        return false;
+    }
+    return '#' + addPrefix(idName);
+};
+
+
+// attr.value helper methods
+
+// prefixes a class attribute value
+var addPrefixToClassAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    attr.value = attr.value.split(/\s+/).map(addPrefix).join(' ');
+};
+
+// prefixes an ID attribute value
+var addPrefixToIdAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    attr.value = addPrefix(attr.value);
+};
+
+// prefixes a href attribute value
+var addPrefixToHrefAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    var idPrefixed = prefixId(attr.value);
+    if (!idPrefixed) {
+        return;
+    }
+    attr.value = idPrefixed;
+};
+
+// prefixes an URL attribute value
+var addPrefixToUrlAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    // url(...) in value
+    var urlVal = matchUrl(attr.value);
+    if (!urlVal) {
+        return;
+    }
+
+    var idPrefixed = prefixId(urlVal);
+    if (!idPrefixed) {
+        return;
+    }
+
+    attr.value = 'url(' + idPrefixed + ')';
+};
+
+
+/**
+ * Prefixes identifiers
+ *
+ * @param {Object} node node
+ * @param {Object} opts plugin params
+ * @param {Object} extra plugin extra information
+ *
+ * @author strarsis <strarsis@gmail.com>
+ */
+exports.fn = function(node, opts, extra) {
+
+    // skip subsequent passes when multipass is used
+    if(extra.multipassCount && extra.multipassCount > 0) {
+        return node;
+    }
+
+    // prefix, from file name or option
+    var prefix = 'prefix';
+    if (opts.prefix) {
+        if (typeof opts.prefix === 'function') {
+            prefix = opts.prefix(node, extra);
+        } else {
+            prefix = opts.prefix;
+        }
+    } else if (opts.prefix === false) {
+        prefix = false;
+    } else if (extra && extra.path && extra.path.length > 0) {
+        var filename = path.basename(extra.path);
+        prefix = filename;
+    }
+
+
+    // prefixes a normal value
+    addPrefix = function(name) {
+        if(prefix === false){
+            return escapeIdentifierName(name);
+        }
+        return escapeIdentifierName(prefix + opts.delim + name);
+    };
+
+
+    // <style/> property values
+
+    if (node.elem === 'style') {
+        if (node.isEmpty()) {
+            // skip empty <style/>s
+            return node;
+        }
+
+        var cssStr = node.content[0].text || node.content[0].cdata || [];
+
+        var cssAst = {};
+        try {
+            cssAst = csstree.parse(cssStr, {
+                parseValue: true,
+                parseCustomProperty: false
+            });
+        } catch (parseError) {
+            console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
+            return node;
+        }
+
+        var idPrefixed = '';
+        csstree.walk(cssAst, function(node) {
+
+            // #ID, .class
+            if (((opts.prefixIds        && node.type === 'IdSelector') ||
+                 (opts.prefixClassNames && node.type === 'ClassSelector')) &&
+                 node.name) {
+                node.name = addPrefix(node.name);
+                return;
+            }
+
+            // url(...) in value
+            if (node.type === 'Url' &&
+                node.value.value && node.value.value.length > 0) {
+                idPrefixed = prefixId(unquote(node.value.value));
+                if (!idPrefixed) {
+                    return;
+                }
+                node.value.value = idPrefixed;
+            }
+
+        });
+
+        // update <style>s
+        node.content[0].text = csstree.generate(cssAst);
+        return node;
+    }
+
+
+    // element attributes
+
+    if (!node.attrs) {
+        return node;
+    }
+
+
+    // Nodes
+
+    if(opts.prefixIds) {
+        // ID
+        addPrefixToIdAttr(node.attrs.id);
+    }
+
+    if(opts.prefixClassNames) {
+        // Class
+        addPrefixToClassAttr(node.attrs.class);
+    }
+
+
+    // References
+
+    // href
+    addPrefixToHrefAttr(node.attrs.href);
+
+    // (xlink:)href (deprecated, must be still supported)
+    addPrefixToHrefAttr(node.attrs['xlink:href']);
+
+    // (referenceable) properties
+    for (var referencesProp of referencesProps) {
+        addPrefixToUrlAttr(node.attrs[referencesProp]);
+    }
+
+
+    return node;
+};
Index: frontend/node_modules/svgo/plugins/removeAttributesBySelector.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeAttributesBySelector.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeAttributesBySelector.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,70 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes attributes of elements that match a css selector';
+
+
+/**
+ * Removes attributes of elements that match a css selector.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @example
+ * <caption>A selector removing a single attribute</caption>
+ * plugins:
+ *   - removeAttributesBySelector:
+ *       selector: "[fill='#00ff00']"
+ *       attributes: "fill"
+ *
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
+ *   ↓
+ * <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>     
+ *
+ * <caption>A selector removing multiple attributes</caption>
+ * plugins:
+ *   - removeAttributesBySelector:
+ *       selector: "[fill='#00ff00']"
+ *       attributes:
+ *         - fill
+ *         - stroke
+ *
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
+ *   ↓
+ * <rect x="0" y="0" width="100" height="100"/>     
+ *
+ * <caption>Multiple selectors removing attributes</caption>
+ * plugins:
+ *   - removeAttributesBySelector:
+ *       selectors:
+ *         - selector: "[fill='#00ff00']"
+ *           attributes: "fill"
+ *
+ *         - selector: "#remove"
+ *           attributes:
+ *             - stroke
+ *             - id
+ *
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
+ *   ↓
+ * <rect x="0" y="0" width="100" height="100"/>
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors}
+ *
+ * @author Bradley Mease
+ */
+exports.fn = function(item, params) {
+
+    var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
+
+    selectors.map(function(i) {
+        if (item.matches(i.selector)) {
+            item.removeAttr(i.attributes);
+        }
+    });
+
+};
Index: frontend/node_modules/svgo/plugins/removeAttrs.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,150 @@
+'use strict';
+
+var DEFAULT_SEPARATOR = ':';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes specified attributes';
+
+exports.params = {
+    elemSeparator: DEFAULT_SEPARATOR,
+    preserveCurrentColor: false,
+    attrs: []
+};
+
+/**
+ * Remove attributes
+ *
+ * @param elemSeparator
+ *   format: string
+ *
+ * @param preserveCurrentColor
+ *   format: boolean
+ *
+ * @param attrs:
+ *
+ *   format: [ element* : attribute* : value* ]
+ *
+ *   element   : regexp (wrapped into ^...$), single * or omitted > all elements (must be present when value is used)
+ *   attribute : regexp (wrapped into ^...$)
+ *   value     : regexp (wrapped into ^...$), single * or omitted > all values
+ *
+ *   examples:
+ *
+ *     > basic: remove fill attribute
+ *     ---
+ *     removeAttrs:
+ *       attrs: 'fill'
+ *
+ *     > remove fill attribute on path element
+ *     ---
+ *       attrs: 'path:fill'
+ *
+ *     > remove fill attribute on path element where value is none
+ *     ---
+ *       attrs: 'path:fill:none'
+ *
+ *
+ *     > remove all fill and stroke attribute
+ *     ---
+ *       attrs:
+ *         - 'fill'
+ *         - 'stroke'
+ *
+ *     [is same as]
+ *
+ *       attrs: '(fill|stroke)'
+ *
+ *     [is same as]
+ *
+ *       attrs: '*:(fill|stroke)'
+ *
+ *     [is same as]
+ *
+ *       attrs: '.*:(fill|stroke)'
+ *
+ *     [is same as]
+ *
+ *       attrs: '.*:(fill|stroke):.*'
+ *
+ *
+ *     > remove all stroke related attributes
+ *     ----
+ *     attrs: 'stroke.*'
+ *
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Benny Schudel
+ */
+exports.fn = function(item, params) {
+        // wrap into an array if params is not
+    if (!Array.isArray(params.attrs)) {
+        params.attrs = [params.attrs];
+    }
+
+    if (item.isElem()) {
+        var elemSeparator = typeof params.elemSeparator == 'string' ? params.elemSeparator : DEFAULT_SEPARATOR;
+        var preserveCurrentColor = typeof params.preserveCurrentColor == 'boolean' ? params.preserveCurrentColor : false;
+
+            // prepare patterns
+        var patterns = params.attrs.map(function(pattern) {
+
+                // if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
+            if (pattern.indexOf(elemSeparator) === -1) {
+                pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
+
+                // if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
+            } else if (pattern.split(elemSeparator).length < 3) {
+                pattern = [pattern, elemSeparator, '.*'].join('');
+            }
+
+                // create regexps for element, attribute name, and attribute value
+            return pattern.split(elemSeparator)
+                .map(function(value) {
+
+                        // adjust single * to match anything
+                    if (value === '*') { value = '.*'; }
+
+                    return new RegExp(['^', value, '$'].join(''), 'i');
+                });
+
+        });
+
+            // loop patterns
+        patterns.forEach(function(pattern) {
+
+                // matches element
+            if (pattern[0].test(item.elem)) {
+
+                    // loop attributes
+                item.eachAttr(function(attr) {
+                    var name = attr.name;
+                    var value = attr.value;
+                    var isFillCurrentColor = preserveCurrentColor && name == 'fill' && value == 'currentColor';
+                    var isStrokeCurrentColor = preserveCurrentColor && name == 'stroke' && value == 'currentColor';
+
+                    if (!(isFillCurrentColor || isStrokeCurrentColor)) {
+                        // matches attribute name
+                        if (pattern[1].test(name)) {
+
+                            // matches attribute value
+                            if (pattern[2].test(attr.value)) {
+                                item.removeAttr(name);
+                            }
+                        }
+                    }
+
+                });
+
+            }
+
+        });
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeComments.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeComments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeComments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,27 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes comments';
+
+/**
+ * Remove comments.
+ *
+ * @example
+ * <!-- Generator: Adobe Illustrator 15.0.0, SVG Export
+ * Plug-In . SVG Version: 6.00 Build 0)  -->
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.comment && item.comment.charAt(0) !== '!') {
+        return false;
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeDesc.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeDesc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeDesc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,32 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.params = {
+    removeAny: true
+};
+
+exports.description = 'removes <desc>';
+
+var standardDescs = /^(Created with|Created using)/;
+
+/**
+ * Removes <desc>.
+ * Removes only standard editors content or empty elements 'cause it can be used for accessibility.
+ * Enable parameter 'removeAny' to remove any description.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Daniel Wabyick
+ */
+exports.fn = function(item, params) {
+
+    return !item.isElem('desc') || !(params.removeAny || item.isEmpty() ||
+            standardDescs.test(item.content[0].text));
+
+};
Index: frontend/node_modules/svgo/plugins/removeDimensions.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeDimensions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeDimensions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,49 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';
+
+/**
+ * Remove width/height attributes and add the viewBox attribute if it's missing
+ *
+ * @example
+ * <svg width="100" height="50" />
+ *   ↓
+ * <svg viewBox="0 0 100 50" />
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if true, with and height will be filtered out
+ *
+ * @author Benny Schudel
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('svg')) {
+        if (item.hasAttr('viewBox')) {
+            item.removeAttr('width');
+            item.removeAttr('height');
+        } else if (
+            item.hasAttr('width') &&
+            item.hasAttr('height') &&
+            !isNaN(Number(item.attr('width').value)) &&
+            !isNaN(Number(item.attr('height').value))
+        ) {
+            item.addAttr({
+                name: 'viewBox',
+                value:
+                    '0 0 ' +
+                    Number(item.attr('width').value) +
+                    ' ' +
+                    Number(item.attr('height').value),
+                prefix: '',
+                local: 'viewBox'
+            });
+            item.removeAttr('width');
+            item.removeAttr('height');
+        }
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeDoctype.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeDoctype.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeDoctype.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,40 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes doctype declaration';
+
+/**
+ * Remove DOCTYPE declaration.
+ *
+ * "Unfortunately the SVG DTDs are a source of so many
+ * issues that the SVG WG has decided not to write one
+ * for the upcoming SVG 1.2 standard. In fact SVG WG
+ * members are even telling people not to use a DOCTYPE
+ * declaration in SVG 1.0 and 1.1 documents"
+ * https://jwatt.org/svg/authoring/#doctype-declaration
+ *
+ * @example
+ * <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ * q"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+ *
+ * @example
+ * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
+ *     <!-- an internal subset can be embedded here -->
+ * ]>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.doctype) {
+        return false;
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeEditorsNSData.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeEditorsNSData.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeEditorsNSData.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,65 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes editors namespaces, elements and attributes';
+
+var editorNamespaces = require('./_collections').editorNamespaces,
+    prefixes = [];
+
+exports.params = {
+    additionalNamespaces: []
+};
+
+/**
+ * Remove editors namespaces, elements and attributes.
+ *
+ * @example
+ * <svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd">
+ * <sodipodi:namedview/>
+ * <path sodipodi:nodetypes="cccc"/>
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (Array.isArray(params.additionalNamespaces)) {
+        editorNamespaces = editorNamespaces.concat(params.additionalNamespaces);
+    }
+
+    if (item.elem) {
+
+        if (item.isElem('svg')) {
+
+            item.eachAttr(function(attr) {
+                if (attr.prefix === 'xmlns' && editorNamespaces.indexOf(attr.value) > -1) {
+                    prefixes.push(attr.local);
+
+                    // <svg xmlns:sodipodi="">
+                    item.removeAttr(attr.name);
+                }
+            });
+
+        }
+
+        // <* sodipodi:*="">
+        item.eachAttr(function(attr) {
+            if (prefixes.indexOf(attr.prefix) > -1) {
+                item.removeAttr(attr.name);
+            }
+        });
+
+        // <sodipodi:*>
+        if (prefixes.indexOf(item.prefix) > -1) {
+            return false;
+        }
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeElementsByAttr.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeElementsByAttr.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeElementsByAttr.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,80 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes arbitrary elements by ID or className (disabled by default)';
+
+exports.params = {
+    id: [],
+    class: []
+};
+
+/**
+ * Remove arbitrary SVG elements by ID or className.
+ *
+ * @param id
+ *   examples:
+ *
+ *     > single: remove element with ID of `elementID`
+ *     ---
+ *     removeElementsByAttr:
+ *       id: 'elementID'
+ *
+ *     > list: remove multiple elements by ID
+ *     ---
+ *     removeElementsByAttr:
+ *       id:
+ *         - 'elementID'
+ *         - 'anotherID'
+ *
+ * @param class
+ *   examples:
+ *
+ *     > single: remove all elements with class of `elementClass`
+ *     ---
+ *     removeElementsByAttr:
+ *       class: 'elementClass'
+ *
+ *     > list: remove all elements with class of `elementClass` or `anotherClass`
+ *     ---
+ *     removeElementsByAttr:
+ *       class:
+ *         - 'elementClass'
+ *         - 'anotherClass'
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Eli Dupuis (@elidupuis)
+ */
+exports.fn = function(item, params) {
+    var elemId, elemClass;
+
+    // wrap params in an array if not already
+    ['id', 'class'].forEach(function(key) {
+        if (!Array.isArray(params[key])) {
+            params[key] = [ params[key] ];
+        }
+    });
+
+    // abort if current item is no an element
+    if (!item.isElem()) {
+        return;
+    }
+
+    // remove element if it's `id` matches configured `id` params
+    elemId = item.attr('id');
+    if (elemId) {
+        return params.id.indexOf(elemId.value) === -1;
+    }
+
+    // remove element if it's `class` contains any of the configured `class` params
+    elemClass = item.attr('class');
+    if (elemClass) {
+        var hasClassRegex = new RegExp(params.class.join('|'));
+        return !hasClassRegex.test(elemClass.value);
+    }
+};
Index: frontend/node_modules/svgo/plugins/removeEmptyAttrs.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeEmptyAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeEmptyAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,29 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes empty attributes';
+
+/**
+ * Remove attributes with empty values.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.elem) {
+
+        item.eachAttr(function(attr) {
+            if (attr.value === '') {
+                item.removeAttr(attr.name);
+            }
+        });
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeEmptyContainers.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeEmptyContainers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeEmptyContainers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,32 @@
+'use strict';
+
+exports.type = 'perItemReverse';
+
+exports.active = true;
+
+exports.description = 'removes empty container elements';
+
+var container = require('./_collections').elemsGroups.container;
+
+/**
+ * Remove empty containers.
+ *
+ * @see http://www.w3.org/TR/SVG/intro.html#TermContainerElement
+ *
+ * @example
+ * <defs/>
+ *
+ * @example
+ * <g><marker><a/></marker></g>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    return !(item.isElem(container) && !item.isElem('svg') && item.isEmpty() &&
+        (!item.isElem('pattern') || !item.hasAttrLocal('href')));
+
+};
Index: frontend/node_modules/svgo/plugins/removeEmptyText.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeEmptyText.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeEmptyText.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,59 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes empty <text> elements';
+
+exports.params = {
+    text: true,
+    tspan: true,
+    tref: true
+};
+
+/**
+ * Remove empty Text elements.
+ *
+ * @see http://www.w3.org/TR/SVG/text.html
+ *
+ * @example
+ * Remove empty text element:
+ * <text/>
+ *
+ * Remove empty tspan element:
+ * <tspan/>
+ *
+ * Remove tref with empty xlink:href attribute:
+ * <tref xlink:href=""/>
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    // Remove empty text element
+    if (
+        params.text &&
+        item.isElem('text') &&
+        item.isEmpty()
+    ) return false;
+
+    // Remove empty tspan element
+    if (
+        params.tspan &&
+        item.isElem('tspan') &&
+        item.isEmpty()
+    ) return false;
+
+    // Remove tref with empty xlink:href attribute
+    if (
+        params.tref &&
+        item.isElem('tref') &&
+        !item.hasAttrLocal('href')
+    ) return false;
+
+};
Index: frontend/node_modules/svgo/plugins/removeHiddenElems.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeHiddenElems.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeHiddenElems.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,225 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes hidden elements (zero sized, with absent attributes)';
+
+exports.params = {
+    isHidden: true,
+    displayNone: true,
+    opacity0: true,
+    circleR0: true,
+    ellipseRX0: true,
+    ellipseRY0: true,
+    rectWidth0: true,
+    rectHeight0: true,
+    patternWidth0: true,
+    patternHeight0: true,
+    imageWidth0: true,
+    imageHeight0: true,
+    pathEmptyD: true,
+    polylineEmptyPoints: true,
+    polygonEmptyPoints: true
+};
+
+var regValidPath = /M\s*(?:[-+]?(?:\d*\.\d+|\d+(?:\.|(?!\.)))([eE][-+]?\d+)?(?!\d)\s*,?\s*){2}\D*\d/i;
+
+/**
+ * Remove hidden elements with disabled rendering:
+ * - display="none"
+ * - opacity="0"
+ * - circle with zero radius
+ * - ellipse with zero x-axis or y-axis radius
+ * - rectangle with zero width or height
+ * - pattern with zero width or height
+ * - image with zero width or height
+ * - path with empty data
+ * - polyline with empty points
+ * - polygon with empty points
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function (item, params) {
+
+    if (item.elem) {
+        // Removes hidden elements
+        // https://www.w3schools.com/cssref/pr_class_visibility.asp
+        if (
+            params.isHidden &&
+            item.hasAttr('visibility', 'hidden')
+        ) return false;
+
+        // display="none"
+        //
+        // http://www.w3.org/TR/SVG/painting.html#DisplayProperty
+        // "A value of display: none indicates that the given element
+        // and its children shall not be rendered directly"
+        if (
+            params.displayNone &&
+            item.hasAttr('display', 'none')
+        ) return false;
+
+        // opacity="0"
+        //
+        // http://www.w3.org/TR/SVG/masking.html#ObjectAndGroupOpacityProperties
+        if (
+            params.opacity0 &&
+            item.hasAttr('opacity', '0')
+        ) return false;
+
+        // Circles with zero radius
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#CircleElementRAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <circle r="0">
+        if (
+            params.circleR0 &&
+            item.isElem('circle') &&
+            item.isEmpty() &&
+            item.hasAttr('r', '0')
+        ) return false;
+
+        // Ellipse with zero x-axis radius
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#EllipseElementRXAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <ellipse rx="0">
+        if (
+            params.ellipseRX0 &&
+            item.isElem('ellipse') &&
+            item.isEmpty() &&
+            item.hasAttr('rx', '0')
+        ) return false;
+
+        // Ellipse with zero y-axis radius
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#EllipseElementRYAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <ellipse ry="0">
+        if (
+            params.ellipseRY0 &&
+            item.isElem('ellipse') &&
+            item.isEmpty() &&
+            item.hasAttr('ry', '0')
+        ) return false;
+
+        // Rectangle with zero width
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#RectElementWidthAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <rect width="0">
+        if (
+            params.rectWidth0 &&
+            item.isElem('rect') &&
+            item.isEmpty() &&
+            item.hasAttr('width', '0')
+        ) return false;
+
+        // Rectangle with zero height
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#RectElementHeightAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <rect height="0">
+        if (
+            params.rectHeight0 &&
+            params.rectWidth0 &&
+            item.isElem('rect') &&
+            item.isEmpty() &&
+            item.hasAttr('height', '0')
+        ) return false;
+
+        // Pattern with zero width
+        //
+        // http://www.w3.org/TR/SVG/pservers.html#PatternElementWidthAttribute
+        // "A value of zero disables rendering of the element (i.e., no paint is applied)"
+        //
+        // <pattern width="0">
+        if (
+            params.patternWidth0 &&
+            item.isElem('pattern') &&
+            item.hasAttr('width', '0')
+        ) return false;
+
+        // Pattern with zero height
+        //
+        // http://www.w3.org/TR/SVG/pservers.html#PatternElementHeightAttribute
+        // "A value of zero disables rendering of the element (i.e., no paint is applied)"
+        //
+        // <pattern height="0">
+        if (
+            params.patternHeight0 &&
+            item.isElem('pattern') &&
+            item.hasAttr('height', '0')
+        ) return false;
+
+        // Image with zero width
+        //
+        // http://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <image width="0">
+        if (
+            params.imageWidth0 &&
+            item.isElem('image') &&
+            item.hasAttr('width', '0')
+        ) return false;
+
+        // Image with zero height
+        //
+        // http://www.w3.org/TR/SVG/struct.html#ImageElementHeightAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <image height="0">
+        if (
+            params.imageHeight0 &&
+            item.isElem('image') &&
+            item.hasAttr('height', '0')
+        ) return false;
+
+        // Path with empty data
+        //
+        // http://www.w3.org/TR/SVG/paths.html#DAttribute
+        //
+        // <path d=""/>
+        if (
+            params.pathEmptyD &&
+            item.isElem('path') &&
+            (!item.hasAttr('d') || !regValidPath.test(item.attr('d').value))
+        ) return false;
+
+        // Polyline with empty points
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#PolylineElementPointsAttribute
+        //
+        // <polyline points="">
+        if (
+            params.polylineEmptyPoints &&
+            item.isElem('polyline') &&
+            !item.hasAttr('points')
+        ) return false;
+
+        // Polygon with empty points
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#PolygonElementPointsAttribute
+        //
+        // <polygon points="">
+        if (
+            params.polygonEmptyPoints &&
+            item.isElem('polygon') &&
+            !item.hasAttr('points')
+        ) return false;
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeMetadata.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeMetadata.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeMetadata.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes <metadata>';
+
+/**
+ * Remove <metadata>.
+ *
+ * http://www.w3.org/TR/SVG/metadata.html
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('metadata');
+
+};
Index: frontend/node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,37 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes non-inheritable group’s presentational attributes';
+
+var inheritableAttrs = require('./_collections').inheritableAttrs,
+    attrsGroups = require('./_collections').attrsGroups,
+    applyGroups = require('./_collections').presentationNonInheritableGroupAttrs;
+
+/**
+ * Remove non-inheritable group's "presentation" attributes.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('g')) {
+
+        item.eachAttr(function(attr) {
+            if (
+                ~attrsGroups.presentation.indexOf(attr.name) &&
+                !~inheritableAttrs.indexOf(attr.name) &&
+                !~applyGroups.indexOf(attr.name)
+            ) {
+                item.removeAttr(attr.name);
+            }
+        });
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeOffCanvasPaths.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeOffCanvasPaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeOffCanvasPaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,133 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes elements that are drawn outside of the viewbox (disabled by default)';
+
+var SVGO       = require('../lib/svgo.js'),
+	_path      = require('./_path.js'),
+	intersects = _path.intersects,
+	path2js    = _path.path2js,
+	viewBox,
+	viewBoxJS;
+
+/**
+ * Remove elements that are drawn outside of the viewbox.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author JoshyPHP
+ */
+exports.fn = function(item) {
+
+	if (item.isElem('path') && item.hasAttr('d') && typeof viewBox !== 'undefined')
+	{
+		// Consider that any item with a transform attribute or a M instruction
+		// within the viewBox is visible
+		if (hasTransform(item) || pathMovesWithinViewBox(item.attr('d').value))
+		{
+			return true;
+		}
+
+		var pathJS = path2js(item);
+		if (pathJS.length === 2)
+		{
+			// Use a closed clone of the path if it's too short for intersects()
+			pathJS = JSON.parse(JSON.stringify(pathJS));
+			pathJS.push({ instruction: 'z' });
+		}
+
+		return intersects(viewBoxJS, pathJS);
+	}
+	if (item.isElem('svg'))
+	{
+		parseViewBox(item);
+	}
+
+	return true;
+};
+
+/**
+ * Test whether given item or any of its ancestors has a transform attribute.
+ *
+ * @param {String} path
+ * @return {Boolean}
+ */
+function hasTransform(item)
+{
+	return item.hasAttr('transform') || (item.parentNode && hasTransform(item.parentNode));
+}
+
+/**
+ * Parse the viewBox coordinates and compute the JS representation of its path.
+ *
+ * @param {Object} svg svg element item
+ */
+function parseViewBox(svg)
+{
+	var viewBoxData = '';
+	if (svg.hasAttr('viewBox'))
+	{
+		// Remove commas and plus signs, normalize and trim whitespace
+		viewBoxData = svg.attr('viewBox').value;
+	}
+	else if (svg.hasAttr('height') && svg.hasAttr('width'))
+	{
+		viewBoxData = '0 0 ' + svg.attr('width').value + ' ' + svg.attr('height').value;
+	}
+
+	// Remove commas and plus signs, normalize and trim whitespace
+	viewBoxData = viewBoxData.replace(/[,+]|px/g, ' ').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, '');
+
+	// Ensure that the dimensions are 4 values separated by space
+	var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(viewBoxData);
+	if (!m)
+	{
+		return;
+	}
+
+	// Store the viewBox boundaries
+	viewBox = {
+		left:   parseFloat(m[1]),
+		top:    parseFloat(m[2]),
+		right:  parseFloat(m[1]) + parseFloat(m[3]),
+		bottom: parseFloat(m[2]) + parseFloat(m[4])
+	};
+
+	var path = new SVGO().createContentItem({
+		elem:   'path',
+		prefix: '',
+		local:  'path'
+	});
+	path.addAttr({
+		name:   'd',
+		prefix: '',
+		local:  'd',
+		value:  'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z'
+	});
+
+	viewBoxJS = path2js(path);
+}
+
+/**
+ * Test whether given path has a M instruction with coordinates within the viewBox.
+ *
+ * @param {String} path
+ * @return {Boolean}
+ */
+function pathMovesWithinViewBox(path)
+{
+	var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, m;
+	while (null !== (m = regexp.exec(path)))
+	{
+		if (m[1] >= viewBox.left && m[1] <= viewBox.right && m[2] >= viewBox.top && m[2] <= viewBox.bottom)
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
Index: frontend/node_modules/svgo/plugins/removeRasterImages.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeRasterImages.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeRasterImages.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,28 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes raster images (disabled by default)';
+
+/**
+ * Remove raster images references in <image>.
+ *
+ * @see https://bugs.webkit.org/show_bug.cgi?id=63548
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (
+        item.isElem('image') &&
+        item.hasAttrLocal('href', /(\.|image\/)(jpg|png|gif)/)
+    ) {
+        return false;
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeScriptElement.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeScriptElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeScriptElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes <script> elements (disabled by default)';
+
+/**
+ * Remove <script>.
+ *
+ * https://www.w3.org/TR/SVG/script.html
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Patrick Klingemann
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('script');
+
+};
Index: frontend/node_modules/svgo/plugins/removeStyleElement.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeStyleElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeStyleElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes <style> element (disabled by default)';
+
+/**
+ * Remove <style>.
+ *
+ * http://www.w3.org/TR/SVG/styling.html#StyleElement
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Betsy Dupuis
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('style');
+
+};
Index: frontend/node_modules/svgo/plugins/removeTitle.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeTitle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeTitle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes <title>';
+
+/**
+ * Remove <title>.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Igor Kalashnikov
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('title');
+
+};
Index: frontend/node_modules/svgo/plugins/removeUnknownsAndDefaults.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeUnknownsAndDefaults.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeUnknownsAndDefaults.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,150 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes unknown elements content and attributes, removes attrs with default values';
+
+exports.params = {
+    unknownContent: true,
+    unknownAttrs: true,
+    defaultAttrs: true,
+    uselessOverrides: true,
+    keepDataAttrs: true,
+    keepAriaAttrs: true,
+    keepRoleAttr: false
+};
+
+var collections = require('./_collections'),
+    elems = collections.elems,
+    attrsGroups = collections.attrsGroups,
+    elemsGroups = collections.elemsGroups,
+    attrsGroupsDefaults = collections.attrsGroupsDefaults,
+    attrsInheritable = collections.inheritableAttrs,
+    applyGroups = collections.presentationNonInheritableGroupAttrs;
+
+// collect and extend all references
+for (var elem in elems) {
+    elem = elems[elem];
+
+    if (elem.attrsGroups) {
+        elem.attrs = elem.attrs || [];
+
+        elem.attrsGroups.forEach(function(attrsGroupName) {
+            elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]);
+
+            var groupDefaults = attrsGroupsDefaults[attrsGroupName];
+
+            if (groupDefaults) {
+                elem.defaults = elem.defaults || {};
+
+                for (var attrName in groupDefaults) {
+                    elem.defaults[attrName] = groupDefaults[attrName];
+                }
+            }
+        });
+
+    }
+
+    if (elem.contentGroups) {
+        elem.content = elem.content || [];
+
+        elem.contentGroups.forEach(function(contentGroupName) {
+            elem.content = elem.content.concat(elemsGroups[contentGroupName]);
+        });
+    }
+}
+
+/**
+ * Remove unknown elements content and attributes,
+ * remove attributes with default values.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    // elems w/o namespace prefix
+    if (item.isElem() && !item.prefix) {
+
+        var elem = item.elem;
+
+        // remove unknown element's content
+        if (
+            params.unknownContent &&
+            !item.isEmpty() &&
+            elems[elem] && // make sure we know of this element before checking its children
+            elem !== 'foreignObject' // Don't check foreignObject
+        ) {
+            item.content.forEach(function(content, i) {
+                if (
+                    content.isElem() &&
+                    !content.prefix &&
+                    (
+                        (
+                            elems[elem].content && // Do we have a record of its permitted content?
+                            elems[elem].content.indexOf(content.elem) === -1
+                        ) ||
+                        (
+                            !elems[elem].content && // we dont know about its permitted content
+                            !elems[content.elem] // check that we know about the element at all
+                        )
+                    )
+                ) {
+                    item.content.splice(i, 1);
+                }
+            });
+        }
+
+        // remove element's unknown attrs and attrs with default values
+        if (elems[elem] && elems[elem].attrs) {
+
+            item.eachAttr(function(attr) {
+
+                if (
+                    attr.name !== 'xmlns' &&
+                    (attr.prefix === 'xml' || !attr.prefix) &&
+                    (!params.keepDataAttrs || attr.name.indexOf('data-') != 0) &&
+                    (!params.keepAriaAttrs || attr.name.indexOf('aria-') != 0) &&
+                    (!params.keepRoleAttr || attr.name != 'role')
+                ) {
+                    if (
+                        // unknown attrs
+                        (
+                            params.unknownAttrs &&
+                            elems[elem].attrs.indexOf(attr.name) === -1
+                        ) ||
+                        // attrs with default values
+                        (
+                            params.defaultAttrs &&
+                            !item.hasAttr('id') &&
+                            elems[elem].defaults &&
+                            elems[elem].defaults[attr.name] === attr.value && (
+                                attrsInheritable.indexOf(attr.name) < 0 ||
+                                !item.parentNode.computedAttr(attr.name)
+                            )
+                        ) ||
+                        // useless overrides
+                        (
+                            params.uselessOverrides &&
+                            !item.hasAttr('id') &&
+                            applyGroups.indexOf(attr.name) < 0 &&
+                            attrsInheritable.indexOf(attr.name) > -1 &&
+                            item.parentNode.computedAttr(attr.name, attr.value)
+                        )
+                    ) {
+                        item.removeAttr(attr.name);
+                    }
+                }
+
+            });
+
+        }
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeUnusedNS.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeUnusedNS.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeUnusedNS.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,109 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'removes unused namespaces declaration';
+
+/**
+ * Remove unused namespaces declaration.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(data) {
+
+    var svgElem,
+        xmlnsCollection = [];
+
+    /**
+     * Remove namespace from collection.
+     *
+     * @param {String} ns namescape name
+     */
+    function removeNSfromCollection(ns) {
+
+        var pos = xmlnsCollection.indexOf(ns);
+
+        // if found - remove ns from the namespaces collection
+        if (pos > -1) {
+            xmlnsCollection.splice(pos, 1);
+        }
+
+    }
+
+    /**
+     * Bananas!
+     *
+     * @param {Array} items input items
+     *
+     * @return {Array} output items
+     */
+    function monkeys(items) {
+
+        var i = 0,
+            length = items.content.length;
+
+        while(i < length) {
+
+            var item = items.content[i];
+
+            if (item.isElem('svg')) {
+
+                item.eachAttr(function(attr) {
+                    // collect namespaces
+                    if (attr.prefix === 'xmlns' && attr.local) {
+                        xmlnsCollection.push(attr.local);
+                    }
+                });
+
+                // if svg element has ns-attr
+                if (xmlnsCollection.length) {
+                    // save svg element
+                    svgElem = item;
+                }
+
+            }
+
+            if (xmlnsCollection.length) {
+
+                // check item for the ns-attrs
+                if (item.prefix) {
+                    removeNSfromCollection(item.prefix);
+                }
+
+                // check each attr for the ns-attrs
+                item.eachAttr(function(attr) {
+                    removeNSfromCollection(attr.prefix);
+                });
+
+            }
+
+            // if nothing is found - go deeper
+            if (xmlnsCollection.length && item.content) {
+                monkeys(item);
+            }
+
+            i++;
+
+        }
+
+        return items;
+
+    }
+
+    data = monkeys(data);
+
+    // remove svg element ns-attributes if they are not used even once
+    if (xmlnsCollection.length) {
+        xmlnsCollection.forEach(function(name) {
+            svgElem.removeAttr('xmlns:' + name);
+        });
+    }
+
+    return data;
+
+};
Index: frontend/node_modules/svgo/plugins/removeUselessDefs.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeUselessDefs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeUselessDefs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,53 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes elements in <defs> without id';
+
+var nonRendering = require('./_collections').elemsGroups.nonRendering;
+
+/**
+ * Removes content of defs and properties that aren't rendered directly without ids.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Lev Solntsev
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('defs')) {
+
+        if (item.content) {
+            item.content = getUsefulItems(item, []);
+        }
+        
+        if (item.isEmpty()) return false;
+
+    } else if (item.isElem(nonRendering) && !item.hasAttr('id')) {
+
+        return false;
+
+    }
+
+};
+
+function getUsefulItems(item, usefulItems) {
+
+    item.content.forEach(function(child) {
+        if (child.hasAttr('id') || child.isElem('style')) {
+
+            usefulItems.push(child);
+            child.parentNode = item;
+
+        } else if (!child.isEmpty()) {
+
+            child.content = getUsefulItems(child, usefulItems);
+
+        }
+    });
+
+    return usefulItems;
+}
Index: frontend/node_modules/svgo/plugins/removeUselessStrokeAndFill.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeUselessStrokeAndFill.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeUselessStrokeAndFill.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,100 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes useless stroke and fill attributes';
+
+exports.params = {
+    stroke: true,
+    fill: true,
+    removeNone: false,
+    hasStyleOrScript: false
+};
+
+var shape = require('./_collections').elemsGroups.shape,
+    regStrokeProps = /^stroke/,
+    regFillProps = /^fill-/,
+    styleOrScript = ['style', 'script'];
+
+/**
+ * Remove useless stroke and fill attrs.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+    
+    if (item.isElem(styleOrScript)) {
+        params.hasStyleOrScript = true;
+    }
+
+    if (!params.hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) {
+
+        var stroke = params.stroke && item.computedAttr('stroke'),
+            fill = params.fill && !item.computedAttr('fill', 'none');
+
+        // remove stroke*
+        if (
+            params.stroke &&
+            (!stroke ||
+                stroke == 'none' ||
+                item.computedAttr('stroke-opacity', '0') ||
+                item.computedAttr('stroke-width', '0')
+            )
+        ) {
+            var parentStroke = item.parentNode.computedAttr('stroke'),
+                declineStroke = parentStroke && parentStroke != 'none';
+
+            item.eachAttr(function(attr) {
+                if (regStrokeProps.test(attr.name)) {
+                    item.removeAttr(attr.name);
+                }
+            });
+
+            if (declineStroke) item.addAttr({
+                name: 'stroke',
+                value: 'none',
+                prefix: '',
+                local: 'stroke'
+            });
+        }
+
+        // remove fill*
+        if (
+            params.fill &&
+            (!fill || item.computedAttr('fill-opacity', '0'))
+        ) {
+            item.eachAttr(function(attr) {
+                if (regFillProps.test(attr.name)) {
+                    item.removeAttr(attr.name);
+                }
+            });
+
+            if (fill) {
+                if (item.hasAttr('fill'))
+                    item.attr('fill').value = 'none';
+                else
+                    item.addAttr({
+                        name: 'fill',
+                        value: 'none',
+                        prefix: '',
+                        local: 'fill'
+                    });
+            }
+        }
+
+        if (params.removeNone && 
+            (!stroke || item.hasAttr('stroke') && item.attr('stroke').value=='none') &&
+            (!fill || item.hasAttr('fill') && item.attr('fill').value=='none')) {
+
+            return false;
+        }
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeViewBox.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeViewBox.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeViewBox.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,48 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes viewBox attribute when possible';
+
+var viewBoxElems = ['svg', 'pattern', 'symbol'];
+
+/**
+ * Remove viewBox attr which coincides with a width/height box.
+ *
+ * @see http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
+ *
+ * @example
+ * <svg width="100" height="50" viewBox="0 0 100 50">
+ *             ⬇
+ * <svg width="100" height="50">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (
+        item.isElem(viewBoxElems) &&
+        item.hasAttr('viewBox') &&
+        item.hasAttr('width') &&
+        item.hasAttr('height')
+    ) {
+
+        var nums = item.attr('viewBox').value.split(/[ ,]+/g);
+
+        if (
+            nums[0] === '0' &&
+            nums[1] === '0' &&
+            item.attr('width').value.replace(/px$/, '') === nums[2] && // could use parseFloat too
+            item.attr('height').value.replace(/px$/, '') === nums[3]
+        ) {
+            item.removeAttr('viewBox');
+        }
+
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeXMLNS.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeXMLNS.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeXMLNS.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,28 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes xmlns attribute (for inline svg, disabled by default)';
+
+/**
+ * Remove the xmlns attribute when present.
+ *
+ * @example
+ * <svg viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
+ *   ↓
+ * <svg viewBox="0 0 100 50">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if true, xmlns will be filtered out
+ *
+ * @author Ricardo Tomasi
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('svg') && item.hasAttr('xmlns')) {
+        item.removeAttr('xmlns');
+    }
+
+};
Index: frontend/node_modules/svgo/plugins/removeXMLProcInst.js
===================================================================
--- frontend/node_modules/svgo/plugins/removeXMLProcInst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/removeXMLProcInst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,24 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes XML processing instructions';
+
+/**
+ * Remove XML Processing Instruction.
+ *
+ * @example
+ * <?xml version="1.0" encoding="utf-8"?>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    return !(item.processinginstruction && item.processinginstruction.name === 'xml');
+
+};
Index: frontend/node_modules/svgo/plugins/reusePaths.js
===================================================================
--- frontend/node_modules/svgo/plugins/reusePaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/reusePaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,168 @@
+/**
+ * @license
+ * The MIT License
+ *
+ * Copyright © 2012–2016 Kir Belevich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ *
+ * Лицензия MIT
+ *
+ * Copyright © 2012–2016 Кир Белевич
+ *
+ * Данная лицензия разрешает лицам, получившим копию
+ * данного
+ * программного обеспечения и сопутствующей
+ * документации
+ * (в дальнейшем именуемыми «Программное Обеспечение»),
+ * безвозмездно
+ * использовать Программное Обеспечение без
+ * ограничений, включая
+ * неограниченное право на использование, копирование,
+ * изменение,
+ * добавление, публикацию, распространение,
+ * сублицензирование
+ * и/или продажу копий Программного Обеспечения, также
+ * как и лицам,
+ * которым предоставляется данное Программное
+ * Обеспечение,
+ * при соблюдении следующих условий:
+ *
+ * Указанное выше уведомление об авторском праве и
+ * данные условия
+ * должны быть включены во все копии или значимые части
+ * данного
+ * Программного Обеспечения.
+ *
+ * ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК
+ * ЕСТЬ»,
+ * БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ
+ * ПОДРАЗУМЕВАЕМЫХ,
+ * ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ
+ * ПРИГОДНОСТИ,
+ * СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
+ * ОТСУТСТВИЯ НАРУШЕНИЙ
+ * ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
+ * НЕСУТ
+ * ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ
+ * ИЛИ ДРУГИХ
+ * ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ
+ * ИНОМУ,
+ * ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С
+ * ПРОГРАММНЫМ
+ * ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО
+ * ОБЕСПЕЧЕНИЯ
+ * ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
+ */
+
+'use strict';
+
+var JSAPI = require('../lib/svgo/jsAPI');
+
+exports.type = 'full';
+
+exports.active = false;
+
+exports.description = 'Finds <path> elements with the same d, fill, and ' +
+                      'stroke, and converts them to <use> elements ' +
+                      'referencing a single <path> def.';
+
+/**
+ * Finds <path> elements with the same d, fill, and stroke, and converts them to
+ * <use> elements referencing a single <path> def.
+ *
+ * @author Jacob Howcroft
+ */
+exports.fn = function(data) {
+  const seen = new Map();
+  let count = 0;
+  const defs = [];
+  traverse(data, item => {
+    if (!item.isElem('path') || !item.hasAttr('d')) {
+      return;
+    }
+    const d = item.attr('d').value;
+    const fill = (item.hasAttr('fill') && item.attr('fill').value) || '';
+    const stroke = (item.hasAttr('stroke') && item.attr('stroke').value) || '';
+    const key = d + ';s:' + stroke + ';f:' + fill;
+    const hasSeen = seen.get(key);
+    if (!hasSeen) {
+      seen.set(key, {elem: item, reused: false});
+      return;
+    }
+    if (!hasSeen.reused) {
+      hasSeen.reused = true;
+      if (!hasSeen.elem.hasAttr('id')) {
+        hasSeen.elem.addAttr({name: 'id', local: 'id',
+                              prefix: '', value: 'reuse-' + (count++)});
+      }
+      defs.push(hasSeen.elem);
+    }
+    item = convertToUse(item, hasSeen.elem.attr('id').value);
+  });
+  const defsTag = new JSAPI({
+    elem: 'defs', prefix: '', local: 'defs', content: [], attrs: []}, data);
+  data.content[0].spliceContent(0, 0, defsTag);
+  for (let def of defs) {
+    // Remove class and style before copying to avoid circular refs in
+    // JSON.stringify. This is fine because we don't actually want class or
+    // style information to be copied.
+    const style = def.style;
+    const defClass = def.class;
+    delete def.style;
+    delete def.class;
+    const defClone = def.clone();
+    def.style = style;
+    def.class = defClass;
+    defClone.removeAttr('transform');
+    defsTag.spliceContent(0, 0, defClone);
+    // Convert the original def to a use so the first usage isn't duplicated.
+    def = convertToUse(def, defClone.attr('id').value);
+    def.removeAttr('id');
+  }
+  return data;
+};
+
+/** */
+function convertToUse(item, href) {
+  item.renameElem('use');
+  item.removeAttr('d');
+  item.removeAttr('stroke');
+  item.removeAttr('fill');
+  item.addAttr({name: 'xlink:href', local: 'xlink:href',
+                prefix: 'none', value: '#' + href});
+  delete item.pathJS;
+  return item;
+}
+
+/** */
+function traverse(parent, callback) {
+  if (parent.isEmpty()) {
+    return;
+  }
+  for (let child of parent.content) {
+    callback(child);
+    traverse(child, callback);
+  }
+}
Index: frontend/node_modules/svgo/plugins/sortAttrs.js
===================================================================
--- frontend/node_modules/svgo/plugins/sortAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/sortAttrs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,84 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'sorts element attributes (disabled by default)';
+
+exports.params = {
+	order: [
+		'id',
+		'width', 'height',
+		'x', 'x1', 'x2',
+		'y', 'y1', 'y2',
+		'cx', 'cy', 'r',
+		'fill', 'stroke', 'marker',
+		'd', 'points'
+	]
+};
+
+/**
+ * Sort element attributes for epic readability.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ *
+ * @author Nikolay Frantsev
+ */
+exports.fn = function(item, params) {
+
+	var attrs = [],
+		sorted = {},
+		orderlen = params.order.length + 1,
+		xmlnsOrder = params.xmlnsOrder || 'front';
+
+	if (item.elem) {
+
+		item.eachAttr(function(attr) {
+			attrs.push(attr);
+		});
+
+		attrs.sort(function(a, b) {
+			if (a.prefix != b.prefix) {
+				// xmlns attributes implicitly have the prefix xmlns
+				if (xmlnsOrder == 'front') {
+                    if (a.prefix == 'xmlns')
+                        return -1;
+                    if (b.prefix == 'xmlns')
+                        return 1;
+                }
+				return a.prefix < b.prefix ? -1 : 1;
+			}
+
+			var aindex = orderlen;
+			var bindex = orderlen;
+
+			for (var i = 0; i < params.order.length; i++) {
+				if (a.name == params.order[i]) {
+					aindex = i;
+				} else if (a.name.indexOf(params.order[i] + '-') === 0) {
+					aindex = i + .5;
+				}
+				if (b.name == params.order[i]) {
+					bindex = i;
+				} else if (b.name.indexOf(params.order[i] + '-') === 0) {
+					bindex = i + .5;
+				}
+			}
+
+			if (aindex != bindex) {
+				return aindex - bindex;
+			}
+			return a.name < b.name ? -1 : 1;
+		});
+
+		attrs.forEach(function (attr) {
+			sorted[attr.name] = attr;
+		});
+
+		item.attrs = sorted;
+
+	}
+
+};
Index: frontend/node_modules/svgo/plugins/sortDefsChildren.js
===================================================================
--- frontend/node_modules/svgo/plugins/sortDefsChildren.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/svgo/plugins/sortDefsChildren.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'Sorts children of <defs> to improve compression';
+
+/**
+ * Sorts children of defs in order to improve compression.
+ * Sorted first by frequency then by element name length then by element name (to ensure grouping).
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author David Leston
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('defs')) {
+
+        if (item.content) {
+            var frequency = item.content.reduce(function (frequency, child) {
+                if (child.elem in frequency) {
+                    frequency[child.elem]++;
+                } else {
+                    frequency[child.elem] = 1;
+                }
+                return frequency;
+            }, {});
+            item.content.sort(function (a, b) {
+                var frequencyComparison = frequency[b.elem] - frequency[a.elem];
+                if (frequencyComparison !== 0 ) {
+                    return frequencyComparison;
+                }
+                var lengthComparison = b.elem.length - a.elem.length;
+                if (lengthComparison !== 0) {
+                    return lengthComparison;
+                }
+                return a.elem != b.elem ? a.elem > b.elem ? -1 : 1 : 0;
+            });
+        }
+
+        return true;
+    }
+
+};
