Gartic Phone — Image Draw Bot v8

Pixel-perfect image drawing on Gartic Phone — samples at true canvas resolution for maximum detail

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Gartic Phone — Image Draw Bot v8
// @namespace    http://tampermonkey.net/
// @license MIT
// @version      8.0
// @description  Pixel-perfect image drawing on Gartic Phone — samples at true canvas resolution for maximum detail
// @author       GarticImageBot
// @match        https://garticphone.com/*
// @grant        GM_xmlhttpRequest
// @connect      *
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    // ═══════════════════════════════════════════
    //  CONFIG
    // ═══════════════════════════════════════════
    const CFG = {
        // Resolution scale: 1.0 = exact canvas pixels, 0.5 = half res, 2.0 = 2x supersampled
        // Values above 1.0 give more detail by averaging multiple source pixels per canvas pixel
        resolutionScale:  1.0,

        // How much adjacent pixels can differ in RGB sum before starting a new stroke
        // Lower = more accurate color, more strokes. Higher = fewer strokes, slightly blurred colors
        colorTolerance:   12,

        // Skip pixels where R, G, and B are all above this value (near-white background)
        whiteThreshold:   242,
        skipWhite:        true,

        // Strokes drawn per yielded animation frame — higher = faster but may freeze briefly
        chunkSize:        500,

        proxyUrl: 'https://corsproxy.io/?',
    };

    let drawing    = false;
    let cancelFlag = false;
    const sleep    = ms => new Promise(r => setTimeout(r, ms));

    function setStatus(msg, color) {
        const el = document.getElementById('gib-status');
        if (el) { el.textContent = msg; if (color) el.style.color = color; }
    }
    function setProgress(pct) {
        const bar = document.getElementById('gib-progress-bar');
        const lbl = document.getElementById('gib-progress-label');
        if (bar) bar.style.width  = Math.min(100, Math.round(pct)) + '%';
        if (lbl) lbl.textContent  = Math.min(100, Math.round(pct)) + '%';
    }

    // ═══════════════════════════════════════════
    //  CANVAS — grab the largest one (the drawing canvas)
    // ═══════════════════════════════════════════
    function getGameCanvas() {
        const all = [...document.querySelectorAll('canvas')];
        if (!all.length) return null;
        return all.reduce((a, b) => (a.width * a.height >= b.width * b.height ? a : b));
    }

    // ═══════════════════════════════════════════
    //  IMAGE LOADER  (direct → CORS proxy → GM blob)
    // ═══════════════════════════════════════════
    function loadImage(url) {
        return new Promise((resolve, reject) => {
            const attempt = (src, fallback) => {
                const img = new Image();
                img.crossOrigin = 'anonymous';
                img.onload  = () => resolve(img);
                img.onerror = fallback;
                img.src = src;
            };
            attempt(url, () =>
                attempt(CFG.proxyUrl + encodeURIComponent(url), () => {
                    try {
                        GM_xmlhttpRequest({
                            method: 'GET', url, responseType: 'blob',
                            onload: r => {
                                const bu = URL.createObjectURL(r.response);
                                const im = new Image();
                                im.onload  = () => { URL.revokeObjectURL(bu); resolve(im); };
                                im.onerror = () => reject(new Error('All load methods failed'));
                                im.src = bu;
                            },
                            onerror: () => reject(new Error('GM request failed')),
                        });
                    } catch(e) { reject(new Error('Load failed: ' + e.message)); }
                })
            );
        });
    }

    // ═══════════════════════════════════════════
    //  CORE: PIXEL-PERFECT STROKE BUILDER
    //
    //  Strategy:
    //  1. Scale source image to EXACTLY the game canvas dimensions
    //     (optionally supersampled at 2x then downscaled for better color accuracy)
    //  2. Read every single pixel row
    //  3. Run-length encode each row into strokes where adjacent pixels
    //     have similar enough color to merge
    //  4. Each stroke is 1px tall — lineWidth = 1 fills every pixel perfectly
    // ═══════════════════════════════════════════
    function buildStrokes(img, canvasW, canvasH) {
        const scale = CFG.resolutionScale;

        // For supersampled modes, we render at Nx then read back at 1x
        // This effectively averages Nx pixels into each canvas pixel = better color
        const supersample = scale > 1.0 ? Math.ceil(scale) : 1;

        // Analysis canvas dimensions = exact game canvas size * supersample
        const aw = Math.round(canvasW * supersample);
        const ah = Math.round(canvasH * supersample);

        // Render source image onto analysis canvas
        const ac  = document.createElement('canvas');
        ac.width  = aw;
        ac.height = ah;
        const ax  = ac.getContext('2d');

        // White background so transparent PNGs don't get weird colors
        ax.fillStyle = '#ffffff';
        ax.fillRect(0, 0, aw, ah);
        ax.imageSmoothingEnabled  = true;
        ax.imageSmoothingQuality  = 'high';
        ax.drawImage(img, 0, 0, aw, ah);

        // If supersampling, downscale back to canvas size using a second canvas
        // This averages groups of pixels giving smoother, more accurate colors
        let finalW = aw, finalH = ah;
        let finalData;

        if (supersample > 1) {
            const bc  = document.createElement('canvas');
            bc.width  = canvasW;
            bc.height = canvasH;
            const bx  = bc.getContext('2d');
            bx.fillStyle = '#ffffff';
            bx.fillRect(0, 0, canvasW, canvasH);
            bx.imageSmoothingEnabled = true;
            bx.imageSmoothingQuality = 'high';
            bx.drawImage(ac, 0, 0, aw, ah, 0, 0, canvasW, canvasH);
            finalData = bx.getImageData(0, 0, canvasW, canvasH).data;
            finalW    = canvasW;
            finalH    = canvasH;
        } else {
            finalData = ax.getImageData(0, 0, aw, ah).data;
            finalW    = aw;
            finalH    = ah;
        }

        const px  = finalData;
        const tol = CFG.colorTolerance;
        const wt  = CFG.whiteThreshold;
        const sw  = CFG.skipWhite;

        // strokes: { x1, x2, y, r, g, b }
        // x1/x2/y are in CANVAS pixel coordinates (integers)
        const strokes = [];

        for (let y = 0; y < finalH; y++) {
            let runX  = -1;
            let rr = 0, rg = 0, rb = 0;   // current run's averaged color
            let count = 0;                  // pixels accumulated in this run

            for (let x = 0; x <= finalW; x++) {
                let r = 255, g = 255, b = 255;
                let isWhite = true;

                if (x < finalW) {
                    const i = (y * finalW + x) * 4;
                    r = px[i]; g = px[i + 1]; b = px[i + 2];
                    // alpha-blend with white background just in case
                    const a = px[i + 3] / 255;
                    if (a < 1) { r = Math.round(r * a + 255 * (1-a)); g = Math.round(g * a + 255 * (1-a)); b = Math.round(b * a + 255 * (1-a)); }
                    isWhite = sw && (r >= wt && g >= wt && b >= wt);
                }

                // Color drift from running average
                const drift = (runX >= 0 && !isWhite)
                    ? (Math.abs(r - rr) + Math.abs(g - rg) + Math.abs(b - rb))
                    : 0;

                const endRun = (x === finalW) || isWhite || drift > tol * 3;

                if (endRun && runX >= 0) {
                    // Emit stroke — coordinates map 1:1 to canvas pixels
                    strokes.push({
                        x1: runX,
                        x2: x,         // exclusive end — lineTo(x2, y) draws up to x2
                        y:  y + 0.5,   // centre of the pixel row
                        r:  Math.round(rr),
                        g:  Math.round(rg),
                        b:  Math.round(rb),
                    });
                    runX = -1; count = 0;
                }

                if (!isWhite && (runX < 0 || endRun)) {
                    // Start a new run
                    runX = x; rr = r; rg = g; rb = b; count = 1;
                } else if (!isWhite && runX >= 0) {
                    // Accumulate running average (weighted towards new pixel)
                    rr = (rr * count + r) / (count + 1);
                    rg = (rg * count + g) / (count + 1);
                    rb = (rb * count + b) / (count + 1);
                    count++;
                }
            }
        }

        return strokes;
    }

    // ═══════════════════════════════════════════
    //  DRAW — direct 2D context
    //  lineWidth = 1 → each stroke is exactly 1 canvas pixel tall
    //  This means: sampleRows = canvasHeight → zero gaps, zero overlap
    // ═══════════════════════════════════════════
    async function drawOnCanvas(canvas, strokes, useColor) {
        const ctx = canvas.getContext('2d');
        if (!ctx) throw new Error('Cannot get 2D context');

        ctx.save();
        ctx.lineCap   = 'butt';    // butt = no rounded overrun at stroke ends
        ctx.lineJoin  = 'miter';
        ctx.lineWidth = 1;         // 1 canvas pixel = perfect fill at full resolution

        let lastColor = null;
        const chunk   = CFG.chunkSize;

        ctx.beginPath();

        for (let i = 0; i < strokes.length; i++) {
            if (cancelFlag) break;

            const s   = strokes[i];
            const col = useColor
                ? `rgb(${s.r},${s.g},${s.b})`
                : 'rgb(17,17,17)';

            if (col !== lastColor) {
                if (lastColor !== null) {
                    ctx.strokeStyle = lastColor;
                    ctx.stroke();
                }
                ctx.beginPath();
                lastColor = col;
            }

            ctx.moveTo(s.x1, s.y);
            ctx.lineTo(s.x2, s.y);

            // Yield every `chunk` strokes so the tab stays responsive
            if (i % chunk === chunk - 1) {
                ctx.strokeStyle = lastColor;
                ctx.stroke();
                ctx.beginPath();
                setProgress((i / strokes.length) * 100);
                await sleep(0);
            }
        }

        // Final flush
        if (lastColor) {
            ctx.strokeStyle = lastColor;
            ctx.stroke();
        }
        ctx.restore();
    }

    // ═══════════════════════════════════════════
    //  MAIN
    // ═══════════════════════════════════════════
    async function runDraw(imageUrl) {
        if (drawing) return;
        drawing = true; cancelFlag = false;
        setProgress(0);

        setStatus('Loading image…', '#fbbf24');
        let img;
        try { img = await loadImage(imageUrl); }
        catch (e) { setStatus('❌ ' + e.message, '#f87171'); drawing = false; return; }

        const canvas = getGameCanvas();
        if (!canvas) {
            setStatus('❌ No canvas — join a drawing round first!', '#f87171');
            drawing = false; return;
        }
        console.log('[GarticBot] canvas:', canvas.width, 'x', canvas.height);

        setStatus('Sampling pixels…', '#fbbf24');
        await sleep(20);

        let strokes;
        try { strokes = buildStrokes(img, canvas.width, canvas.height); }
        catch (e) { setStatus('❌ ' + e.message, '#f87171'); drawing = false; return; }

        console.log('[GarticBot] strokes:', strokes.length.toLocaleString());

        const useColor = document.getElementById('gib-usecolor')?.checked ?? true;

        setStatus(`Drawing ${strokes.length.toLocaleString()} strokes…`, '#4ade80');

        try { await drawOnCanvas(canvas, strokes, useColor); }
        catch (e) { setStatus('❌ ' + e.message, '#f87171'); drawing = false; return; }

        setProgress(100);
        setStatus(
            cancelFlag ? '⏹ Cancelled' : '✅ Done! Click DONE in Gartic.',
            cancelFlag ? '#f87171' : '#4ade80'
        );
        drawing = false;
    }

    // ═══════════════════════════════════════════
    //  PREVIEW
    // ═══════════════════════════════════════════
    async function renderPreview(url) {
        const box   = document.getElementById('gib-preview');
        const label = document.getElementById('gib-preview-label');
        if (!box) return;
        label.textContent = 'Loading…';
        box.style.backgroundImage = 'none';
        try {
            const img = await loadImage(url);
            const pc  = document.createElement('canvas');
            const sc  = Math.min(180 / img.naturalWidth, 86 / img.naturalHeight, 1);
            pc.width  = Math.round(img.naturalWidth  * sc);
            pc.height = Math.round(img.naturalHeight * sc);
            pc.getContext('2d').drawImage(img, 0, 0, pc.width, pc.height);
            box.style.backgroundImage    = `url(${pc.toDataURL()})`;
            box.style.backgroundSize     = 'contain';
            box.style.backgroundRepeat   = 'no-repeat';
            box.style.backgroundPosition = 'center';
            label.textContent = `${img.naturalWidth} × ${img.naturalHeight}`;
        } catch (e) {
            label.textContent = '⚠ Preview failed';
        }
    }

    // ═══════════════════════════════════════════
    //  PANEL
    // ═══════════════════════════════════════════
    function buildPanel() {
        if (document.getElementById('gib-panel')) return;

        const panel = document.createElement('div');
        panel.id = 'gib-panel';
        panel.style.cssText = `
            position:fixed;top:16px;right:16px;z-index:2147483647;
            width:275px;background:#09090f;
            border:1px solid #2a2a50;border-radius:14px;
            font-family:'Segoe UI',system-ui,sans-serif;font-size:13px;
            color:#c4c4e8;box-shadow:0 8px 40px rgba(0,0,0,.85);
            overflow:hidden;user-select:none;
        `;

        panel.innerHTML = `
        <div id="gib-header" style="background:#120e28;padding:11px 14px 9px;cursor:move;
            border-bottom:1px solid #1e1e40;display:flex;align-items:center;gap:8px;">
            <span style="font-size:17px">🖼️</span>
            <div>
                <div style="font-weight:700;font-size:14px;color:#a78bfa;letter-spacing:.3px">Image Draw Bot</div>
                <div style="font-size:10px;color:#3a3a60;margin-top:1px">v8 — pixel-perfect mode</div>
            </div>
            <button id="gib-min" style="margin-left:auto;background:transparent;border:1px solid #2a2a50;
                border-radius:5px;color:#5050a0;font-size:11px;cursor:pointer;padding:2px 7px;line-height:1.4">_</button>
        </div>

        <div id="gib-body" style="padding:13px 14px;">

            <!-- URL row -->
            <div style="font-size:10px;color:#3a3a60;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px">Image URL</div>
            <div style="display:flex;gap:6px;margin-bottom:9px">
                <input id="gib-url" type="text" placeholder="https://…/image.png"
                    style="flex:1;min-width:0;background:#0f0f1e;border:1px solid #2a2a50;border-radius:7px;
                    color:#c4c4e8;font-size:11px;padding:6px 8px;outline:none;">
                <button id="gib-prev-btn" title="Preview"
                    style="background:#1a1040;border:1px solid #3a2a70;border-radius:7px;
                    color:#a78bfa;font-size:14px;cursor:pointer;padding:4px 9px;">👁</button>
            </div>

            <!-- Preview box -->
            <div id="gib-preview" style="width:100%;height:88px;background:#0a0a1a;border:1px solid #1a1a38;
                border-radius:8px;margin-bottom:11px;display:flex;align-items:center;justify-content:center;">
                <span id="gib-preview-label" style="font-size:11px;color:#2a2a50">No preview</span>
            </div>

            <!-- Mode selector -->
            <div style="margin-bottom:11px">
                <div style="font-size:10px;color:#3a3a60;text-transform:uppercase;letter-spacing:.5px;margin-bottom:5px">Resolution</div>
                <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px">

                    <button class="gib-m" data-mode="normal"
                        style="padding:6px 0;background:#0f0f1e;border:1px solid #2a2a50;border-radius:7px;
                        color:#6060a0;font-size:10px;cursor:pointer;line-height:1.4;">
                        1× Normal<br>
                        <span style="font-size:8px;color:#3a3a60">Canvas pixels</span>
                    </button>

                    <button class="gib-m" data-mode="super2"
                        style="padding:6px 0;background:#1a1040;border:1px solid #7c3aed;border-radius:7px;
                        color:#a78bfa;font-size:10px;cursor:pointer;line-height:1.4;">
                        2× Super<br>
                        <span style="font-size:8px;color:#7060a0">Avg 4px→1px</span>
                    </button>

                    <button class="gib-m" data-mode="super4"
                        style="padding:6px 0;background:#0f0f1e;border:1px solid #2a2a50;border-radius:7px;
                        color:#6060a0;font-size:10px;cursor:pointer;line-height:1.4;">
                        4× Ultra<br>
                        <span style="font-size:8px;color:#3a3a60">Avg 16px→1px</span>
                    </button>

                </div>
                <div id="gib-mode-info" style="font-size:10px;color:#4a4a70;margin-top:5px;text-align:center;min-height:13px">
                    Samples image at 2× then averages down — better color accuracy
                </div>
            </div>

            <!-- Options row -->
            <div style="display:flex;align-items:center;gap:14px;margin-bottom:10px;font-size:12px">
                <label style="display:flex;align-items:center;gap:5px;cursor:pointer">
                    <input type="checkbox" id="gib-usecolor" checked> Colors
                </label>
                <label style="display:flex;align-items:center;gap:5px;cursor:pointer">
                    <input type="checkbox" id="gib-skip-white" checked> Skip white bg
                </label>
            </div>

            <!-- Color tolerance -->
            <div style="margin-bottom:10px">
                <div style="display:flex;justify-content:space-between;font-size:11px;color:#4a4a70;margin-bottom:3px">
                    <span>Color accuracy</span>
                    <span id="gib-tol-lbl">High</span>
                </div>
                <input type="range" id="gib-tol" min="1" max="5" value="3" style="width:100%">
                <div style="font-size:9px;color:#3a3a60;margin-top:2px">Higher = fewer strokes but less accurate colors</div>
            </div>

            <!-- Draw speed -->
            <div style="margin-bottom:11px">
                <div style="display:flex;justify-content:space-between;font-size:11px;color:#4a4a70;margin-bottom:3px">
                    <span>Draw speed</span><span id="gib-speed-lbl">Fast</span>
                </div>
                <input type="range" id="gib-speed" min="1" max="5" value="2" style="width:100%">
            </div>

            <!-- Status + progress -->
            <div style="margin-bottom:10px">
                <div id="gib-status" style="font-size:12px;color:#94a3b8;min-height:16px;margin-bottom:5px">
                    Ready — paste a URL and press ▶
                </div>
                <div style="background:#0f0f1e;border-radius:4px;height:6px;overflow:hidden;border:1px solid #1e1e40">
                    <div id="gib-progress-bar" style="height:100%;width:0%;
                        background:linear-gradient(90deg,#6d28d9,#a78bfa);
                        border-radius:4px;transition:width .15s"></div>
                </div>
                <div id="gib-progress-label" style="font-size:10px;color:#3a3a60;text-align:right;margin-top:2px">0%</div>
            </div>

            <button id="gib-start"
                style="width:100%;padding:9px;background:#4c1d95;color:#ddd6fe;border:1px solid #7c3aed;
                border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;margin-bottom:6px;">
                ▶ Start Drawing
            </button>
            <button id="gib-stop" disabled
                style="width:100%;padding:7px;background:#1c0a0a;color:#f87171;border:1px solid #7f1d1d;
                border-radius:8px;cursor:pointer;font-size:12px;">
                ⏹ Cancel
            </button>

            <div style="font-size:10px;color:#2a2a40;text-align:center;margin-top:8px">
                Click DONE in Gartic after drawing finishes
            </div>
        </div>`;

        document.body.appendChild(panel);

        // ── Resolution modes ──
        const MODES = {
            normal: {
                resolutionScale: 1.0,
                colorTolerance:  12,
                info: 'Draws 1 pixel per canvas pixel — maximum sharpness',
            },
            super2: {
                resolutionScale: 2.0,
                colorTolerance:  10,
                info: 'Samples 4 source pixels per canvas pixel — smoother colors',
            },
            super4: {
                resolutionScale: 4.0,
                colorTolerance:  8,
                info: 'Samples 16 source pixels per canvas pixel — best for photos',
            },
        };

        function selectMode(m) {
            const { resolutionScale, colorTolerance, info } = MODES[m];
            CFG.resolutionScale = resolutionScale;
            CFG.colorTolerance  = colorTolerance;
            document.getElementById('gib-mode-info').textContent = info;
            document.querySelectorAll('.gib-m').forEach(b => {
                const on = b.dataset.mode === m;
                b.style.background  = on ? '#1a1040' : '#0f0f1e';
                b.style.borderColor = on ? '#7c3aed' : '#2a2a50';
                b.style.color       = on ? '#a78bfa' : '#6060a0';
                b.querySelector('span').style.color = on ? '#7060a0' : '#3a3a60';
            });
        }
        document.querySelectorAll('.gib-m').forEach(b => b.addEventListener('click', () => selectMode(b.dataset.mode)));
        selectMode('super2');

        // ── Skip white ──
        document.getElementById('gib-skip-white').addEventListener('change', e => {
            CFG.skipWhite = e.target.checked;
        });

        // ── Color tolerance slider ──
        const TOL_LABELS = [, 'Max', 'High', 'Medium', 'Low', 'Min'];
        const TOL_VALUES = [, 4,     12,     22,       35,    55  ];
        document.getElementById('gib-tol').addEventListener('input', e => {
            const v = +e.target.value;
            document.getElementById('gib-tol-lbl').textContent = TOL_LABELS[v];
            CFG.colorTolerance = TOL_VALUES[v];
        });
        // default to High (v=2)
        document.getElementById('gib-tol').value = 2;
        document.getElementById('gib-tol-lbl').textContent = 'High';
        CFG.colorTolerance = 12;

        // ── Draw speed ──
        const SL = [, 'Fastest', 'Fast', 'Normal', 'Slow', 'Slowest'];
        const SC = [, 1000, 500, 150, 40, 8];
        document.getElementById('gib-speed').addEventListener('input', e => {
            const v = +e.target.value;
            document.getElementById('gib-speed-lbl').textContent = SL[v];
            CFG.chunkSize = SC[v];
        });

        // ── Preview ──
        document.getElementById('gib-prev-btn').addEventListener('click', () => {
            const u = document.getElementById('gib-url').value.trim();
            if (u) renderPreview(u);
            else setStatus('Enter a URL first', '#f87171');
        });
        document.getElementById('gib-url').addEventListener('keydown', e => {
            if (e.key === 'Enter') document.getElementById('gib-prev-btn').click();
        });

        // ── Start ──
        document.getElementById('gib-start').addEventListener('click', async () => {
            const url = document.getElementById('gib-url').value.trim();
            if (!url) { setStatus('❌ Paste an image URL first', '#f87171'); return; }
            if (drawing) return;
            document.getElementById('gib-start').disabled = true;
            document.getElementById('gib-stop').disabled  = false;
            await runDraw(url);
            document.getElementById('gib-start').disabled = false;
            document.getElementById('gib-stop').disabled  = true;
        });

        // ── Cancel ──
        document.getElementById('gib-stop').addEventListener('click', () => {
            cancelFlag = true;
            document.getElementById('gib-stop').disabled  = true;
            document.getElementById('gib-start').disabled = false;
        });

        // ── Minimize ──
        let min = false;
        document.getElementById('gib-min').addEventListener('click', () => {
            min = !min;
            document.getElementById('gib-body').style.display = min ? 'none' : 'block';
            document.getElementById('gib-min').textContent    = min ? '□' : '_';
        });

        // ── Drag ──
        let drag=false, ox,oy,sr,st;
        document.getElementById('gib-header').addEventListener('mousedown', e => {
            drag=true; ox=e.clientX; oy=e.clientY;
            const r = panel.getBoundingClientRect();
            sr = window.innerWidth - r.right; st = r.top;
        });
        document.addEventListener('mousemove', e => {
            if (!drag) return;
            panel.style.right = Math.max(0, sr-(e.clientX-ox)) + 'px';
            panel.style.top   = Math.max(0, st+(e.clientY-oy)) + 'px';
            panel.style.left  = 'auto';
        });
        document.addEventListener('mouseup', () => { drag=false; });
    }

    // ═══════════════════════════════════════════
    //  INIT
    // ═══════════════════════════════════════════
    const init = () => { console.log('[GarticBot] v8 loaded'); buildPanel(); };
    document.readyState === 'loading'
        ? document.addEventListener('DOMContentLoaded', init)
        : setTimeout(init, 600);

})();