403Webshell
Server IP : 167.235.67.158  /  Your IP : 216.73.216.95
Web Server : Apache
System : Linux ubuntu-8gb-nbg1-1 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64
User : upstairsbar.co.uk ( 982)
PHP Version : 8.3.6
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/bc.the-word.com/oliver/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/bc.the-word.com/oliver/ray-numbers.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>Ray Numbers</title>
    <style>
        :root {
            color-scheme: dark;
            --panel: rgba(17, 24, 39, 0.86);
            --panel-strong: rgba(13, 18, 29, 0.94);
            --line: rgba(255, 255, 255, 0.16);
            --text: #f8fafc;
            --muted: #a8b3c7;
            --accent: #5eead4;
            --remaining: #22c55e;
            --removed: #ef4444;
            font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        }

        * {
            box-sizing: border-box;
        }

        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0;
            overflow: hidden;
            background: #080c13;
        }

        body {
            color: var(--text);
            user-select: none;
        }

        #board {
            position: fixed;
            inset: 0;
            width: 100vw;
            height: 100vh;
            cursor: grab;
            touch-action: none;
            background: #080c13;
        }

        #board.is-dragging {
            cursor: grabbing;
        }

        .panel {
            width: min(320px, calc(100vw - 32px));
            display: flex;
            flex-direction: column;
            overflow: hidden;
            border: 1px solid var(--line);
            background: var(--panel);
            backdrop-filter: blur(16px);
            box-shadow: 0 18px 48px rgba(0, 0, 0, 0.28);
        }

        .panels {
            position: fixed;
            top: 16px;
            left: 16px;
            display: flex;
            flex-direction: column;
            gap: 12px;
            width: min(320px, calc(100vw - 32px));
            max-height: calc(100vh - 32px);
            overflow-y: auto;
            scrollbar-width: thin;
        }

        .panel__head {
            width: 100%;
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto;
            gap: 12px;
            align-items: end;
            padding: 14px 14px 12px;
            border: 0;
            border-bottom: 1px solid var(--line);
            color: var(--text);
            background: var(--panel-strong);
            font: inherit;
            text-align: left;
            cursor: pointer;
        }

        .panel__head:hover,
        .panel__head:focus-visible {
            background: rgba(24, 34, 52, 0.96);
            outline: none;
        }

        .panel__head-text {
            min-width: 0;
        }

        .panel__toggle {
            color: var(--muted);
            font-size: 12px;
            font-weight: 800;
            line-height: 1;
            padding-bottom: 2px;
        }

        .panel__toggle::after {
            content: "▼";
            color: var(--accent);
        }

        .panel.is-collapsed .panel__head {
            border-bottom: 0;
        }

        .panel.is-collapsed .panel__toggle::after {
            content: "▶";
        }

        .panel.is-collapsed .panel__body {
            display: none;
        }

        .panel__eyebrow {
            display: block;
            margin: 0 0 4px;
            color: var(--accent);
            font-size: 12px;
            font-weight: 800;
            letter-spacing: 0.08em;
            text-transform: uppercase;
        }

        .panel__title {
            display: block;
            margin: 0;
            font-size: 22px;
            font-weight: 850;
            line-height: 1.15;
        }

        .panel__stats {
            padding: 14px 16px;
            display: flex;
            flex-direction: column;
            gap: 10px;
        }

        .stat {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
        }

        .stat__label {
            display: flex;
            align-items: center;
            gap: 8px;
            color: var(--muted);
            font-size: 13px;
            font-weight: 740;
        }

        .stat__dot {
            width: 12px;
            height: 12px;
            border-radius: 3px;
        }

        .stat__dot--remaining {
            background: var(--remaining);
        }

        .stat__dot--removed {
            background: var(--removed);
        }

        .stat__value {
            font-size: 17px;
            font-weight: 850;
            font-variant-numeric: tabular-nums;
        }

        .panel__action {
            padding: 0 16px 16px;
            display: flex;
            flex-direction: column;
            gap: 10px;
        }

        .remove-button {
            width: 100%;
            min-height: 50px;
            display: grid;
            place-items: center;
            border: 1px solid rgba(239, 68, 68, 0.55);
            border-radius: 10px;
            color: #fff;
            background: linear-gradient(180deg, #f05252, #dc2626);
            font: inherit;
            font-size: 17px;
            font-weight: 850;
            cursor: pointer;
            transition: transform 0.06s ease, filter 0.15s ease;
        }

        .remove-button:hover,
        .remove-button:focus-visible {
            filter: brightness(1.08);
            outline: none;
        }

        .remove-button:active {
            transform: translateY(1px);
        }

        .remove-button:disabled {
            cursor: not-allowed;
            border-color: var(--line);
            color: var(--muted);
            background: rgba(255, 255, 255, 0.08);
            filter: none;
            transform: none;
        }

        .reset-button {
            width: 100%;
            min-height: 44px;
            display: grid;
            place-items: center;
            border: 1px solid var(--line);
            border-radius: 10px;
            color: var(--text);
            background: rgba(255, 255, 255, 0.08);
            font: inherit;
            font-size: 15px;
            font-weight: 760;
            cursor: pointer;
            transition: transform 0.06s ease, background 0.15s ease;
        }

        .reset-button:hover,
        .reset-button:focus-visible {
            border-color: rgba(94, 234, 212, 0.55);
            background: rgba(20, 184, 166, 0.16);
            outline: none;
        }

        .reset-button:active {
            transform: translateY(1px);
        }

        .panel__field {
            padding: 14px 16px 0;
        }

        .panel__label {
            display: block;
            margin-bottom: 8px;
            color: var(--muted);
            font-size: 13px;
            font-weight: 740;
        }

        .panel__input {
            width: 100%;
            min-height: 46px;
            padding: 0 14px;
            border: 1px solid var(--line);
            border-radius: 10px;
            color: var(--text);
            background: rgba(255, 255, 255, 0.06);
            font: inherit;
            font-size: 18px;
            font-weight: 800;
            font-variant-numeric: tabular-nums;
            user-select: text;
        }

        .panel__input:hover {
            border-color: rgba(255, 255, 255, 0.24);
        }

        .panel__input:focus {
            border-color: rgba(94, 234, 212, 0.72);
            background: rgba(20, 184, 166, 0.1);
            outline: none;
        }

        .panel__input::placeholder {
            color: rgba(168, 179, 199, 0.72);
            font-weight: 600;
        }

        .panel__hint {
            margin: 10px 16px 0;
            color: var(--muted);
            font-size: 12px;
            font-weight: 740;
            font-variant-numeric: tabular-nums;
        }

        .run-button {
            width: 100%;
            min-height: 50px;
            display: grid;
            place-items: center;
            border: 1px solid rgba(94, 234, 212, 0.55);
            border-radius: 10px;
            color: #042f2e;
            background: linear-gradient(180deg, #5eead4, #14b8a6);
            font: inherit;
            font-size: 17px;
            font-weight: 850;
            cursor: pointer;
            transition: transform 0.06s ease, filter 0.15s ease;
        }

        .run-button:hover,
        .run-button:focus-visible {
            filter: brightness(1.08);
            outline: none;
        }

        .run-button:active {
            transform: translateY(1px);
        }

        .run-button:disabled {
            cursor: not-allowed;
            border-color: var(--line);
            color: var(--muted);
            background: rgba(255, 255, 255, 0.08);
            filter: none;
            transform: none;
        }

        .zoom-panel {
            position: fixed;
            top: 16px;
            right: 16px;
            display: grid;
            grid-auto-flow: column;
            gap: 8px;
            padding: 8px;
            border: 1px solid var(--line);
            background: var(--panel);
            backdrop-filter: blur(16px);
        }

        .zoom-panel__button {
            width: 42px;
            height: 42px;
            display: grid;
            place-items: center;
            border: 1px solid var(--line);
            border-radius: 8px;
            color: var(--text);
            background: rgba(255, 255, 255, 0.1);
            font: inherit;
            font-size: 18px;
            font-weight: 900;
            cursor: pointer;
        }

        .zoom-panel__button:hover,
        .zoom-panel__button:focus-visible {
            border-color: rgba(94, 234, 212, 0.72);
            background: rgba(20, 184, 166, 0.22);
            outline: none;
        }

        .status-pill {
            position: fixed;
            right: 16px;
            bottom: 16px;
            max-width: 250px;
            padding: 8px 10px;
            border: 1px solid var(--line);
            background: var(--panel);
            color: var(--muted);
            font-size: 12px;
            font-weight: 760;
            backdrop-filter: blur(16px);
        }

        @media (max-width: 760px) {
            .panels {
                top: 10px;
                left: 10px;
                width: min(290px, calc(100vw - 20px));
                max-height: calc(100vh - 20px);
            }

            .panel {
                width: 100%;
            }

            .panel__title {
                font-size: 19px;
            }

            .zoom-panel {
                top: auto;
                right: 10px;
                bottom: 14px;
                grid-auto-flow: row;
            }

            .status-pill {
                display: none;
            }
        }
    </style>
</head>

<body>
    <canvas id="board" aria-label="One million number grid"></canvas>

    <div class="panels">
        <section class="panel" id="mainPanel" data-panel="main" aria-label="Ray numbers controls">
            <button class="panel__head" id="mainPanelToggle" type="button" aria-expanded="true" aria-controls="mainPanelBody">
                <span class="panel__head-text">
                    <span class="panel__eyebrow">Ray Numbers</span>
                    <span class="panel__title" id="iterationTitle">Not divisible by 2</span>
                </span>
                <span class="panel__toggle" aria-hidden="true"></span>
            </button>
            <div class="panel__body" id="mainPanelBody">
                <div class="panel__stats" aria-live="polite">
                    <div class="stat">
                        <span class="stat__label"><span class="stat__dot stat__dot--remaining"></span>Numbers remaining</span>
                        <span class="stat__value" id="remainingValue">1,000,000</span>
                    </div>
                    <div class="stat">
                        <span class="stat__label"><span class="stat__dot stat__dot--removed"></span>Numbers removed</span>
                        <span class="stat__value" id="removedValue">0</span>
                    </div>
                </div>
                <div class="panel__action">
                    <button class="remove-button" id="removeButton" type="button">Remove</button>
                    <button class="reset-button" id="resetButton" type="button">Reset</button>
                </div>
            </div>
        </section>

        <section class="panel" id="customPanel" data-panel="custom" aria-label="Try one divisor">
            <button class="panel__head" id="customPanelToggle" type="button" aria-expanded="true" aria-controls="customPanelBody">
                <span class="panel__head-text">
                    <span class="panel__eyebrow">Quick try</span>
                    <span class="panel__title" id="customIterationTitle">Not divisible by …</span>
                </span>
                <span class="panel__toggle" aria-hidden="true"></span>
            </button>
            <div class="panel__body" id="customPanelBody">
                <div class="panel__field">
                    <label class="panel__label" for="customDivisorInput">Divisor</label>
                    <input class="panel__input" id="customDivisorInput" type="number" inputmode="numeric" min="2" step="1" placeholder="e.g. 5">
                </div>
                <div class="panel__stats" aria-live="polite">
                    <div class="stat">
                        <span class="stat__label"><span class="stat__dot stat__dot--remaining"></span>Numbers remaining</span>
                        <span class="stat__value" id="customRemainingValue">1,000,000</span>
                    </div>
                    <div class="stat">
                        <span class="stat__label"><span class="stat__dot stat__dot--removed"></span>Numbers removed</span>
                        <span class="stat__value" id="customRemovedValue">0</span>
                    </div>
                </div>
                <div class="panel__action">
                    <button class="run-button" id="runCustomButton" type="button" disabled>Run</button>
                </div>
            </div>
        </section>

        <section class="panel" id="gridPanel" data-panel="grid" aria-label="Grid settings">
            <button class="panel__head" id="gridPanelToggle" type="button" aria-expanded="true" aria-controls="gridPanelBody">
                <span class="panel__head-text">
                    <span class="panel__eyebrow">Grid</span>
                    <span class="panel__title" id="gridSizeTitle">1,000,000 numbers</span>
                </span>
                <span class="panel__toggle" aria-hidden="true"></span>
            </button>
            <div class="panel__body" id="gridPanelBody">
                <div class="panel__field">
                    <label class="panel__label" for="gridSizeInput">Number of items</label>
                    <input class="panel__input" id="gridSizeInput" type="number" inputmode="numeric" min="1" step="1" value="1000000" placeholder="e.g. 10000">
                </div>
                <p class="panel__hint" id="gridDimensionsLabel">1000 × 1000 grid</p>
                <div class="panel__action">
                    <button class="run-button" id="applyGridButton" type="button">Update grid</button>
                </div>
            </div>
        </section>
    </div>

    <section class="zoom-panel" aria-label="Board view controls">
        <button class="zoom-panel__button" id="zoomOutButton" type="button" title="Zoom out" aria-label="Zoom out">-</button>
        <button class="zoom-panel__button" id="resetViewButton" type="button" title="Reset view" aria-label="Reset view">&#8962;</button>
        <button class="zoom-panel__button" id="zoomInButton" type="button" title="Zoom in" aria-label="Zoom in">+</button>
    </section>

    <div class="status-pill" id="viewStatus">Zoom 100%</div>

    <script>
        (() => {
            "use strict";

            const DEFAULT_TILE_COUNT = 1_000_000;
            const MIN_TILE_COUNT = 1;
            const MAX_TILE_COUNT = 4_000_000;
            const TILE_NUMBER_MIN_ZOOM = 28;
            const STATE_KEY = "rayNumbers:v2";
            const MIN_ZOOM = 0.2;
            const MAX_ZOOM = 96;
            const ZOOM_STEP = 1.22;

            const COLOR_REMAINING = { r: 34, g: 197, b: 94 };
            const COLOR_REMOVED = { r: 190, g: 32, b: 38 };
            const COLOR_EMPTY = { r: 18, g: 24, b: 35 };

            const canvas = document.getElementById("board");
            const ctx = canvas.getContext("2d", { alpha: false });
            const iterationTitle = document.getElementById("iterationTitle");
            const remainingValue = document.getElementById("remainingValue");
            const removedValue = document.getElementById("removedValue");
            const removeButton = document.getElementById("removeButton");
            const resetButton = document.getElementById("resetButton");
            const customIterationTitle = document.getElementById("customIterationTitle");
            const customDivisorInput = document.getElementById("customDivisorInput");
            const customRemainingValue = document.getElementById("customRemainingValue");
            const customRemovedValue = document.getElementById("customRemovedValue");
            const runCustomButton = document.getElementById("runCustomButton");
            const gridSizeTitle = document.getElementById("gridSizeTitle");
            const gridSizeInput = document.getElementById("gridSizeInput");
            const gridDimensionsLabel = document.getElementById("gridDimensionsLabel");
            const applyGridButton = document.getElementById("applyGridButton");
            const viewStatus = document.getElementById("viewStatus");

            let tileCount = DEFAULT_TILE_COUNT;
            let gridCols = 1000;
            let gridRows = 1000;

            const mainGame = {
                state: new Uint8Array(DEFAULT_TILE_COUNT),
                divisor: 2,
                removedCount: 0
            };

            const customGame = {
                state: new Uint8Array(DEFAULT_TILE_COUNT),
                lastDivisor: null,
                removedCount: 0
            };

            let activeMode = "main";

            const panelCollapse = {
                main: false,
                custom: false,
                grid: false
            };

            let boardBitmap = null;
            let boardImage = null;
            let boardImageData = null;
            let animationFrame = 0;
            let resizeObserverFrame = 0;
            let devicePixelRatioValue = window.devicePixelRatio || 1;

            const view = { zoom: 1, panX: 0, panY: 0 };

            const pointerState = {
                pointers: new Map(),
                mode: "idle",
                startX: 0,
                startY: 0,
                startPanX: 0,
                startPanY: 0,
                pinchDistance: 0,
                pinchZoom: 1,
                pinchCenterX: 0,
                pinchCenterY: 0,
                pinchWorldX: 0,
                pinchWorldY: 0
            };

            init();

            function init() {
                const settings = loadSettings();
                applyTileCount(settings.tileCount, { skipSave: true });
                if (settings.divisor >= 2) {
                    restoreMainState(settings.divisor);
                }
                ensureBoardCanvas();
                rebuildBoardFromState(getActiveState());
                resizeCanvas();
                resetView();
                updateMainPanel();
                updateCustomPanel();
                updateGridPanel();
                initPanelCollapse();
                bindEvents();
                requestDraw();
            }

            function initPanelCollapse() {
                document.querySelectorAll("[data-panel]").forEach(panel => {
                    const key = panel.dataset.panel;
                    setPanelCollapsed(panel, panelCollapse[key]);
                    panel.querySelector(".panel__head").addEventListener("click", () => {
                        panelCollapse[key] = !panelCollapse[key];
                        setPanelCollapsed(panel, panelCollapse[key]);
                        saveSettings();
                    });
                });
            }

            function setPanelCollapsed(panel, collapsed) {
                panel.classList.toggle("is-collapsed", collapsed);
                const toggle = panel.querySelector(".panel__head");
                toggle.setAttribute("aria-expanded", String(!collapsed));
                toggle.title = collapsed ? "Expand panel" : "Collapse panel";
            }

            function computeGridDimensions(total) {
                const cols = Math.ceil(Math.sqrt(total));
                const rows = Math.ceil(total / cols);
                return { cols, rows };
            }

            function applyTileCount(total, options = {}) {
                tileCount = total;
                const dimensions = computeGridDimensions(total);
                gridCols = dimensions.cols;
                gridRows = dimensions.rows;
                mainGame.state = new Uint8Array(tileCount);
                customGame.state = new Uint8Array(tileCount);
                resetMainGame();
                resetCustomGame();
                activeMode = "main";
                invalidateBoardCanvas();
                canvas.setAttribute("aria-label", `${tileCount.toLocaleString()} number grid`);
                if (!options.skipSave) {
                    saveSettings();
                }
            }

            function invalidateBoardCanvas() {
                boardBitmap = null;
                boardImage = null;
                boardImageData = null;
            }

            function bindEvents() {
                window.addEventListener("resize", () => {
                    cancelAnimationFrame(resizeObserverFrame);
                    resizeObserverFrame = requestAnimationFrame(() => {
                        resizeCanvas();
                        requestDraw();
                    });
                });

                canvas.addEventListener("wheel", event => {
                    event.preventDefault();
                    const rect = canvas.getBoundingClientRect();
                    const x = event.clientX - rect.left;
                    const y = event.clientY - rect.top;
                    const factor = event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP;
                    zoomAt(x, y, factor);
                }, { passive: false });

                canvas.addEventListener("pointerdown", event => {
                    canvas.setPointerCapture(event.pointerId);
                    pointerState.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY });
                    canvas.classList.add("is-dragging");
                    updatePointerMode();
                });

                canvas.addEventListener("pointermove", event => {
                    if (!pointerState.pointers.has(event.pointerId)) {
                        return;
                    }
                    pointerState.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY });

                    if (pointerState.mode === "pan") {
                        view.panX = pointerState.startPanX + event.clientX - pointerState.startX;
                        view.panY = pointerState.startPanY + event.clientY - pointerState.startY;
                        clampPan();
                        requestDraw();
                    } else if (pointerState.mode === "pinch") {
                        updatePinchZoom();
                    }
                });

                canvas.addEventListener("pointerup", endPointer);
                canvas.addEventListener("pointercancel", endPointer);
                canvas.addEventListener("lostpointercapture", event => {
                    pointerState.pointers.delete(event.pointerId);
                    updatePointerMode();
                });

                document.getElementById("zoomOutButton").addEventListener("click", () => {
                    zoomAt(canvas.clientWidth / 2, canvas.clientHeight / 2, 1 / ZOOM_STEP);
                });
                document.getElementById("zoomInButton").addEventListener("click", () => {
                    zoomAt(canvas.clientWidth / 2, canvas.clientHeight / 2, ZOOM_STEP);
                });
                document.getElementById("resetViewButton").addEventListener("click", resetView);
                removeButton.addEventListener("click", performRemoval);
                resetButton.addEventListener("click", resetGame);
                runCustomButton.addEventListener("click", runCustomDivisor);
                customDivisorInput.addEventListener("input", updateCustomRunButton);
                customDivisorInput.addEventListener("keydown", event => {
                    if (event.key === "Enter" && !runCustomButton.disabled) {
                        event.preventDefault();
                        runCustomDivisor();
                    }
                });
                applyGridButton.addEventListener("click", applyGridSize);
                gridSizeInput.addEventListener("input", updateGridApplyButton);
                gridSizeInput.addEventListener("keydown", event => {
                    if (event.key === "Enter" && !applyGridButton.disabled) {
                        event.preventDefault();
                        applyGridSize();
                    }
                });

                window.addEventListener("keydown", event => {
                    const target = event.target;
                    if (target && ["INPUT", "TEXTAREA", "SELECT", "BUTTON"].includes(target.tagName)) {
                        return;
                    }

                    const panAmount = Math.max(28, 72 / view.zoom);
                    if (event.key === "+" || event.key === "=") {
                        event.preventDefault();
                        zoomAt(canvas.clientWidth / 2, canvas.clientHeight / 2, ZOOM_STEP);
                    } else if (event.key === "-" || event.key === "_") {
                        event.preventDefault();
                        zoomAt(canvas.clientWidth / 2, canvas.clientHeight / 2, 1 / ZOOM_STEP);
                    } else if (event.key === "Home") {
                        event.preventDefault();
                        resetView();
                    } else if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") {
                        event.preventDefault();
                        panBy(panAmount, 0);
                    } else if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") {
                        event.preventDefault();
                        panBy(-panAmount, 0);
                    } else if (event.key === "ArrowUp" || event.key.toLowerCase() === "w") {
                        event.preventDefault();
                        panBy(0, panAmount);
                    } else if (event.key === "ArrowDown" || event.key.toLowerCase() === "s") {
                        event.preventDefault();
                        panBy(0, -panAmount);
                    }
                });
            }

            function resetMainGame() {
                mainGame.state.fill(0);
                mainGame.divisor = 2;
                mainGame.removedCount = 0;
            }

            function resetCustomGame() {
                customGame.state.fill(0);
                customGame.lastDivisor = null;
                customGame.removedCount = 0;
            }

            function getActiveState() {
                return activeMode === "main" ? mainGame.state : customGame.state;
            }

            function resetGame() {
                resetMainGame();
                activeMode = "main";
                rebuildBoardFromState(mainGame.state);
                saveSettings();
                updateMainPanel();
                requestDraw();
            }

            function removeByDivisor(stateArray, targetDivisor) {
                let changed = 0;
                for (let i = 0; i < tileCount; i += 1) {
                    if (stateArray[i] === 1) {
                        continue;
                    }
                    const number = i + 1;
                    if (number % targetDivisor !== 0) {
                        stateArray[i] = 1;
                        changed += 1;
                    }
                }
                return changed;
            }

            function applyGridSize() {
                const nextCount = parseGridSizeInput();
                if (nextCount === null || nextCount === tileCount) {
                    return;
                }

                applyTileCount(nextCount);
                ensureBoardCanvas();
                rebuildBoardFromState(mainGame.state);
                resetView();
                updateMainPanel();
                updateCustomPanel();
                updateGridPanel();
                requestDraw();
            }

            function parseGridSizeInput() {
                const value = Number.parseInt(gridSizeInput.value, 10);
                if (!Number.isInteger(value) || value < MIN_TILE_COUNT || value > MAX_TILE_COUNT) {
                    return null;
                }
                return value;
            }

            function updateGridApplyButton() {
                const parsed = parseGridSizeInput();
                applyGridButton.disabled = parsed === null || parsed === tileCount;
            }

            function updateGridPanel() {
                gridSizeTitle.textContent = `${tileCount.toLocaleString()} numbers`;
                gridDimensionsLabel.textContent = `${gridCols.toLocaleString()} × ${gridRows.toLocaleString()} grid`;
                if (document.activeElement !== gridSizeInput) {
                    gridSizeInput.value = String(tileCount);
                }
                updateGridApplyButton();
            }

            function runCustomDivisor() {
                const targetDivisor = parseCustomDivisor();
                if (targetDivisor === null) {
                    return;
                }

                resetMainGame();

                customGame.state.fill(0);
                customGame.removedCount = removeByDivisor(customGame.state, targetDivisor);
                customGame.lastDivisor = targetDivisor;

                activeMode = "custom";
                rebuildBoardFromState(customGame.state);
                saveSettings();
                updateMainPanel();
                updateCustomPanel();
                requestDraw();
            }

            function parseCustomDivisor() {
                const value = Number.parseInt(customDivisorInput.value, 10);
                if (!Number.isInteger(value) || value < 2) {
                    return null;
                }
                return value;
            }

            function updateCustomRunButton() {
                runCustomButton.disabled = parseCustomDivisor() === null;
            }

            function performRemoval() {
                mainGame.removedCount += removeByDivisor(mainGame.state, mainGame.divisor);
                mainGame.divisor += 1;
                activeMode = "main";
                rebuildBoardFromState(mainGame.state);
                saveSettings();
                updateMainPanel();
                requestDraw();
            }

            function updateMainPanel() {
                const remaining = tileCount - mainGame.removedCount;
                iterationTitle.textContent = `Not divisible by ${mainGame.divisor}`;
                remainingValue.textContent = remaining.toLocaleString();
                removedValue.textContent = mainGame.removedCount.toLocaleString();

                if (remaining === 0) {
                    removeButton.disabled = true;
                    removeButton.textContent = "No numbers left";
                } else {
                    removeButton.disabled = false;
                    removeButton.textContent = "Remove";
                }
            }

            function updateCustomPanel() {
                const remaining = tileCount - customGame.removedCount;
                customRemainingValue.textContent = remaining.toLocaleString();
                customRemovedValue.textContent = customGame.removedCount.toLocaleString();
                customIterationTitle.textContent = customGame.lastDivisor === null
                    ? "Not divisible by …"
                    : `Not divisible by ${customGame.lastDivisor}`;
                updateCustomRunButton();
            }

            function endPointer(event) {
                pointerState.pointers.delete(event.pointerId);
                if (pointerState.pointers.size === 0) {
                    canvas.classList.remove("is-dragging");
                }
                updatePointerMode();
            }

            function updatePointerMode() {
                const points = Array.from(pointerState.pointers.values());
                if (points.length === 0) {
                    pointerState.mode = "idle";
                    return;
                }

                if (points.length === 1) {
                    pointerState.mode = "pan";
                    pointerState.startX = points[0].x;
                    pointerState.startY = points[0].y;
                    pointerState.startPanX = view.panX;
                    pointerState.startPanY = view.panY;
                    return;
                }

                const first = points[0];
                const second = points[1];
                const center = getPointerCenter(first, second);
                const distance = getPointerDistance(first, second);
                pointerState.mode = "pinch";
                pointerState.pinchDistance = distance || 1;
                pointerState.pinchZoom = view.zoom;
                pointerState.pinchCenterX = center.x;
                pointerState.pinchCenterY = center.y;
                pointerState.pinchWorldX = (center.x - view.panX) / view.zoom;
                pointerState.pinchWorldY = (center.y - view.panY) / view.zoom;
            }

            function updatePinchZoom() {
                const points = Array.from(pointerState.pointers.values());
                if (points.length < 2) {
                    return;
                }

                const first = points[0];
                const second = points[1];
                const center = getPointerCenter(first, second);
                const distance = getPointerDistance(first, second) || 1;
                const nextZoom = clamp(pointerState.pinchZoom * distance / pointerState.pinchDistance, MIN_ZOOM, MAX_ZOOM);

                view.zoom = nextZoom;
                view.panX = center.x - pointerState.pinchWorldX * view.zoom;
                view.panY = center.y - pointerState.pinchWorldY * view.zoom;
                clampPan();
                requestDraw();
            }

            function getPointerCenter(first, second) {
                return { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };
            }

            function getPointerDistance(first, second) {
                return Math.hypot(first.x - second.x, first.y - second.y);
            }

            function resizeCanvas() {
                devicePixelRatioValue = window.devicePixelRatio || 1;
                const width = Math.max(1, Math.floor(window.innerWidth * devicePixelRatioValue));
                const height = Math.max(1, Math.floor(window.innerHeight * devicePixelRatioValue));
                canvas.width = width;
                canvas.height = height;
                canvas.style.width = `${window.innerWidth}px`;
                canvas.style.height = `${window.innerHeight}px`;
                ctx.setTransform(devicePixelRatioValue, 0, 0, devicePixelRatioValue, 0, 0);
                clampPan();
            }

            function resetView() {
                const sidePadding = window.innerWidth < 760 ? 24 : 48;
                const verticalPadding = window.innerWidth < 760 ? 220 : 48;
                const fitZoom = Math.min(
                    (canvas.clientWidth - sidePadding) / gridCols,
                    (canvas.clientHeight - verticalPadding) / gridRows
                );
                view.zoom = clamp(fitZoom, MIN_ZOOM, MAX_ZOOM);
                view.panX = (canvas.clientWidth - gridCols * view.zoom) / 2;
                view.panY = (canvas.clientHeight - gridRows * view.zoom) / 2;
                clampPan();
                requestDraw();
            }

            function zoomAt(screenX, screenY, factor) {
                const worldX = (screenX - view.panX) / view.zoom;
                const worldY = (screenY - view.panY) / view.zoom;
                view.zoom = clamp(view.zoom * factor, MIN_ZOOM, MAX_ZOOM);
                view.panX = screenX - worldX * view.zoom;
                view.panY = screenY - worldY * view.zoom;
                clampPan();
                requestDraw();
            }

            function panBy(deltaX, deltaY) {
                view.panX += deltaX;
                view.panY += deltaY;
                clampPan();
                requestDraw();
            }

            function clampPan() {
                const boardWidth = gridCols * view.zoom;
                const boardHeight = gridRows * view.zoom;
                const canvasWidth = canvas.clientWidth || window.innerWidth;
                const canvasHeight = canvas.clientHeight || window.innerHeight;
                const margin = Math.max(80, Math.min(canvasWidth, canvasHeight) * 0.55 + view.zoom);

                if (boardWidth <= canvasWidth) {
                    view.panX = clamp(view.panX, -margin, canvasWidth - boardWidth + margin);
                } else {
                    view.panX = clamp(view.panX, canvasWidth - boardWidth - margin, margin);
                }

                if (boardHeight <= canvasHeight) {
                    view.panY = clamp(view.panY, -margin, canvasHeight - boardHeight + margin);
                } else {
                    view.panY = clamp(view.panY, canvasHeight - boardHeight - margin, margin);
                }
            }

            function requestDraw() {
                if (animationFrame) {
                    return;
                }
                animationFrame = requestAnimationFrame(draw);
            }

            function draw() {
                animationFrame = 0;
                const width = canvas.clientWidth;
                const height = canvas.clientHeight;

                ctx.save();
                ctx.setTransform(devicePixelRatioValue, 0, 0, devicePixelRatioValue, 0, 0);
                ctx.imageSmoothingEnabled = false;
                ctx.fillStyle = "#080c13";
                ctx.fillRect(0, 0, width, height);

                ctx.drawImage(
                    boardBitmap,
                    Math.round(view.panX),
                    Math.round(view.panY),
                    Math.round(gridCols * view.zoom),
                    Math.round(gridRows * view.zoom)
                );

                drawBoardBoundary();
                if (view.zoom >= 8) {
                    drawGridLines();
                }
                if (view.zoom >= TILE_NUMBER_MIN_ZOOM) {
                    drawTileNumbers();
                }
                ctx.restore();

                viewStatus.textContent = `Zoom ${Math.round(view.zoom * 100)}%`;
            }

            function drawBoardBoundary() {
                ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
                ctx.lineWidth = 2;
                ctx.strokeRect(
                    Math.round(view.panX) + 0.5,
                    Math.round(view.panY) + 0.5,
                    Math.round(gridCols * view.zoom),
                    Math.round(gridRows * view.zoom)
                );
            }

            function drawGridLines() {
                const startCol = clamp(Math.floor((-view.panX) / view.zoom), 0, gridCols);
                const endCol = clamp(Math.ceil((canvas.clientWidth - view.panX) / view.zoom), 0, gridCols);
                const startRow = clamp(Math.floor((-view.panY) / view.zoom), 0, gridRows);
                const endRow = clamp(Math.ceil((canvas.clientHeight - view.panY) / view.zoom), 0, gridRows);

                ctx.beginPath();
                ctx.strokeStyle = view.zoom >= 16 ? "rgba(0, 0, 0, 0.42)" : "rgba(0, 0, 0, 0.24)";
                ctx.lineWidth = 1;

                for (let col = startCol; col <= endCol; col += 1) {
                    const x = Math.round(view.panX + col * view.zoom) + 0.5;
                    ctx.moveTo(x, view.panY + startRow * view.zoom);
                    ctx.lineTo(x, view.panY + endRow * view.zoom);
                }

                for (let row = startRow; row <= endRow; row += 1) {
                    const y = Math.round(view.panY + row * view.zoom) + 0.5;
                    ctx.moveTo(view.panX + startCol * view.zoom, y);
                    ctx.lineTo(view.panX + endCol * view.zoom, y);
                }

                ctx.stroke();
            }

            function drawTileNumbers() {
                const startCol = clamp(Math.floor((-view.panX) / view.zoom), 0, gridCols - 1);
                const endCol = clamp(Math.ceil((canvas.clientWidth - view.panX) / view.zoom), 0, gridCols - 1);
                const startRow = clamp(Math.floor((-view.panY) / view.zoom), 0, gridRows - 1);
                const endRow = clamp(Math.ceil((canvas.clientHeight - view.panY) / view.zoom), 0, gridRows - 1);
                const fontSize = clamp(Math.floor(view.zoom * 0.2), 6, 14);
                const tileCenterOffset = view.zoom / 2;

                ctx.save();
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                ctx.font = `800 ${fontSize}px Inter, ui-sans-serif, system-ui, sans-serif`;
                ctx.lineWidth = Math.max(2, fontSize * 0.24);
                ctx.strokeStyle = "rgba(0, 0, 0, 0.76)";
                ctx.fillStyle = "rgba(255, 255, 255, 0.96)";

                for (let row = startRow; row <= endRow; row += 1) {
                    for (let col = startCol; col <= endCol; col += 1) {
                        const number = row * gridCols + col + 1;
                        if (number > tileCount) {
                            continue;
                        }
                        const x = view.panX + col * view.zoom + tileCenterOffset;
                        const y = view.panY + row * view.zoom + tileCenterOffset;
                        const label = String(number);
                        ctx.strokeText(label, x, y);
                        ctx.fillText(label, x, y);
                    }
                }
                ctx.restore();
            }

            function ensureBoardCanvas() {
                if (boardBitmap && boardBitmap.width === gridCols && boardBitmap.height === gridRows) {
                    return;
                }
                boardBitmap = document.createElement("canvas");
                boardBitmap.width = gridCols;
                boardBitmap.height = gridRows;
                boardImage = boardBitmap.getContext("2d", { alpha: false });
                boardImageData = boardImage.createImageData(gridCols, gridRows);
            }

            function rebuildBoardFromState(stateArray) {
                ensureBoardCanvas();
                const data = boardImageData.data;
                const cellCount = gridCols * gridRows;
                for (let cell = 0; cell < cellCount; cell += 1) {
                    const offset = cell * 4;
                    let color;
                    if (cell >= tileCount) {
                        color = COLOR_EMPTY;
                    } else {
                        color = stateArray[cell] === 1 ? COLOR_REMOVED : COLOR_REMAINING;
                    }
                    data[offset] = color.r;
                    data[offset + 1] = color.g;
                    data[offset + 2] = color.b;
                    data[offset + 3] = 255;
                }
                boardImage.putImageData(boardImageData, 0, 0);
            }

            function loadSettings() {
                let saved = null;
                try {
                    const value = localStorage.getItem(STATE_KEY);
                    saved = value ? JSON.parse(value) : null;
                } catch (error) {
                    saved = null;
                }

                const savedTileCount = saved && Number.isInteger(saved.tileCount)
                    ? clamp(saved.tileCount, MIN_TILE_COUNT, MAX_TILE_COUNT)
                    : DEFAULT_TILE_COUNT;
                const divisor = saved && Number.isInteger(saved.divisor) && saved.divisor >= 2
                    ? saved.divisor
                    : 2;
                const collapse = saved && saved.panelCollapse && typeof saved.panelCollapse === "object"
                    ? saved.panelCollapse
                    : {};

                if (typeof collapse.main === "boolean") {
                    panelCollapse.main = collapse.main;
                }
                if (typeof collapse.custom === "boolean") {
                    panelCollapse.custom = collapse.custom;
                }
                if (typeof collapse.grid === "boolean") {
                    panelCollapse.grid = collapse.grid;
                }

                return { tileCount: savedTileCount, divisor };
            }

            function restoreMainState(divisor) {
                activeMode = "main";
                mainGame.divisor = divisor;
                const keepMultiple = lcmRange(2, mainGame.divisor - 1);
                if (keepMultiple === null || keepMultiple > tileCount) {
                    mainGame.state.fill(1);
                    mainGame.removedCount = tileCount;
                    return;
                }

                mainGame.removedCount = 0;
                for (let i = 0; i < tileCount; i += 1) {
                    const removed = (i + 1) % keepMultiple !== 0 ? 1 : 0;
                    mainGame.state[i] = removed;
                    mainGame.removedCount += removed;
                }
            }

            function saveSettings() {
                try {
                    localStorage.setItem(STATE_KEY, JSON.stringify({
                        tileCount,
                        divisor: mainGame.divisor,
                        panelCollapse: {
                            main: panelCollapse.main,
                            custom: panelCollapse.custom,
                            grid: panelCollapse.grid
                        }
                    }));
                } catch (error) {
                    // Ignore storage failures (private mode etc.).
                }
            }

            function lcmRange(from, to) {
                let result = 1;
                for (let n = from; n <= to; n += 1) {
                    result = result / gcd(result, n) * n;
                    if (result > tileCount) {
                        return result;
                    }
                }
                return result;
            }

            function gcd(a, b) {
                while (b !== 0) {
                    [a, b] = [b, a % b];
                }
                return a;
            }

            function clamp(value, min, max) {
                return Math.min(max, Math.max(min, value));
            }
        })();
    </script>
</body>

</html>

Youez - 2016 - github.com/yon3zu
LinuXploit