{"id":41748,"date":"2026-01-24T12:19:18","date_gmt":"2026-01-24T11:19:18","guid":{"rendered":"https:\/\/gruble.net\/?p=41748"},"modified":"2026-04-09T08:10:05","modified_gmt":"2026-04-09T06:10:05","slug":"rpl","status":"publish","type":"post","link":"https:\/\/gruble.net\/rpl\/","title":{"rendered":"Rainbow Puzzle Light"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"41748\" class=\"elementor elementor-41748\" data-elementor-post-type=\"post\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-c741b99 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"c741b99\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-472a332\" data-id=\"472a332\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-3587fe2 elementor-widget elementor-widget-html\" data-id=\"3587fe2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" \/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n    <title>Rainbow Puzzle Light<\/title>\n    <!-- Poppins (som i Elementor-varianten) -->\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Poppins&display=swap\" rel=\"stylesheet\">\n    <style>\n    \/*\n    ------------------------------------------------------------\n    SENTRAL FARGEKONFIGURASJON\n    ------------------------------------------------------------\n    *\/\n    :root {\n        --color-red: #e74c3c;\n        --color-blue: #1e90ff;\n        --color-yellow: #f1c40f;\n        --color-orange: #e67e22;\n        --color-pink: #ff8cbf;\n        --color-green: #28a745;\n        --color-black: #111111;\n        --color-white: #f5f5f5;\n\n        --border-red: #c0392b;\n        --border-blue: #1c6ea4;\n        --border-yellow: #d4ac0d;\n        --border-orange: #cf711f;\n        --border-pink: #e06699;\n        --border-green: #1e7e34;\n        --border-black: #000000;\n        --border-white: #d0d0d0;\n\n        --text-red: white;\n        --text-blue: white;\n        --text-yellow: #2e003e;  \n        --text-orange: white;\n        --text-pink: #2e003e;    \n        --text-green: white;\n        --text-black: white;\n        --text-white: #2e003e;\n\n        --bg-color: #2e003e; \n        --global-text-color: white;\n    }\n\n    \/*\n    ------------------------------------------------------------\n    GLOBAL STIL\n    ------------------------------------------------------------\n    *\/\n    body {\n        font-family: 'Poppins', sans-serif !important;\n        font-weight: 400 !important;\n        color: var(--global-text-color) !important;\n        margin: 0 !important;\n        padding: 0 !important;\n        text-align: center !important;\n        background-color: var(--bg-color);\n        word-wrap: break-word;\n        overflow-wrap: break-word;\n    }\n\n    strong, b { font-weight: 700 !important; }\n    h1, h2, h3, h4, h5, h6 {\n        font-family: 'Poppins', sans-serif !important;\n        font-weight: 700 !important;\n    }\n\n    \/*\n    ------------------------------------------------------------\n    SPILLBRETT OG RUTER (5x5)\n    ------------------------------------------------------------\n    *\/\n    .game-container {\n        display: grid;\n        grid-template-columns: repeat(5, 1fr);\n        gap: 2px;\n        margin: 20px auto;\n        width: 100%;\n        max-width: 500px;\n    }\n    .game-cell {\n        width: 100%;\n        aspect-ratio: 1 \/ 1;\n        cursor: pointer;\n        border-radius: 8px;\n        transition: background-color 0.3s;\n        box-shadow: 0 0 3px rgba(255, 255, 255, 0.3);\n    }\n    .game-cell:hover {\n        box-shadow: 0 0 8px rgba(255, 255, 255, 0.7);\n        transform: scale(1.02);\n    }\n\n    .message-board {\n        margin: 20px;\n        font-size: 16px;\n        min-height: 50px;\n    }\n    .message-board:empty { display: none; }\n\n    .color-red    { background-color: var(--color-red); }\n    .color-blue   { background-color: var(--color-blue); }\n    .color-yellow { background-color: var(--color-yellow); }\n    .color-orange { background-color: var(--color-orange); }\n    .color-pink   { background-color: var(--color-pink); }\n    .color-green  { background-color: var(--color-green); }\n    .color-black  { background-color: var(--color-black); }\n    .color-white  { background-color: var(--color-white); }\n\n    .subtitle {\n        color: var(--color-blue);\n        font-size: 1.2em;\n    }\n\n    \/* Hovedtittel over spillet: kicker + hovedtittel p\u00e5 hver sin linje *\/\n    .topline {\n        margin: 10px 0 14px;\n        padding: 0 12px;\n        box-sizing: border-box;\n        opacity: 1;\n        color: white;\n        text-align: center;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        gap: 4px;\n        line-height: 1.15;\n    }\n    .topline-kicker {\n        font-size: clamp(13px, 3.5vw, 17px);\n        font-weight: 700;\n        letter-spacing: 0.08em;\n        line-height: 1.2;\n        opacity: 0.9;\n    }\n    .topline-main {\n        font-size: clamp(28px, 7.2vw, 44px);\n        font-weight: 900;\n        letter-spacing: 0.2px;\n        line-height: 1.08;\n    }\n\n    \/*\n    ------------------------------------------------------------\n    KNAPPER\n    ------------------------------------------------------------\n    *\/\n    .start-button {\n        background-color: #8e44ad !important;\n        color: white !important;\n        border: 2px solid #732d91 !important;\n        padding: 12px 24px !important;\n        font-size: 18px !important;\n        border-radius: 50px !important;\n        cursor: pointer !important;\n        margin: 20px auto !important;\n        text-decoration: none !important;\n        box-shadow: 0 8px 15px rgba(0, 0, 0, 0.4) !important;\n        transition: background-color 0.3s, box-shadow 0.3s !important;\n        display: flex !important;\n        align-items: center;\n        justify-content: center;\n        width: fit-content;\n        outline: none;\n    }\n    .start-button:hover {\n        background-color: #732d91 !important;\n        box-shadow: 0 12px 20px rgba(0, 0, 0, 0.5) !important;\n    }\n\n    \/* Flyttet ?-knapp nederst (litt mindre) *\/\n    .help-button {\n        padding: 10px 18px !important;\n        font-size: 18px !important;\n        margin: 10px auto 20px !important;\n        min-width: 56px;\n        border-radius: 999px !important;\n        line-height: 1;\n    }\n\n    \/*\n    ------------------------------------------------------------\n    POENGVISNING\n    ------------------------------------------------------------\n    *\/\n    #scoreDisplay {\n        font-size: 20px;\n        margin: 10px auto;\n        padding: 8px 16px;\n        border-radius: 25px;\n        width: 120px;\n        display: block !important;\n        transition: background-color 0.3s, color 0.3s, box-shadow 0.3s, border 0.3s;\n        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);\n        border: 2px solid transparent;\n        text-align: center !important;\n        margin: 0 auto !important;\n    }\n    #scoreDisplay.color-red {\n        background-color: var(--color-red);\n        color: var(--text-red);\n        border: 2px solid var(--border-red);\n    }\n    #scoreDisplay.color-blue {\n        background-color: var(--color-blue);\n        color: var(--text-blue);\n        border: 2px solid var(--border-blue);\n    }\n    #scoreDisplay.color-yellow {\n        background-color: var(--color-yellow);\n        color: var(--text-yellow);\n        border: 2px solid var(--border-yellow);\n    }\n    #scoreDisplay.color-orange {\n        background-color: var(--color-orange);\n        color: var(--text-orange);\n        border: 2px solid var(--border-orange);\n    }\n    #scoreDisplay.color-pink {\n        background-color: var(--color-pink);\n        color: var(--text-pink);\n        border: 2px solid var(--border-pink);\n    }\n    #scoreDisplay.color-green {\n        background-color: var(--color-green);\n        color: var(--text-green);\n        border: 2px solid var(--border-green);\n    }\n    #scoreDisplay.color-black {\n        background-color: var(--color-black);\n        color: var(--text-black);\n        border: 2px solid var(--border-black);\n    }\n    #scoreDisplay.color-white {\n        background-color: var(--color-white);\n        color: var(--text-white);\n        border: 2px solid var(--border-white);\n    }\n\n    \/* Liten \"dagens rekord\" rute p\u00e5 forsiden *\/\n    .daily-top-card {\n        margin: 10px auto 8px;\n        width: fit-content;\n        max-width: calc(100% - 24px);\n        padding: 8px 14px;\n        border-radius: 999px;\n        border: 2px solid rgba(255,255,255,0.22);\n        background: rgba(255,255,255,0.10);\n        color: white;\n        font-size: 14px;\n        line-height: 1.2;\n        box-shadow: 0 6px 12px rgba(0,0,0,0.25);\n        box-sizing: border-box;\n    }\n    .daily-top-card {\n        cursor: pointer;\n    }\n    .daily-top-card:focus {\n        outline: 3px solid rgba(255,255,255,0.35);\n        outline-offset: 2px;\n    }\n    .daily-top-card strong { font-weight: 700; }\n    .daily-top-card.loading { opacity: 0.75; font-style: italic; }\n    .daily-top-card.beat { animation: dailyTopBeat 1.1s ease-in-out infinite; }\n    @keyframes dailyTopBeat {\n        0%, 100% { transform: scale(1); }\n        50% { transform: scale(1.05); }\n    }\n\n    \/* Dagens beste + krone-knapp p\u00e5 samme linje *\/\n    .daily-top-row {\n        margin: 10px auto 8px;\n        width: fit-content;\n        max-width: calc(100% - 24px);\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        gap: 10px;\n    }\n    .daily-top-row .daily-top-card {\n        margin: 0;\n    }\n    .daily-top-rank {\n        \/* Reset typisk WordPress\/Elementor button-styling *\/\n        all: unset;\n        -webkit-appearance: none;\n        appearance: none;\n\n        width: 36px;\n        height: 36px;\n        border-radius: 999px;\n        border: 2px solid rgba(255,255,255,0.22) !important;\n        background: rgba(255,255,255,0.10) !important;\n        color: white !important;\n        box-shadow: 0 6px 12px rgba(0,0,0,0.25) !important;\n        display: inline-flex !important;\n        align-items: center;\n        justify-content: center;\n        cursor: pointer !important;\n        padding: 0 !important;\n        line-height: 1;\n        box-sizing: border-box;\n        font-weight: 900;\n        font-size: 16px;\n        letter-spacing: 0.2px;\n    }\n    .daily-top-rank:hover {\n        background: rgba(255,255,255,0.16) !important;\n        transform: translateY(-1px);\n    }\n    .daily-top-rank:active { transform: translateY(0); }\n    .daily-top-rank:focus {\n        outline: 3px solid rgba(255,255,255,0.35);\n        outline-offset: 2px;\n    }\n    .daily-top-crown {\n        \/* Reset typisk WordPress\/Elementor button-styling *\/\n        all: unset;\n        -webkit-appearance: none;\n        appearance: none;\n\n        width: 36px;\n        height: 36px;\n        border-radius: 999px;\n        border: 2px solid rgba(255,255,255,0.22) !important;\n        background: rgba(255,255,255,0.10) !important;\n        color: white !important;\n        box-shadow: 0 6px 12px rgba(0,0,0,0.25) !important;\n        display: inline-flex !important;\n        align-items: center;\n        justify-content: center;\n        cursor: pointer !important;\n        padding: 0 !important;\n        line-height: 1;\n        box-sizing: border-box;\n    }\n    .daily-top-crown:hover {\n        background: rgba(255,255,255,0.16) !important;\n        transform: translateY(-1px);\n    }\n    .daily-top-crown:active {\n        transform: translateY(0);\n    }\n    .daily-top-crown:focus {\n        outline: 3px solid rgba(255,255,255,0.35);\n        outline-offset: 2px;\n    }\n    .daily-top-crown svg {\n        width: 18px;\n        height: 18px;\n        fill: currentColor;\n        display: block;\n    }\n\n    \/*\n    ------------------------------------------------------------\n    INSTRUKSJONS-POPUP\n    ------------------------------------------------------------\n    *\/\n    .instructions-popup {\n        display: none;\n        position: fixed;\n        left: 50%;\n        top: 50%;\n        transform: translate(-50%, -50%);\n        background-color: #ffffff;\n        color: #2e003e;\n        padding: 20px;\n        border-radius: 10px;\n        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n        z-index: 1000;\n        max-width: 80%;\n        text-align: left;\n        overflow-y: auto;\n        max-height: 80vh;\n    }\n    .instructions-popup h2,\n    .instructions-popup h3,\n    .instructions-popup p,\n    .instructions-popup ul,\n    .instructions-popup li {\n        color: #2e003e;\n    }\n    .instructions-popup button {\n        margin-top: 15px;\n        background-color: #8e44ad !important;\n        color: white !important;\n        border: none !important;\n        padding: 10px 20px !important;\n        border-radius: 25px !important;\n        cursor: pointer !important;\n        transition: background-color 0.3s !important;\n    }\n    .instructions-popup button:hover { background-color: #732d91 !important; }\n    .overlay {\n        display: none;\n        position: fixed;\n        top: 0;\n        left: 0;\n        width: 100%;\n        height: 100%;\n        background-color: rgba(0, 0, 0, 0.5);\n        z-index: 999;\n    }\n\n    \/* Modal for dagens beste (topp 5 \/ i g\u00e5r topp 3) *\/\n    .daily-modal {\n        display: none;\n        position: fixed;\n        left: 50%;\n        top: 50%;\n        transform: translate(-50%, -50%);\n        background: radial-gradient(120% 120% at 50% 0%, rgba(142, 68, 173, 0.14), rgba(255,255,255,1) 55%);\n        color: #2e003e !important;\n        padding: 18px;\n        border-radius: 16px;\n        box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);\n        z-index: 2000;\n        width: 92%;\n        max-width: 520px;\n        text-align: center !important;\n        overflow-y: auto;\n        max-height: 80vh;\n        box-sizing: border-box;\n        border: 3px solid rgba(142, 68, 173, 0.28);\n    }\n    .rank-modal {\n        max-width: 460px;\n    }\n    \/* S\u00f8rg for at ekstern CSS ikke gj\u00f8r teksten hvit *\/\n    .daily-modal, .daily-modal h2, .daily-modal h3, .daily-modal p, .daily-modal div, .daily-modal span {\n        color: #2e003e !important;\n    }\n    .daily-modal.is-open {\n        animation: dailyModalIn 240ms ease-out;\n    }\n    @keyframes dailyModalIn {\n        0% { transform: translate(-50%, -50%) scale(0.94); opacity: 0; }\n        100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }\n    }\n    .daily-modal h2 {\n        margin: 0 0 6px 0;\n        font-size: 22px;\n        letter-spacing: 0.2px;\n        text-shadow: 0 6px 18px rgba(142, 68, 173, 0.18);\n    }\n    .daily-modal .subtitle {\n        margin: 0 0 14px 0;\n        font-size: 13px;\n        opacity: 0.85;\n    }\n    .daily-modal h3 {\n        margin: 16px 0 10px 0;\n        font-size: 15px;\n        text-transform: uppercase;\n        letter-spacing: 0.6px;\n        opacity: 0.9;\n    }\n    .score-badges {\n        display: flex;\n        justify-content: center;\n        gap: 10px;\n        flex-wrap: wrap;\n        margin: 0 auto;\n    }\n    .score-badges.leader-row {\n        margin: 0 auto 12px;\n    }\n    .score-badges.rest-row {\n        margin: 0 auto;\n    }\n    .score-badge {\n        display: inline-flex !important;\n        align-items: center;\n        justify-content: center;\n        padding: 10px 14px;\n        border-radius: 999px;\n        font-weight: 800;\n        font-size: 16px;\n        line-height: 1;\n        min-width: 92px;\n        box-shadow: 0 10px 18px rgba(0,0,0,0.12);\n        border: 2px solid rgba(46,0,62,0.14);\n        transform: translateY(0);\n        animation: badgePop 520ms ease-out both;\n        flex-direction: column !important;\n        text-align: center !important;\n    }\n    .score-badge .score-number {\n        font-size: 18px !important;\n        line-height: 1 !important;\n        display: block !important;\n    }\n    .score-badge.is-leader {\n        min-width: 126px !important;\n        padding: 12px 18px !important;\n    }\n    .score-badge.is-leader .score-number {\n        font-size: 22px !important;\n    }\n    .score-badge.is-leader small {\n        font-size: 12px !important;\n        margin-top: 8px !important;\n    }\n    .score-badge.is-highlight {\n        outline: 4px solid rgba(46,0,62,0.55);\n        outline-offset: 2px;\n        box-shadow: 0 14px 26px rgba(0,0,0,0.18);\n    }\n    .score-badge.is-yesterday .score-number {\n        font-size: 16px !important;\n    }\n    @keyframes badgePop {\n        0% { transform: translateY(6px) scale(0.96); opacity: 0; }\n        100% { transform: translateY(0) scale(1); opacity: 1; }\n    }\n    .score-badge small {\n        display: block !important;\n        font-size: 11px;\n        font-weight: 600;\n        opacity: 0.85;\n        margin-top: 0;\n        margin-top: 6px;\n        white-space: nowrap;\n    }\n    .rank-1 { background: linear-gradient(135deg, #ffd700, #ffb400); }\n    .rank-2 { background: linear-gradient(135deg, #e6e6e6, #bfbfbf); }\n    .rank-3 { background: linear-gradient(135deg, #e8c39e, #c79b6d); }\n    .rank-4 { background: linear-gradient(135deg, #cbb2ff, #8f6bff); color: white !important; }\n    .rank-5 { background: linear-gradient(135deg, #7dd3ff, #1e90ff); color: white !important; }\n    .empty-hint {\n        margin: 6px 0 0;\n        font-size: 14px;\n        opacity: 0.85;\n    }\n    .daily-modal .modal-actions {\n        margin-top: 16px;\n        display: flex;\n        justify-content: center;\n    }\n    .daily-modal button {\n        background-color: #8e44ad !important;\n        color: white !important;\n        border: none !important;\n        padding: 10px 20px !important;\n        border-radius: 999px !important;\n        cursor: pointer !important;\n        box-shadow: 0 10px 18px rgba(142, 68, 173, 0.22);\n    }\n    .daily-modal button:hover { background-color: #732d91 !important; }\n\n    \/*\n    ------------------------------------------------------------\n    END SCREEN (erstatter brett etter fullf\u00f8rt spill)\n    ------------------------------------------------------------\n    *\/\n    .end-content {\n        margin: 20px auto;\n        width: 100%;\n        max-width: 500px;\n        min-height: 320px;\n        border-radius: 12px;\n        padding: 18px;\n        border: 2px solid rgba(255,255,255,0.35);\n        display: none;\n        align-items: center;\n        justify-content: center;\n        text-align: center;\n        box-shadow: 0 0 10px rgba(255,255,255,0.15);\n        background: rgba(255,255,255,0.06);\n        word-wrap: break-word;\n        overflow-wrap: break-word;\n        box-sizing: border-box;\n    }\n    \n    .score-display-row {\n        margin-top: 20px;\n        display: flex;\n        gap: 12px;\n        justify-content: center;\n        align-items: stretch;\n        width: 100%;\n        max-width: 500px;\n        flex-wrap: wrap;\n    }\n    \n    .score-window {\n        padding: 14px 16px;\n        background: rgba(46,0,62,0.12);\n        border-radius: 10px;\n        border: 2px solid rgba(46,0,62,0.2);\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n        flex: 1;\n        min-width: 120px;\n        max-width: 140px;\n    }\n    \n    .score-window.player-score {\n        flex: 1.4;\n        min-width: 160px;\n        max-width: 200px;\n        background: rgba(46,0,62,0.18);\n        border-width: 3px;\n        padding: 18px 20px;\n    }\n    \n    .score-window h3 {\n        margin: 0 0 8px 0;\n        font-size: 13px;\n        font-weight: 600;\n        opacity: 0.9;\n        text-transform: uppercase;\n        letter-spacing: 0.5px;\n    }\n    \n    .score-window.player-score h3 {\n        font-size: 15px;\n        margin-bottom: 10px;\n    }\n    \n    .score-window .score-value {\n        font-size: 24px;\n        font-weight: 700;\n        margin: 0;\n        line-height: 1.2;\n    }\n    \n    .score-window.player-score .score-value {\n        font-size: 32px;\n    }\n    \n    .score-window .new-record {\n        color: #c0392b;\n        font-weight: 700;\n        animation: newRecordPulse 1.5s ease-in-out infinite;\n        position: relative;\n    }\n    \n    @keyframes newRecordPulse {\n        0%, 100% {\n            transform: scale(1);\n            text-shadow: 0 0 0 rgba(192, 57, 43, 0);\n        }\n        50% {\n            transform: scale(1.08);\n            text-shadow: 0 0 20px rgba(192, 57, 43, 0.8), 0 0 30px rgba(192, 57, 43, 0.6);\n        }\n    }\n    \n    .score-window.player-score.new-record-window {\n        animation: recordWindowGlow 2s ease-in-out infinite;\n        border-color: rgba(192, 57, 43, 0.6);\n    }\n    \n    @keyframes recordWindowGlow {\n        0%, 100% {\n            box-shadow: 0 4px 8px rgba(0,0,0,0.15), 0 0 0 rgba(192, 57, 43, 0);\n            border-color: rgba(46,0,62,0.2);\n        }\n        50% {\n            box-shadow: 0 4px 8px rgba(0,0,0,0.15), 0 0 25px rgba(192, 57, 43, 0.6), 0 0 40px rgba(192, 57, 43, 0.4);\n            border-color: rgba(192, 57, 43, 0.8);\n        }\n    }\n    \n    .score-window .loading {\n        opacity: 0.7;\n        font-style: italic;\n        font-size: 12px;\n        margin: 0;\n    }\n\n    \/*\n    ------------------------------------------------------------\n    ADMIN-KONTROLLER (vises kun for innlogget admin)\n    ------------------------------------------------------------\n    *\/\n    .admin-controls {\n        width: 100%;\n        max-width: 500px;\n        margin-top: 10px;\n        padding: 12px;\n        border-radius: 10px;\n        background: rgba(46,0,62,0.10);\n        border: 2px dashed rgba(46,0,62,0.25);\n        box-sizing: border-box;\n    }\n    .admin-controls-row {\n        display: flex;\n        gap: 10px;\n        justify-content: center;\n        align-items: center;\n        flex-wrap: wrap;\n    }\n    .admin-controls input {\n        width: 180px;\n        max-width: 100%;\n        padding: 10px 12px;\n        border-radius: 999px;\n        border: 1px solid rgba(46,0,62,0.3);\n        background: rgba(255,255,255,0.9);\n        color: #2e003e;\n        text-align: center;\n        font-size: 14px;\n        font-family: 'Poppins', sans-serif;\n        outline: none;\n        box-sizing: border-box;\n    }\n    .admin-controls button {\n        padding: 10px 14px;\n        border-radius: 999px;\n        border: 1px solid rgba(46,0,62,0.35);\n        background: rgba(46,0,62,0.14);\n        color: #2e003e;\n        cursor: pointer;\n        font-family: 'Poppins', sans-serif;\n        font-size: 14px;\n        box-sizing: border-box;\n    }\n    .admin-controls button:hover {\n        background: rgba(46,0,62,0.22);\n    }\n    .admin-status {\n        margin: 8px 0 0;\n        font-size: 12px;\n        opacity: 0.85;\n        min-height: 16px;\n    }\n\n    .end-content-inner {\n        width: 100%;\n        display: flex;\n        flex-direction: column;\n        gap: 14px;\n        align-items: center;\n        justify-content: center;\n        word-wrap: break-word;\n        overflow-wrap: break-word;\n        box-sizing: border-box;\n    }\n\n    .store-badges {\n        margin-top: 10px;\n        display: flex;\n        gap: 12px;\n        justify-content: center;\n        align-items: center;\n        flex-wrap: wrap;\n    }\n    .store-badges a {\n        text-decoration: none;\n        display: inline-block;\n        transition: transform 0.2s, opacity 0.2s;\n    }\n    .store-badges a:hover {\n        transform: scale(1.05);\n        opacity: 0.9;\n    }\n    .store-badges a:active {\n        transform: scale(0.98);\n    }\n    .store-badges img {\n        height: 44px; \/* samme h\u00f8yde = \"samme st\u00f8rrelse\" visuelt *\/\n        width: auto;\n        display: block;\n        border-radius: 8px;\n        box-shadow: 0 6px 12px rgba(0,0,0,0.25);\n        background: rgba(255,255,255,0.08);\n    }\n\n\n    @media (max-width: 400px) {\n        .game-container {\n            max-width: 100%;\n            padding: 0 12px;\n            box-sizing: border-box;\n        }\n        .start-button { padding: 8px 16px !important; font-size: 16px !important; }\n        \n        .score-display-row {\n            flex-direction: column;\n            align-items: center;\n        }\n        .score-window {\n            width: 100%;\n            max-width: 100%;\n        }\n        .score-window.player-score {\n            order: -1; \/* Vis spillerens score f\u00f8rst p\u00e5 mobile *\/\n        }\n    }\n    \n    \/* Tekst-wrap generelt *\/\n    p, h1, h2, h3, h4, h5, h6, div, span {\n        word-wrap: break-word;\n        overflow-wrap: break-word;\n        max-width: 100%;\n        box-sizing: border-box;\n    }\n    <\/style>\n<\/head>\n<body>\n\n<p class=\"topline\">\n    <span class=\"topline-kicker\">Dagens<\/span>\n    <span class=\"topline-main\">Rainbow Puzzle<\/span>\n<\/p>\n\n<div class=\"daily-top-row\" aria-label=\"Dagens beste\">\n    <button type=\"button\" id=\"dailyTopRankBtn\" class=\"daily-top-rank\" aria-label=\"Din plassering i dag\">?<\/button>\n    <div id=\"dailyTopCard\" class=\"daily-top-card loading\" aria-live=\"polite\" role=\"button\" tabindex=\"0\">Laster dagens beste\u2026<\/div>\n    <button type=\"button\" id=\"dailyTopCrown\" class=\"daily-top-crown\" aria-label=\"Se flere rekorder (topp 5 i dag og topp 3 i g\u00e5r)\">\n        <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n            <path d=\"M3 7l4 4 5-7 5 7 4-4v11H3V7zm2 9h14v-5.2l-2.7 2.7L12 6.9 7.7 13.5 5 10.8V16z\"\/>\n        <\/svg>\n    <\/button>\n<\/div>\n<div id=\"scoreDisplay\" class=\"color-red\" tabindex=\"-1\">0<\/div>\n<div class=\"message-board\" id=\"messageBoard\" tabindex=\"-1\"><\/div>\n\n<div id=\"gameContainer\" class=\"game-container\" data-post-id=\"post5x5\"><\/div>\n<div id=\"endContent\" class=\"end-content\" aria-live=\"polite\"><\/div>\n\n<button class=\"start-button\" onclick=\"initGame()\" id=\"startButton\" tabindex=\"0\">Nytt fors\u00f8k<\/button>\n\n<button class=\"start-button help-button\" onclick=\"toggleInstructions()\" id=\"instructionsButton\" tabindex=\"0\">?<\/button>\n\n<div class=\"overlay\" id=\"overlay\" onclick=\"toggleInstructions()\"><\/div>\n<div class=\"instructions-popup\" id=\"instructionsPopup\">\n    <h2>Forklaring<\/h2>\n    <p>N\u00e5r du klikker p\u00e5 en farge, eller p\u00e5 en gruppe med samme farge, smittes rutene som ligger inntil, og f\u00e5r samme farge som den fargen du klikket p\u00e5.<\/p>\n    <p>Spillet er ferdig n\u00e5r alle rutene har samme farge.<\/p>\n    <button onclick=\"toggleInstructions()\">Lukk<\/button>\n<\/div>\n\n<!-- Modal: Dagens beste (topp 5) + i g\u00e5r (topp 3) -->\n<div class=\"overlay\" id=\"dailyOverlay\"><\/div>\n<div class=\"daily-modal\" id=\"dailyModal\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Dagens beste\">\n    <h2>Dagens beste<\/h2>\n    <p class=\"subtitle\">Topp 5 i dag + topp 3 i g\u00e5r<\/p>\n    <div id=\"dailyModalContent\">Laster\u2026<\/div>\n    <div class=\"modal-actions\">\n        <button type=\"button\" id=\"dailyModalClose\">Lukk<\/button>\n    <\/div>\n<\/div>\n\n<!-- Modal: Plassering-info (n\u00e5r du er utenfor topp 5) -->\n<div class=\"daily-modal rank-modal\" id=\"rankModal\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Plassering\">\n    <h2>Plassering<\/h2>\n    <p class=\"subtitle\">Slik f\u00e5r du et plassnummer<\/p>\n    <div id=\"rankModalContent\">\n        Kom inn p\u00e5 topp 5-lista i dag, s\u00e5 f\u00e5r du et plassnummer her.\n    <\/div>\n    <div class=\"modal-actions\">\n        <button type=\"button\" id=\"rankModalClose\">Lukk<\/button>\n    <\/div>\n<\/div>\n\n<script>\n    \/\/ Spillvariabler\n    let totalScore        = 0;\n    let colors            = [];\n    let isAnimating       = false;\n    let currentFocusIndex = 0;\n    let isGameOver        = false;\n\n    \/\/ DOM-elementer\n    const gameContainer      = document.getElementById('gameContainer');\n    const messageBoard       = document.getElementById('messageBoard');\n    const startButton        = document.getElementById('startButton');\n    const instructionsButton = document.getElementById('instructionsButton');\n    const scoreDisplay       = document.getElementById('scoreDisplay');\n    const dailyTopCard       = document.getElementById('dailyTopCard');\n    const dailyTopCrown      = document.getElementById('dailyTopCrown');\n    const dailyTopRankBtn    = document.getElementById('dailyTopRankBtn');\n    const endContent         = document.getElementById('endContent');\n\n    \/\/ === 5x5 KONFIG ===\n    const GRID_SIZE    = 5;\n    const TOTAL_CELLS  = GRID_SIZE * GRID_SIZE;\n\n    const colorList = ['red', 'blue', 'yellow', 'orange', 'pink', 'green', 'black', 'white'];\n\n    \/\/ --- Dagens brett (deterministisk per dag) ---\n    \/\/ Bruker Europe\/Oslo slik at \"dagens brett\" skifter ved midnatt i Norge.\n    function getOsloDateKey() {\n        const dtf = new Intl.DateTimeFormat('en-CA', {\n            timeZone: 'Europe\/Oslo',\n            year: 'numeric',\n            month: '2-digit',\n            day: '2-digit'\n        });\n        \/\/ en-CA gir YYYY-MM-DD\n        return dtf.format(new Date());\n    }\n\n    function hashStringToUint32(str) {\n        \/\/ enkel 32-bit hash (FNV-1a-ish)\n        let h = 2166136261;\n        for (let i = 0; i < str.length; i++) {\n            h ^= str.charCodeAt(i);\n            h = Math.imul(h, 16777619);\n        }\n        return h >>> 0;\n    }\n\n    function mulberry32(seed) {\n        let a = seed >>> 0;\n        return function () {\n            a |= 0;\n            a = (a + 0x6D2B79F5) | 0;\n            let t = Math.imul(a ^ (a >>> 15), 1 | a);\n            t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n            return ((t ^ (t >>> 14)) >>> 0) \/ 4294967296;\n        };\n    }\n\n    function generateDailyBoardColors() {\n        const postId = gameContainer?.dataset?.postId || 'default';\n        const dateKey = getOsloDateKey();\n        const seed = hashStringToUint32(`rainbowpuzzle|${postId}|${dateKey}`);\n        const rnd = mulberry32(seed);\n\n        \/\/ Garant\u00e9r fordeling: alle 8 farger med 3+3+3+3+3+3+3+4 = 25 celler\n        \/\/ Velg tilfeldig (men deterministisk) hvilken farge som f\u00e5r 4 (resten f\u00e5r 3)\n        const fourColorIdx = Math.floor(rnd() * colorList.length);\n        const colorCounts = [];\n        for (let i = 0; i < colorList.length; i++) {\n            colorCounts.push(i === fourColorIdx ? 4 : 3);\n        }\n\n        \/\/ Bygg en liste med alle farger i riktig antall\n        const colorPool = [];\n        for (let i = 0; i < colorList.length; i++) {\n            for (let j = 0; j < colorCounts[i]; j++) {\n                colorPool.push(colorList[i]);\n            }\n        }\n\n        \/\/ Stokk colorPool deterministisk (Fisher-Yates shuffle med seed)\n        for (let i = colorPool.length - 1; i > 0; i--) {\n            const j = Math.floor(rnd() * (i + 1));\n            [colorPool[i], colorPool[j]] = [colorPool[j], colorPool[i]];\n        }\n\n        \/\/ Fyll brettet med stokket liste\n        const out = colorPool.slice();\n\n        \/\/ Forbedre spredning: minimer like-farge naboer (reduser sammenhengende klynger)\n        \/\/ Vi gj\u00f8r en deterministisk lokal-s\u00f8k (hill-climb) basert p\u00e5 seed.\n        \/\/ VIKTIG: Vi bytter kun mellom celler som har samme farge-telling, s\u00e5 fordelingen bevares.\n        const neighborsOf = (index) => {\n            const row = Math.floor(index \/ GRID_SIZE);\n            const col = index % GRID_SIZE;\n            const outIdx = [];\n            if (row > 0) outIdx.push(index - GRID_SIZE);\n            if (row < GRID_SIZE - 1) outIdx.push(index + GRID_SIZE);\n            if (col > 0) outIdx.push(index - 1);\n            if (col < GRID_SIZE - 1) outIdx.push(index + 1);\n            return outIdx;\n        };\n\n        const localMatches = (index, color) => {\n            \/\/ Hvor mange naboer (4-retning) som har samme farge\n            let m = 0;\n            const nbs = neighborsOf(index);\n            for (let k = 0; k < nbs.length; k++) {\n                if (out[nbs[k]] === color) m++;\n            }\n            return m;\n        };\n\n        \/\/ Antall iterasjoner er lite (5x5), men nok til \u00e5 redusere klynger merkbart.\n        const ITERATIONS = 2500;\n        for (let t = 0; t < ITERATIONS; t++) {\n            const i = Math.floor(rnd() * TOTAL_CELLS);\n            const j = Math.floor(rnd() * TOTAL_CELLS);\n            if (i === j) continue;\n\n            const colorI = out[i];\n            const colorJ = out[j];\n\n            \/\/ Bytt kun hvis fargene er ulike (bevarer fordelingen)\n            if (colorI === colorJ) continue;\n\n            const beforeI = localMatches(i, colorI);\n            const beforeJ = localMatches(j, colorJ);\n            const afterI = localMatches(i, colorJ);\n            const afterJ = localMatches(j, colorI);\n\n            \/\/ Godta hvis totalt antall matcher reduseres, eller av og til hvis likt (litt \"st\u00f8y\")\n            const totalBefore = beforeI + beforeJ;\n            const totalAfter = afterI + afterJ;\n            if (totalAfter < totalBefore || (totalAfter === totalBefore && rnd() < 0.06)) {\n                out[i] = colorJ;\n                out[j] = colorI;\n            }\n        }\n\n        \/\/ Unng\u00e5 \"ferdig brett\" (alle samme farge) - dette skal ikke skje med v\u00e5r garanterte fordeling,\n        \/\/ men vi sjekker likevel for sikkerhet\n        const first = out[0];\n        const allSame = out.every(c => c === first);\n        if (allSame) {\n            \/\/ Dette skal ikke skje, men hvis det gj\u00f8r det, endre en celle\n            out[TOTAL_CELLS - 1] = colorList[(colorList.indexOf(first) + 1) % colorList.length];\n        }\n\n        return out;\n    }\n\n    function getEndLevel(score) {\n        if (score <= 1000) return 1; \/\/ 0\u20131000\n        if (score <= 2000) return 2; \/\/ 1001\u20132000\n        if (score <= 3000) return 3; \/\/ 2001\u20133000\n        return 4;                    \/\/ 3001+\n    }\n\n    function escapeHtml(str) {\n        return String(str)\n            .replaceAll('&', '&amp;')\n            .replaceAll('<', '&lt;')\n            .replaceAll('>', '&gt;')\n            .replaceAll('\"', '&quot;')\n            .replaceAll(\"'\", '&#039;');\n    }\n\n    \/\/ === DAGENS\/G\u00c5RSDAGENS H\u00d8YESTE SCORE (WordPress REST API) ===\n    let cachedTodayTop = 0;\n    let lastCompletedScore = null; \/\/ siste fullf\u00f8rte score (denne runden)\n    let modalHighlightScore = null;\n    const BEST_SCORE_LS_KEY = 'rainbowpuzzle:bestScore'; \/\/ per nettleser, per dag\n\n    function updateBestScoreForToday(score) {\n        try {\n            const dateKey = getOsloDateKey();\n            const s = Math.floor(Number(score));\n            if (!Number.isFinite(s) || s < 0) return null;\n\n            let best = s;\n            const raw = localStorage.getItem(BEST_SCORE_LS_KEY);\n            if (raw) {\n                const obj = JSON.parse(raw);\n                if (obj && obj.dateKey === dateKey && Number.isFinite(Number(obj.bestScore))) {\n                    best = Math.max(best, Math.floor(Number(obj.bestScore)));\n                }\n            }\n\n            const payload = { dateKey, bestScore: best };\n            localStorage.setItem(BEST_SCORE_LS_KEY, JSON.stringify(payload));\n            return best;\n        } catch (e) {\n            \/\/ localStorage kan v\u00e6re blokkert i enkelte milj\u00f8er\n            console.warn('Kunne ikke lagre score lokalt:', e);\n            return null;\n        }\n    }\n\n    function loadBestScoreForToday() {\n        try {\n            const dateKey = getOsloDateKey();\n            const raw = localStorage.getItem(BEST_SCORE_LS_KEY);\n            if (!raw) return null;\n            const obj = JSON.parse(raw);\n            if (!obj || obj.dateKey !== dateKey) {\n                \/\/ ikke overf\u00f8r til neste dag\n                localStorage.removeItem(BEST_SCORE_LS_KEY);\n                return null;\n            }\n            const s = Number(obj.bestScore);\n            return Number.isFinite(s) ? Math.floor(s) : null;\n        } catch (e) {\n            console.warn('Kunne ikke lese lagret score:', e);\n            return null;\n        }\n    }\n\n    function setDailyRankBadge(rank) {\n        if (!dailyTopRankBtn) return;\n        const r = Number(rank);\n        const inTop5 = Number.isFinite(r) && r >= 1 && r <= 5;\n        dailyTopRankBtn.textContent = inTop5 ? String(r) : '?';\n        dailyTopRankBtn.dataset.rank = inTop5 ? String(r) : '';\n        dailyTopRankBtn.setAttribute(\n            'aria-label',\n            inTop5 ? `Din plassering i dag: ${r}. Trykk for \u00e5 se lista.` : 'Du er ikke p\u00e5 topp 5 i dag. Trykk for info.'\n        );\n    }\n\n    async function refreshDailyRankBadge(score) {\n        \/\/ score her er \"din score vi rangerer etter\" (vi bruker n\u00e5 beste score i dag)\n        lastCompletedScore = Number(score);\n        try {\n            const { today } = await fetchDailyLeaderboard();\n            const uniqToday = Array.from(new Set(today)).sort((a,b)=>b-a).slice(0,5);\n            const idx = uniqToday.indexOf(Math.floor(lastCompletedScore));\n            const rank = idx >= 0 ? (idx + 1) : null;\n            setDailyRankBadge(rank);\n            return rank;\n        } catch (e) {\n            console.warn('Kunne ikke beregne plassering:', e);\n            setDailyRankBadge(null);\n            return null;\n        }\n    }\n\n    function openRankInfoModal() {\n        const overlay = document.getElementById('dailyOverlay');\n        const modal = document.getElementById('rankModal');\n        if (!overlay || !modal) return;\n        \/\/ S\u00f8rg for at leaderboard-modal er skjult\n        const dailyModal = document.getElementById('dailyModal');\n        if (dailyModal) dailyModal.style.display = 'none';\n\n        overlay.style.display = 'block';\n        modal.style.display = 'block';\n        modal.classList.remove('is-open');\n        \/\/ eslint-disable-next-line no-unused-expressions\n        modal.offsetHeight;\n        modal.classList.add('is-open');\n    }\n\n    function setDailyTopCard(value, isLoading = false) {\n        if (!dailyTopCard) return;\n        dailyTopCard.classList.toggle('loading', isLoading);\n        if (isLoading) {\n            dailyTopCard.textContent = 'Laster dagens beste\u2026';\n            return;\n        }\n        const v = Number(value || 0);\n        cachedTodayTop = v;\n        dailyTopCard.innerHTML = `Dagens beste: <strong>${v > 0 ? v.toLocaleString('nb-NO') : '\u2014'}<\/strong>`;\n    }\n\n    function markBeatDailyTopCard() {\n        if (!dailyTopCard) return;\n        dailyTopCard.classList.add('beat');\n        setTimeout(() => dailyTopCard.classList.remove('beat'), 2200);\n    }\n\n    async function fetchDailyHighscores() {\n        try {\n            const response = await fetch('\/wp-json\/rainbowpuzzle\/v1\/daily-highscores');\n            if (!response.ok) throw new Error('Failed to fetch');\n            const data = await response.json();\n            \/\/ Speil dagens beste p\u00e5 forsiden\n            setDailyTopCard(Number(data.today || 0), false);\n            return {\n                today: Number(data.today || 0),\n                yesterday: Number(data.yesterday || 0)\n            };\n        } catch (error) {\n            console.error('Feil ved henting av dagens highscores:', error);\n            return { today: 0, yesterday: 0 };\n        }\n    }\n\n    async function fetchDailyLeaderboard() {\n        const res = await fetch('\/wp-json\/rainbowpuzzle\/v1\/daily-leaderboard');\n        if (!res.ok) throw new Error('Failed to fetch daily leaderboard');\n        const data = await res.json();\n        return {\n            today: Array.isArray(data.today) ? data.today.map(Number).filter(n => Number.isFinite(n)) : [],\n            yesterday: Array.isArray(data.yesterday) ? data.yesterday.map(Number).filter(n => Number.isFinite(n)) : []\n        };\n    }\n\n    function renderDailyModalContent(todayScores, yesterdayScores, highlightScore = null) {\n        const uniqToday = Array.from(new Set(todayScores)).sort((a,b)=>b-a).slice(0,5);\n        const uniqYesterday = Array.from(new Set(yesterdayScores)).sort((a,b)=>b-a).slice(0,3);\n\n        const badgeClassForIndex = (i) => {\n            switch (i) {\n                case 0: return 'rank-1';\n                case 1: return 'rank-2';\n                case 2: return 'rank-3';\n                case 3: return 'rank-4';\n                default: return 'rank-5';\n            }\n        };\n\n        const todayHtml = (() => {\n            if (!uniqToday.length) return `<p class=\"empty-hint\">Ingen score enn\u00e5 i dag.<\/p>`;\n            const leader = uniqToday[0];\n            const rest = uniqToday.slice(1);\n            const leaderIsHighlight = (highlightScore !== null && Number(leader) === Number(highlightScore));\n            const leaderHtml = `\n                <div class=\"score-badges leader-row\">\n                    <div class=\"score-badge is-leader ${leaderIsHighlight ? 'is-highlight' : ''} ${badgeClassForIndex(0)}\" style=\"animation-delay:0ms\">\n                        <span class=\"score-number\">${leader.toLocaleString('nb-NO')}<\/span>\n                        <small>poeng<\/small>\n                    <\/div>\n                <\/div>\n            `;\n            const restHtml = rest.length ? `\n                <div class=\"score-badges rest-row\">${\n                    rest.map((s, i) =>\n                        `<div class=\"score-badge ${(highlightScore !== null && Number(s) === Number(highlightScore)) ? 'is-highlight' : ''} ${badgeClassForIndex(i + 1)}\" style=\"animation-delay:${(i + 1) * 60}ms\">\n                            <span class=\"score-number\">${s.toLocaleString('nb-NO')}<\/span>\n                            <small>poeng<\/small>\n                        <\/div>`\n                    ).join('')\n                }<\/div>\n            ` : '';\n            return leaderHtml + restHtml;\n        })();\n\n        const yesterdayHtml = (() => {\n            if (!uniqYesterday.length) return `<p class=\"empty-hint\">Ingen score i g\u00e5r.<\/p>`;\n            const leader = uniqYesterday[0];\n            const rest = uniqYesterday.slice(1);\n            const leaderIsHighlight = (highlightScore !== null && Number(leader) === Number(highlightScore));\n            const leaderHtml = `\n                <div class=\"score-badges leader-row\">\n                    <div class=\"score-badge is-leader is-yesterday ${leaderIsHighlight ? 'is-highlight' : ''} ${badgeClassForIndex(0)}\" style=\"animation-delay:0ms; min-width:86px; padding:9px 12px\">\n                        <span class=\"score-number\">${leader.toLocaleString('nb-NO')}<\/span>\n                        <small>poeng<\/small>\n                    <\/div>\n                <\/div>\n            `;\n            const restHtml = rest.length ? `\n                <div class=\"score-badges rest-row\">${\n                    rest.map((s, i) =>\n                        `<div class=\"score-badge is-yesterday ${(highlightScore !== null && Number(s) === Number(highlightScore)) ? 'is-highlight' : ''} ${badgeClassForIndex(i + 1)}\" style=\"animation-delay:${(i + 1) * 60}ms; min-width:86px; padding:9px 12px\">\n                            <span class=\"score-number\">${s.toLocaleString('nb-NO')}<\/span>\n                            <small>poeng<\/small>\n                        <\/div>`\n                    ).join('')\n                }<\/div>\n            ` : '';\n            return leaderHtml + restHtml;\n        })();\n\n        return `\n            <h3>Topp 5 i dag<\/h3>\n            ${todayHtml}\n            <h3>Topp 3 i g\u00e5r<\/h3>\n            ${yesterdayHtml}\n        `;\n    }\n\n    function openDailyModal() {\n        const overlay = document.getElementById('dailyOverlay');\n        const modal = document.getElementById('dailyModal');\n        const content = document.getElementById('dailyModalContent');\n        if (!overlay || !modal || !content) return;\n\n        overlay.style.display = 'block';\n        modal.style.display = 'block';\n        \/\/ retrigger entrance animation\n        modal.classList.remove('is-open');\n        \/\/ eslint-disable-next-line no-unused-expressions\n        modal.offsetHeight;\n        modal.classList.add('is-open');\n        content.textContent = 'Laster\u2026';\n\n        fetchDailyLeaderboard()\n            .then(({today, yesterday}) => {\n                \/\/ Bruk eksplisitt highlight fra rank-knapp hvis satt, ellers automatisk fra beste score i dag\n                let highlight = modalHighlightScore;\n                modalHighlightScore = null;\n                \n                \/\/ Hvis ingen eksplisitt highlight, sjekk om din beste score i dag er i topplisten\n                if (highlight === null) {\n                    const bestToday = loadBestScoreForToday();\n                    if (Number.isFinite(bestToday)) {\n                        const uniqToday = Array.from(new Set(today)).sort((a,b)=>b-a).slice(0,5);\n                        if (uniqToday.includes(Math.floor(bestToday))) {\n                            highlight = bestToday;\n                        }\n                    }\n                }\n                \n                content.innerHTML = renderDailyModalContent(today, yesterday, highlight);\n            })\n            .catch((e) => {\n                console.error('Feil ved henting av modal-data:', e);\n                content.textContent = 'Kunne ikke laste listen akkurat n\u00e5.';\n            });\n    }\n\n    function closeDailyModal() {\n        const overlay = document.getElementById('dailyOverlay');\n        const modal = document.getElementById('dailyModal');\n        if (overlay) overlay.style.display = 'none';\n        if (modal) modal.style.display = 'none';\n    }\n\n    async function submitScore(score) {\n        try {\n            const response = await fetch('\/wp-json\/rainbowpuzzle\/v1\/submit-score', {\n                method: 'POST',\n                headers: { 'Content-Type': 'application\/json' },\n                body: JSON.stringify({ score: Number(score) })\n            });\n            if (!response.ok) throw new Error('Failed to submit');\n            const data = await response.json();\n            return data;\n        } catch (error) {\n            console.error('Feil ved innsending av score:', error);\n            return null;\n        }\n    }\n\n    \/\/ === ADMIN (WordPress cookie + nonce) ===\n    let adminNonceCache = null;\n\n    async function fetchAdminNonce() {\n        \/\/ Best: nonce injisert av WordPress for admin\n        if (window.RAINBOWPUZZLE_ADMIN_NONCE) return window.RAINBOWPUZZLE_ADMIN_NONCE;\n\n        if (adminNonceCache) return adminNonceCache;\n        try {\n            \/\/ Fallback: noen WP-oppsett har wpApiSettings.nonce globalt\n            adminNonceCache = window.wpApiSettings?.nonce || null;\n            if (!adminNonceCache) console.warn('[admin-controls] no admin nonce found');\n            return adminNonceCache;\n        } catch {\n            console.warn('[admin-controls] nonce lookup error');\n            return null;\n        }\n    }\n\n    async function adminResetToday() {\n        const nonce = await fetchAdminNonce();\n        if (!nonce) return { ok: false, message: 'Ikke admin \/ ikke innlogget.' };\n        const res = await fetch('\/wp-json\/rainbowpuzzle\/v1\/reset-today', {\n            method: 'POST',\n            credentials: 'include',\n            headers: { 'X-WP-Nonce': nonce }\n        });\n        const data = await res.json().catch(() => null);\n        return { ok: res.ok, data, message: res.ok ? 'Nullstilt.' : 'Feil ved nullstilling.' };\n    }\n\n    async function adminSetToday(score) {\n        const nonce = await fetchAdminNonce();\n        if (!nonce) return { ok: false, message: 'Ikke admin \/ ikke innlogget.' };\n        const res = await fetch('\/wp-json\/rainbowpuzzle\/v1\/set-today', {\n            method: 'POST',\n            credentials: 'include',\n            headers: { 'Content-Type': 'application\/json', 'X-WP-Nonce': nonce },\n            body: JSON.stringify({ score: Number(score) })\n        });\n        const data = await res.json().catch(() => null);\n        return { ok: res.ok, data, message: res.ok ? 'Oppdatert.' : 'Feil ved oppdatering.' };\n    }\n\n    async function adminResetYesterday() {\n        const nonce = await fetchAdminNonce();\n        if (!nonce) return { ok: false, message: 'Ikke admin \/ ikke innlogget.' };\n        const res = await fetch('\/wp-json\/rainbowpuzzle\/v1\/reset-yesterday', {\n            method: 'POST',\n            credentials: 'include',\n            headers: { 'X-WP-Nonce': nonce }\n        });\n        const data = await res.json().catch(() => null);\n        return { ok: res.ok, data, message: res.ok ? 'Nullstilt.' : 'Feil ved nullstilling.' };\n    }\n\n    async function adminSetYesterday(score) {\n        const nonce = await fetchAdminNonce();\n        if (!nonce) return { ok: false, message: 'Ikke admin \/ ikke innlogget.' };\n        const res = await fetch('\/wp-json\/rainbowpuzzle\/v1\/set-yesterday', {\n            method: 'POST',\n            credentials: 'include',\n            headers: { 'Content-Type': 'application\/json', 'X-WP-Nonce': nonce },\n            body: JSON.stringify({ score: Number(score) })\n        });\n        const data = await res.json().catch(() => null);\n        return { ok: res.ok, data, message: res.ok ? 'Oppdatert.' : 'Feil ved oppdatering.' };\n    }\n\n    function getAdminControlsHtml() {\n        return `\n            <div id=\"adminControls\" class=\"admin-controls\" style=\"display:none;\">\n                <div class=\"admin-controls-row\" style=\"margin-bottom:10px;\">\n                    <button id=\"adminResetBtn\" type=\"button\">Nullstill dagens beste<\/button>\n                <\/div>\n                <div class=\"admin-controls-row\">\n                    <input id=\"adminSetInput\" type=\"number\" min=\"0\" step=\"1\" placeholder=\"Sett dagens beste\" \/>\n                    <button id=\"adminSetBtn\" type=\"button\">Sett<\/button>\n                <\/div>\n                <div class=\"admin-controls-row\" style=\"margin-top:10px; margin-bottom:10px;\">\n                    <button id=\"adminResetYesterdayBtn\" type=\"button\">Nullstill g\u00e5rsdagens topp<\/button>\n                <\/div>\n                <div class=\"admin-controls-row\">\n                    <input id=\"adminSetYesterdayInput\" type=\"number\" min=\"0\" step=\"1\" placeholder=\"Sett g\u00e5rsdagens topp\" \/>\n                    <button id=\"adminSetYesterdayBtn\" type=\"button\">Sett<\/button>\n                <\/div>\n                <p id=\"adminStatus\" class=\"admin-status\"><\/p>\n            <\/div>\n        `;\n    }\n\n    async function setupAdminControls(root, currentScore) {\n        const panel = root?.querySelector?.('#adminControls');\n        if (!panel) return;\n\n        const nonce = await fetchAdminNonce();\n        if (!nonce) {\n            \/\/ Fjern helt hvis ikke admin\n            console.warn('[admin-controls] hidden (not admin \/ no nonce)');\n            panel.remove();\n            return;\n        }\n\n        panel.style.display = 'block';\n\n        const statusEl = panel.querySelector('#adminStatus');\n        const resetBtn = panel.querySelector('#adminResetBtn');\n        const setBtn = panel.querySelector('#adminSetBtn');\n        const input = panel.querySelector('#adminSetInput');\n        const resetYBtn = panel.querySelector('#adminResetYesterdayBtn');\n        const setYBtn = panel.querySelector('#adminSetYesterdayBtn');\n        const inputY = panel.querySelector('#adminSetYesterdayInput');\n\n        const setStatus = (txt) => { if (statusEl) statusEl.textContent = txt || ''; };\n\n        const refreshScoreRow = async () => {\n            const hs = await fetchDailyHighscores();\n            const row = root.querySelector('.score-display-row');\n            if (row) row.outerHTML = renderDailyHighscoresHtml(hs.today, hs.yesterday, currentScore);\n        };\n\n        if (resetBtn) {\n            resetBtn.onclick = async () => {\n                setStatus('Nullstiller dagens beste\u2026');\n                const result = await adminResetToday();\n                setStatus(result.message);\n                await refreshScoreRow();\n            };\n        }\n\n        if (setBtn) {\n            setBtn.onclick = async () => {\n                const v = Number(input?.value);\n                if (!Number.isFinite(v) || v < 0) {\n                    setStatus('Ugyldig score.');\n                    return;\n                }\n                setStatus('Setter dagens beste\u2026');\n                const result = await adminSetToday(Math.floor(v));\n                setStatus(result.message);\n                await refreshScoreRow();\n            };\n        }\n\n        if (resetYBtn) {\n            resetYBtn.onclick = async () => {\n                setStatus('Nullstiller g\u00e5rsdagens topp\u2026');\n                const result = await adminResetYesterday();\n                setStatus(result.message);\n                await refreshScoreRow();\n            };\n        }\n\n        if (setYBtn) {\n            setYBtn.onclick = async () => {\n                const v = Number(inputY?.value);\n                if (!Number.isFinite(v) || v < 0) {\n                    setStatus('Ugyldig score.');\n                    return;\n                }\n                setStatus('Setter g\u00e5rsdagens topp\u2026');\n                const result = await adminSetYesterday(Math.floor(v));\n                setStatus(result.message);\n                await refreshScoreRow();\n            };\n        }\n    }\n\n    function renderDailyHighscoresHtml(today, yesterday, currentScore) {\n        const isNewRecord = currentScore > today;\n        const bestToday = loadBestScoreForToday();\n        return `\n            <div class=\"score-display-row\">\n                <div class=\"score-window\">\n                    <h3>Dagens beste<\/h3>\n                    <p class=\"score-value\">${today > 0 ? today.toLocaleString('nb-NO') : '\u2014'}<\/p>\n                <\/div>\n                <div class=\"score-window player-score ${isNewRecord ? 'new-record-window' : ''}\">\n                    <h3>Din poengsum<\/h3>\n                    <p class=\"score-value ${isNewRecord ? 'new-record' : ''}\">\n                        ${isNewRecord ? '\ud83c\udf89 ' : ''}${currentScore.toLocaleString('nb-NO')}\n                    <\/p>\n                    ${Number.isFinite(bestToday) ? `<p style=\"margin:6px 0 0; font-size:12px; opacity:0.85;\">Din beste i dag: <strong>${Number(bestToday).toLocaleString('nb-NO')}<\/strong><\/p>` : ''}\n                <\/div>\n                <div class=\"score-window\">\n                    <h3>G\u00e5rsdagens topp<\/h3>\n                    <p class=\"score-value\">${yesterday > 0 ? yesterday.toLocaleString('nb-NO') : '\u2014'}<\/p>\n                <\/div>\n            <\/div>\n        `;\n    }\n\n    function renderEndContent(score) {\n        const level = getEndLevel(score);\n\n        \/\/ Niv\u00e5-tekster\n        const levelTexts = {\n            1: 'Du har fullf\u00f8rt brettet med lav poengsum. Du f\u00e5r mange poeng hvis du endrer farge mange ganger.',\n            2: 'Du har begynt \u00e5 forst\u00e5 det. \u00c5 endre store grupper gir mange poeng. Det l\u00f8nner seg \u00e5 klikke p\u00e5 brikker som ikke er del av en fargegruppe. Du kan laste ned appen (nedenfor), men det er ikke sikkert at du er helt klar for det niv\u00e5et. Kanskje du b\u00f8r trene litt mer f\u00f8rst.',\n            3: 'Dette er imponerende. \u00c5 isolere enkeltst\u00e5ende farger mot slutten gir gode poengsummer. Du er klar til \u00e5 laste ned appen. Klikk p\u00e5 en av knappene nedenfor.',\n            4: 'Du er en stormester. Gruble.net kan ikke l\u00e6re deg mer om dette spillet. Men vi anbefaler deg sterkt \u00e5 laste ned appen, og konkurrere med de beste av de beste.'\n        };\n\n        \/\/ Bakgrunnsfarger per niv\u00e5\n        const levelBgColors = {\n            1: '#ffffff',      \/\/ hvit\n            2: '#e8c39e',      \/\/ lys bronse\n            3: '#c0c0c0',      \/\/ s\u00f8lv\n            4: '#ffd700'       \/\/ gull\n        };\n\n        \/\/ Sett bakgrunnsfarge og tekstfarge\n        const bgColor = levelBgColors[level] || '#ffffff';\n        endContent.style.backgroundColor = bgColor;\n        endContent.style.color = '#2e003e'; \/\/ m\u00f8rk tekst p\u00e5 lyse bakgrunner\n        endContent.style.borderColor = 'rgba(46,0,62,0.25)';\n\n        const storeBadgesHtml = (level === 1) ? '' : `\n            <div class=\"store-badges\" aria-label=\"App-butikker\">\n                <a href=\"https:\/\/apps.apple.com\/no\/app\/rainbow-puzzle\/id6751543177?l=nb\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"Last ned i App Store\">\n                    <img decoding=\"async\"\n                        src=\"https:\/\/gruble.net\/wp-content\/uploads\/appstore-btn.png\"\n                        alt=\"Last ned i App Store\"\n                        loading=\"lazy\"\n                    \/>\n                <\/a>\n                <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.VEGILLITOCANSTUDIO.RAINBOWPUZZLE\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"F\u00e5 den p\u00e5 Google Play\">\n                    <img decoding=\"async\"\n                        src=\"https:\/\/gruble.net\/wp-content\/uploads\/Google_Play_Store_badge_EN.svg.png\"\n                        alt=\"F\u00e5 den p\u00e5 Google Play\"\n                        loading=\"lazy\"\n                    \/>\n                <\/a>\n            <\/div>\n        `;\n        const adminControlsHtml = getAdminControlsHtml();\n\n        \/\/ Vis loading-state f\u00f8rst\n        endContent.innerHTML = `\n            <div class=\"end-content-inner\">\n                <p style=\"margin:0; font-size:18px; line-height:1.6; word-wrap:break-word; overflow-wrap:break-word;\">${escapeHtml(levelTexts[level] || '')}<\/p>\n                <div class=\"score-display-row\">\n                    <div class=\"score-window\">\n                        <p class=\"loading\">Laster...<\/p>\n                    <\/div>\n                    <div class=\"score-window player-score\">\n                        <h3>Din poengsum<\/h3>\n                        <p class=\"score-value\">${score.toLocaleString('nb-NO')}<\/p>\n                    <\/div>\n                    <div class=\"score-window\">\n                        <p class=\"loading\">Laster...<\/p>\n                    <\/div>\n                <\/div>\n                ${adminControlsHtml}\n                ${storeBadgesHtml}\n            <\/div>\n        `;\n        endContent.style.display = 'flex';\n\n        \/\/ Pr\u00f8v \u00e5 aktivere admin-kontroller umiddelbart (hvis admin-nonce finnes)\n        \/\/ (sikrer at panelet ikke blir st\u00e5ende skjult)\n        setupAdminControls(endContent, score);\n\n        \/\/ Hent dagens\/g\u00e5rsdagens highscores og oppdater visningen\n        (async () => {\n            const highscores = await fetchDailyHighscores();\n            const dailyHtml = renderDailyHighscoresHtml(highscores.today, highscores.yesterday, score);\n            \n            \/\/ Oppdater HTML med faktiske verdier\n            const inner = endContent.querySelector('.end-content-inner');\n            if (inner) {\n                inner.innerHTML = `\n                    <p style=\"margin:0; font-size:18px; line-height:1.6; word-wrap:break-word; overflow-wrap:break-word;\">${escapeHtml(levelTexts[level] || '')}<\/p>\n                    ${dailyHtml}\n                    ${adminControlsHtml}\n                    ${storeBadgesHtml}\n                `;\n                await setupAdminControls(inner, score);\n            }\n\n            \/\/ Send alltid inn score: backend lagrer kun topp 5 (unik score) for dagen\n            const submitResult = await submitScore(score);\n            \/\/ Regel B + \"ikke rykke ned av lavere score\":\n            \/\/ lagre og bruke din beste score i dag som grunnlag for plassering\/highlight.\n            const bestToday = updateBestScoreForToday(score);\n            const effective = Number.isFinite(bestToday) ? bestToday : score;\n            await refreshDailyRankBadge(effective);\n\n            \/\/ Hvis ny dagsrekord, hent p\u00e5 nytt for \u00e5 vise oppdatert verdi i UI\n            if (submitResult && submitResult.new_record) {\n                const updated = await fetchDailyHighscores();\n                const updatedHtml = renderDailyHighscoresHtml(updated.today, updated.yesterday, score);\n                if (inner) {\n                    inner.innerHTML = `\n                        <p style=\"margin:0; font-size:18px; line-height:1.6; word-wrap:break-word; overflow-wrap:break-word;\">${escapeHtml(levelTexts[level] || '')}<\/p>\n                        ${updatedHtml}\n                        ${adminControlsHtml}\n                        ${storeBadgesHtml}\n                    `;\n                    await setupAdminControls(inner, score);\n                }\n            }\n        })();\n    }\n\n    function hideEndContent() {\n        endContent.style.display = 'none';\n        endContent.innerHTML = '';\n    }\n\n    \/\/ INITIER SPILLET\n    function initGame() {\n        totalScore = 0;\n        isGameOver = false;\n\n        hideEndContent();\n        gameContainer.style.display = 'grid';\n        gameContainer.innerHTML = '';\n\n        messageBoard.textContent = '';\n        colors = generateDailyBoardColors();\n        isAnimating = false;\n        currentFocusIndex = 0;\n\n        updateScoreDisplay();\n        changeScoreDisplayColor();\n\n        \/\/ Vis dagens beste p\u00e5 \"forsiden\"\n        setDailyTopCard(0, true);\n        setDailyRankBadge(null);\n        fetchDailyHighscores();\n        \/\/ Regel B: bare vis plassering hvis du har spilt i dag (denne nettleseren)\n        const storedBest = loadBestScoreForToday();\n        if (Number.isFinite(storedBest)) {\n            refreshDailyRankBadge(storedBest);\n        }\n\n        for (let i = 0; i < TOTAL_CELLS; i++) {\n            const cell = document.createElement('div');\n            cell.classList.add('game-cell', `color-${colors[i]}`);\n            cell.dataset.color = colors[i];\n            cell.setAttribute('tabindex', '0');\n            cell.dataset.index = i;\n            cell.addEventListener('click', handleCellClick);\n            gameContainer.appendChild(cell);\n        }\n    }\n\n    function updateScoreDisplay() {\n        scoreDisplay.textContent = totalScore;\n    }\n\n    function changeScoreDisplayColor() {\n        const selectedColor = colorList[Math.floor(Math.random() * colorList.length)];\n        colorList.forEach(c => scoreDisplay.classList.remove(`color-${c}`));\n        scoreDisplay.classList.add(`color-${selectedColor}`);\n    }\n\n    \/\/ Klikk p\u00e5 celle\n    async function handleCellClick(e) {\n        if (isGameOver || isAnimating) return;\n        const index = parseInt(e.target.dataset.index);\n        const color = colors[index];\n\n        messageBoard.textContent = '';\n        isAnimating = true;\n\n        try {\n            const { pointsEarned, updatedColors } = await performColorChange(index, color);\n            isAnimating = false;\n\n            if (pointsEarned > 0) {\n                totalScore += pointsEarned;\n                updateScoreDisplay();\n                changeScoreDisplayColor();\n                colors = updatedColors;\n                updateBoardUI();\n\n                \/\/ Motivasjon: hvis du passerer dagens beste underveis\n                if (cachedTodayTop > 0 && totalScore > cachedTodayTop) {\n                    markBeatDailyTopCard();\n                }\n            }\n\n            if (isGameCompleted(updatedColors)) {\n                isGameOver = true;\n                \/\/ Fjern rutene og vis end-screen med niv\u00e5\n                gameContainer.innerHTML = '';\n                gameContainer.style.display = 'none';\n                renderEndContent(totalScore);\n            }\n        } catch (error) {\n            isAnimating = false;\n            console.error('Feil under h\u00e5ndtering av celleklikk:', error);\n        }\n    }\n\n    \/\/ Fargeendring med animasjon\n    async function performColorChange(index, color) {\n        const updatedColors = [...colors];\n        const initialCluster = getConnectedGroup(index, color);\n        const adjacentIndices = getAdjacentIndicesMultiple(initialCluster);\n        const adjacentClusters = getUniqueClusters(adjacentIndices);\n        const allClusters = [initialCluster, ...adjacentClusters];\n        const cellsToChange = [...new Set(allClusters.flat())];\n\n        const cellsThatWillChange = cellsToChange.filter(idx => updatedColors[idx] !== color);\n        const numChangedCells = cellsThatWillChange.length;\n        const pointsEarned = numChangedCells * numChangedCells;\n\n        if (numChangedCells === 0) return { pointsEarned: 0, updatedColors };\n\n        const shuffledCells = shuffleArraySecure(cellsThatWillChange);\n        shuffledCells.forEach((idx, i) => {\n            setTimeout(() => {\n                updatedColors[idx] = color;\n                const cell = gameContainer.children[idx];\n                if (cell) cell.className = `game-cell color-${color}`;\n            }, i * 30);\n        });\n        await delay(numChangedCells * 30 + 50);\n        return { pointsEarned, updatedColors };\n    }\n\n    function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }\n\n    \/\/ Finn sammenhengende cluster\n    function getConnectedGroup(index, color) {\n        const group = [];\n        const queue = [index];\n        const visited = new Set();\n        while (queue.length > 0) {\n            const currentIndex = queue.shift();\n            if (visited.has(currentIndex)) continue;\n            visited.add(currentIndex);\n\n            if (colors[currentIndex] === color) {\n                group.push(currentIndex);\n                const neighbors = getAdjacentIndices(currentIndex);\n                neighbors.forEach(n => {\n                    if (!visited.has(n) && colors[n] === color) queue.push(n);\n                });\n            }\n        }\n        return group;\n    }\n\n    function getAdjacentIndicesMultiple(cluster) {\n        const adjacentIndices = new Set();\n        cluster.forEach(idx => getAdjacentIndices(idx).forEach(a => adjacentIndices.add(a)));\n        return Array.from(adjacentIndices);\n    }\n\n    function getUniqueClusters(indices) {\n        const clusters = [];\n        const seenClusters = new Set();\n        indices.forEach(idx => {\n            if (colors[idx]) {\n                const c = colors[idx];\n                const cluster = getConnectedGroup(idx, c).sort((a,b)=>a-b);\n                const key = cluster.join(',');\n                if (!seenClusters.has(key)) {\n                    seenClusters.add(key);\n                    clusters.push(cluster);\n                }\n            }\n        });\n        return clusters;\n    }\n\n    function getAdjacentIndices(index) {\n        const row = Math.floor(index \/ GRID_SIZE);\n        const col = index % GRID_SIZE;\n        const out = [];\n        if (row > 0) out.push(index - GRID_SIZE);\n        if (row < GRID_SIZE - 1) out.push(index + GRID_SIZE);\n        if (col > 0) out.push(index - 1);\n        if (col < GRID_SIZE - 1) out.push(index + 1);\n        return out;\n    }\n\n    function updateBoardUI() {\n        const cells = document.querySelectorAll('.game-cell');\n        cells.forEach((cell, i) => {\n            const c = colors[i];\n            cell.className = `game-cell color-${c}`;\n            cell.dataset.color = c;\n        });\n    }\n\n    function isGameCompleted(updatedColors = colors) {\n        const firstColor = updatedColors[0];\n        return updatedColors.every(c => c === firstColor);\n    }\n\n    \/\/ Instruksjons-popup\n    function toggleInstructions() {\n        const popup = document.getElementById('instructionsPopup');\n        const overlay= document.getElementById('overlay');\n        const isVisible= (popup.style.display === 'block');\n        popup.style.display   = isVisible ? 'none' : 'block';\n        overlay.style.display = isVisible ? 'none' : 'block';\n    }\n\n    function closeAllRecordModals() {\n        closeDailyModal();\n        const rankModal = document.getElementById('rankModal');\n        if (rankModal) rankModal.style.display = 'none';\n    }\n\n    \/\/ Modal: Dagens beste \/ Plassering\n    document.getElementById('dailyOverlay')?.addEventListener('click', closeAllRecordModals);\n    document.getElementById('dailyModalClose')?.addEventListener('click', closeAllRecordModals);\n    document.getElementById('rankModalClose')?.addEventListener('click', closeAllRecordModals);\n\n    if (dailyTopCard) {\n        dailyTopCard.addEventListener('click', openDailyModal);\n        dailyTopCard.addEventListener('keydown', (e) => {\n            if (e.key === 'Enter' || e.key === ' ') {\n                e.preventDefault();\n                openDailyModal();\n            }\n        });\n    }\n\n    if (dailyTopCrown) {\n        dailyTopCrown.addEventListener('click', openDailyModal);\n        dailyTopCrown.addEventListener('keydown', (e) => {\n            if (e.key === 'Enter' || e.key === ' ') {\n                e.preventDefault();\n                openDailyModal();\n            }\n        });\n    }\n\n    if (dailyTopRankBtn) {\n        dailyTopRankBtn.addEventListener('click', () => {\n            const r = Number(dailyTopRankBtn.dataset.rank || 0);\n            if (r >= 1 && r <= 5 && Number.isFinite(lastCompletedScore)) {\n                modalHighlightScore = lastCompletedScore;\n                openDailyModal();\n            } else {\n                openRankInfoModal();\n            }\n        });\n        dailyTopRankBtn.addEventListener('keydown', (e) => {\n            if (e.key === 'Enter' || e.key === ' ') {\n                e.preventDefault();\n                dailyTopRankBtn.click();\n            }\n        });\n    }\n\n    \/\/ Secure random\n    function getSecureRandomInt(max) {\n        const array = new Uint32Array(1);\n        window.crypto.getRandomValues(array);\n        return array[0] % max;\n    }\n    function shuffleArraySecure(array) {\n        for (let i = array.length - 1; i > 0; i--) {\n            const j = getSecureRandomInt(i + 1);\n            [array[i], array[j]] = [array[j], array[i]];\n        }\n        return array;\n    }\n\n    \/\/ Tastaturnavigasjon (for test: ? + start + ruter)\n    document.addEventListener('keydown', (event) => {\n        const isInstructionsOpen = (document.getElementById('instructionsPopup').style.display === 'block');\n        if (isInstructionsOpen) return;\n\n        const cells = document.querySelectorAll('.game-cell');\n\n        if (document.activeElement === instructionsButton) {\n            if (event.key === 'ArrowDown') {\n                event.preventDefault();\n                currentFocusIndex = 0;\n                updateCellFocus();\n            }\n        }\n        else if (document.activeElement === startButton) {\n            if (event.key === 'Enter' || event.key === ' ') {\n                event.preventDefault();\n                startButton.click();\n            }\n        }\n        else if (document.activeElement.classList.contains('game-cell')) {\n            if (['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(event.key)) {\n                event.preventDefault();\n                switch (event.key) {\n                    case 'ArrowUp':\n                        if (currentFocusIndex >= GRID_SIZE) currentFocusIndex -= GRID_SIZE;\n                        break;\n                    case 'ArrowDown':\n                        if (currentFocusIndex < TOTAL_CELLS - GRID_SIZE) currentFocusIndex += GRID_SIZE;\n                        break;\n                    case 'ArrowLeft':\n                        if (currentFocusIndex % GRID_SIZE !== 0) currentFocusIndex -= 1;\n                        break;\n                    case 'ArrowRight':\n                        if ((currentFocusIndex + 1) % GRID_SIZE !== 0) currentFocusIndex += 1;\n                        break;\n                }\n                updateCellFocus();\n            }\n            else if (event.key === 'Enter' || event.key === ' ') {\n                event.preventDefault();\n                if (cells[currentFocusIndex]) cells[currentFocusIndex].click();\n            }\n        }\n    });\n\n    function updateCellFocus() {\n        const cells = document.querySelectorAll('.game-cell');\n        if (cells[currentFocusIndex] && document.activeElement !== cells[currentFocusIndex]) {\n            cells[currentFocusIndex].focus();\n        }\n    }\n\n    \/\/ Start spillet ved innlasting\n    window.addEventListener('load', initGame);\n<\/script>\n<\/body>\n<\/html>\n\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Rainbow Puzzle er bra for hjernen. Her kan du teste og trene med en mini-versjon. Last ned appen n\u00e5r du er blitt god nok.<\/p>\n","protected":false},"author":2,"featured_media":33784,"comment_status":"closed","ping_status":"open","sticky":false,"template":"elementor_header_footer","format":"standard","meta":{"footnotes":""},"categories":[93,71,100,4,40,99,5,41,94,101,1],"tags":[],"post_folder":[],"class_list":["post-41748","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-eldre","category-gratis","category-grubling","category-grubliser","category-konkurranser","category-kunnskap","category-matematikk","category-nyheter","category-pop","category-spill","category-ukategorisert"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Rainbow Puzzle Light - Gruble.net<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/gruble.net\/rpl\/\" \/>\n<meta property=\"og:locale\" content=\"nb_NO\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Rainbow Puzzle Light - Gruble.net\" \/>\n<meta property=\"og:description\" content=\"Rainbow Puzzle er bra for hjernen. Her kan du teste og trene med en mini-versjon. Last ned appen n\u00e5r du er blitt god nok.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/gruble.net\/rpl\/\" \/>\n<meta property=\"og:site_name\" content=\"Gruble.net\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Gruble.net\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-24T11:19:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-09T06:10:05+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/gruble.net\/wp-content\/uploads\/rainbowpuzzlebanner-fb1.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1536\" \/>\n\t<meta property=\"og:image:height\" content=\"1024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Stig Hamstad\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Skrevet av\" \/>\n\t<meta name=\"twitter:data1\" content=\"Stig Hamstad\" \/>\n\t<meta name=\"twitter:label2\" content=\"Ansl. lesetid\" \/>\n\t<meta name=\"twitter:data2\" content=\"1 minutt\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/\"},\"author\":{\"name\":\"Stig Hamstad\",\"@id\":\"https:\\\/\\\/gruble.net\\\/#\\\/schema\\\/person\\\/713eaee8c60258dd79836b2ce3743569\"},\"headline\":\"Rainbow Puzzle Light\",\"datePublished\":\"2026-01-24T11:19:18+00:00\",\"dateModified\":\"2026-04-09T06:10:05+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/\"},\"wordCount\":88,\"publisher\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/gruble.net\\\/wp-content\\\/uploads\\\/rainbowpuzzlebanner-fb1.jpg\",\"articleSection\":[\"Eldre\",\"Gratis\",\"Grubling\",\"Grubliser\",\"Konkurranser\",\"Kunnskap\",\"Matematikk\",\"Nyheter\",\"Popul\u00e6rt\",\"Spill\"],\"inLanguage\":\"nb-NO\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/\",\"url\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/\",\"name\":\"Rainbow Puzzle Light - Gruble.net\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/gruble.net\\\/wp-content\\\/uploads\\\/rainbowpuzzlebanner-fb1.jpg\",\"datePublished\":\"2026-01-24T11:19:18+00:00\",\"dateModified\":\"2026-04-09T06:10:05+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#breadcrumb\"},\"inLanguage\":\"nb-NO\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/gruble.net\\\/rpl\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"nb-NO\",\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#primaryimage\",\"url\":\"https:\\\/\\\/gruble.net\\\/wp-content\\\/uploads\\\/rainbowpuzzlebanner-fb1.jpg\",\"contentUrl\":\"https:\\\/\\\/gruble.net\\\/wp-content\\\/uploads\\\/rainbowpuzzlebanner-fb1.jpg\",\"width\":1536,\"height\":1024,\"caption\":\"Fargerik kanin holder en tommel opp ved tittel \\\"Rainbow Puzzle\\\"\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/gruble.net\\\/rpl\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Hjem\",\"item\":\"https:\\\/\\\/gruble.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Rainbow Puzzle Light\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/gruble.net\\\/#website\",\"url\":\"https:\\\/\\\/gruble.net\\\/\",\"name\":\"Gruble.net\",\"description\":\"Spill deg til kunnskap og sett kunnskapen p\u00e5 spill!\",\"publisher\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/gruble.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"nb-NO\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/gruble.net\\\/#organization\",\"name\":\"Gruble.net\",\"url\":\"https:\\\/\\\/gruble.net\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"nb-NO\",\"@id\":\"https:\\\/\\\/gruble.net\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/gruble.net\\\/wp-content\\\/uploads\\\/logo-1-e1717058548444.png\",\"contentUrl\":\"https:\\\/\\\/gruble.net\\\/wp-content\\\/uploads\\\/logo-1-e1717058548444.png\",\"width\":483,\"height\":149,\"caption\":\"Gruble.net\"},\"image\":{\"@id\":\"https:\\\/\\\/gruble.net\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/Gruble.net\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/gruble.net\\\/#\\\/schema\\\/person\\\/713eaee8c60258dd79836b2ce3743569\",\"name\":\"Stig Hamstad\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"nb-NO\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/7e9060851f3f4d21de8e999d31b1d4646ce70f48ae4f93b9ee6a4a3121b6f40c?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/7e9060851f3f4d21de8e999d31b1d4646ce70f48ae4f93b9ee6a4a3121b6f40c?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/7e9060851f3f4d21de8e999d31b1d4646ce70f48ae4f93b9ee6a4a3121b6f40c?s=96&d=mm&r=g\",\"caption\":\"Stig Hamstad\"},\"sameAs\":[\"https:\\\/\\\/gruble.net\"],\"url\":\"https:\\\/\\\/gruble.net\\\/author\\\/stig-hamstad-2\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Rainbow Puzzle Light - Gruble.net","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/gruble.net\/rpl\/","og_locale":"nb_NO","og_type":"article","og_title":"Rainbow Puzzle Light - Gruble.net","og_description":"Rainbow Puzzle er bra for hjernen. Her kan du teste og trene med en mini-versjon. Last ned appen n\u00e5r du er blitt god nok.","og_url":"https:\/\/gruble.net\/rpl\/","og_site_name":"Gruble.net","article_publisher":"https:\/\/www.facebook.com\/Gruble.net","article_published_time":"2026-01-24T11:19:18+00:00","article_modified_time":"2026-04-09T06:10:05+00:00","og_image":[{"width":1536,"height":1024,"url":"https:\/\/gruble.net\/wp-content\/uploads\/rainbowpuzzlebanner-fb1.jpg","type":"image\/jpeg"}],"author":"Stig Hamstad","twitter_card":"summary_large_image","twitter_misc":{"Skrevet av":"Stig Hamstad","Ansl. lesetid":"1 minutt"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/gruble.net\/rpl\/#article","isPartOf":{"@id":"https:\/\/gruble.net\/rpl\/"},"author":{"name":"Stig Hamstad","@id":"https:\/\/gruble.net\/#\/schema\/person\/713eaee8c60258dd79836b2ce3743569"},"headline":"Rainbow Puzzle Light","datePublished":"2026-01-24T11:19:18+00:00","dateModified":"2026-04-09T06:10:05+00:00","mainEntityOfPage":{"@id":"https:\/\/gruble.net\/rpl\/"},"wordCount":88,"publisher":{"@id":"https:\/\/gruble.net\/#organization"},"image":{"@id":"https:\/\/gruble.net\/rpl\/#primaryimage"},"thumbnailUrl":"https:\/\/gruble.net\/wp-content\/uploads\/rainbowpuzzlebanner-fb1.jpg","articleSection":["Eldre","Gratis","Grubling","Grubliser","Konkurranser","Kunnskap","Matematikk","Nyheter","Popul\u00e6rt","Spill"],"inLanguage":"nb-NO"},{"@type":"WebPage","@id":"https:\/\/gruble.net\/rpl\/","url":"https:\/\/gruble.net\/rpl\/","name":"Rainbow Puzzle Light - Gruble.net","isPartOf":{"@id":"https:\/\/gruble.net\/#website"},"primaryImageOfPage":{"@id":"https:\/\/gruble.net\/rpl\/#primaryimage"},"image":{"@id":"https:\/\/gruble.net\/rpl\/#primaryimage"},"thumbnailUrl":"https:\/\/gruble.net\/wp-content\/uploads\/rainbowpuzzlebanner-fb1.jpg","datePublished":"2026-01-24T11:19:18+00:00","dateModified":"2026-04-09T06:10:05+00:00","breadcrumb":{"@id":"https:\/\/gruble.net\/rpl\/#breadcrumb"},"inLanguage":"nb-NO","potentialAction":[{"@type":"ReadAction","target":["https:\/\/gruble.net\/rpl\/"]}]},{"@type":"ImageObject","inLanguage":"nb-NO","@id":"https:\/\/gruble.net\/rpl\/#primaryimage","url":"https:\/\/gruble.net\/wp-content\/uploads\/rainbowpuzzlebanner-fb1.jpg","contentUrl":"https:\/\/gruble.net\/wp-content\/uploads\/rainbowpuzzlebanner-fb1.jpg","width":1536,"height":1024,"caption":"Fargerik kanin holder en tommel opp ved tittel \"Rainbow Puzzle\""},{"@type":"BreadcrumbList","@id":"https:\/\/gruble.net\/rpl\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Hjem","item":"https:\/\/gruble.net\/"},{"@type":"ListItem","position":2,"name":"Rainbow Puzzle Light"}]},{"@type":"WebSite","@id":"https:\/\/gruble.net\/#website","url":"https:\/\/gruble.net\/","name":"Gruble.net","description":"Spill deg til kunnskap og sett kunnskapen p\u00e5 spill!","publisher":{"@id":"https:\/\/gruble.net\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/gruble.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"nb-NO"},{"@type":"Organization","@id":"https:\/\/gruble.net\/#organization","name":"Gruble.net","url":"https:\/\/gruble.net\/","logo":{"@type":"ImageObject","inLanguage":"nb-NO","@id":"https:\/\/gruble.net\/#\/schema\/logo\/image\/","url":"https:\/\/gruble.net\/wp-content\/uploads\/logo-1-e1717058548444.png","contentUrl":"https:\/\/gruble.net\/wp-content\/uploads\/logo-1-e1717058548444.png","width":483,"height":149,"caption":"Gruble.net"},"image":{"@id":"https:\/\/gruble.net\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Gruble.net"]},{"@type":"Person","@id":"https:\/\/gruble.net\/#\/schema\/person\/713eaee8c60258dd79836b2ce3743569","name":"Stig Hamstad","image":{"@type":"ImageObject","inLanguage":"nb-NO","@id":"https:\/\/secure.gravatar.com\/avatar\/7e9060851f3f4d21de8e999d31b1d4646ce70f48ae4f93b9ee6a4a3121b6f40c?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/7e9060851f3f4d21de8e999d31b1d4646ce70f48ae4f93b9ee6a4a3121b6f40c?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/7e9060851f3f4d21de8e999d31b1d4646ce70f48ae4f93b9ee6a4a3121b6f40c?s=96&d=mm&r=g","caption":"Stig Hamstad"},"sameAs":["https:\/\/gruble.net"],"url":"https:\/\/gruble.net\/author\/stig-hamstad-2\/"}]}},"_links":{"self":[{"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/posts\/41748","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/comments?post=41748"}],"version-history":[{"count":67,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/posts\/41748\/revisions"}],"predecessor-version":[{"id":44374,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/posts\/41748\/revisions\/44374"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/media\/33784"}],"wp:attachment":[{"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/media?parent=41748"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/categories?post=41748"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/tags?post=41748"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/gruble.net\/wp-json\/wp\/v2\/post_folder?post=41748"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}