'use strict';
  const { user, isTeacher, isAdmin } = LS.initPage();
  window._simQuizAllowed = true; // default; overridden after permission fetch for students
  LS.showBoardIfAllowed();


  /* ════════════════════════════════
     SIM CATALOGUE  (defined after P_* consts below)
  ════════════════════════════════ */

  let _catFilter = 'all';
  var _disabledSimIds  = new Set();
  let _simModuleDisabled = false;

  function filterSims(cat, btn) {
    _catFilter = cat;
    document.querySelectorAll('.lab-filter').forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    renderSims();
  }

  function renderSims() {
    const base = _catFilter === 'all' ? SIMS : SIMS.filter(s => s.cat === _catFilter);
    const list = base.filter(s => !s.id || !_disabledSimIds.has(s.id));
    document.getElementById('sim-grid').innerHTML = list.map(s => `
      <div class="sim-card ${s.id ? '' : 'soon'}" ${s.id ? `onclick="openSim('${s.id}')"` : ''}>
        ${s.preview}
        <div class="sim-body">
          <div class="sim-cat ${s.cat}">${s.cat === 'math' ? '∑ Математика' : s.cat === 'chem' ? '<svg class="ic" viewBox="0 0 24 24"><path d="M9 3h6m-4.5 0v5.5l-4 7.5a1 1 0 0 0 .9 1.5h8.2a1 1 0 0 0 .9-1.5l-4-7.5V3"/></svg> Химия' : s.cat === 'bio' ? '<svg class="ic" viewBox="0 0 24 24"><path d="M2 15c6.667-6 13.333 0 20-6"/><path d="M9 22c1.798-2 2.518-4 2.807-6"/><path d="M15 2c-1.798 2-2.518 4-2.807 6"/><path d="m17 6-2.5-2.5M14 8 13 7M7 18l2.5 2.5M3.5 14.5l.5.5M20 9l.5.5M6.5 12.5l1 1M16.5 10.5l1 1M10 16l1.5 1.5"/></svg> Биология' : s.cat === 'game' ? '<svg class="ic" viewBox="0 0 24 24"><line x1="6" y1="12" x2="10" y2="12"/><line x1="8" y1="10" x2="8" y2="14"/><line x1="15" y1="13" x2="15.01" y2="13"/><line x1="18" y1="11" x2="18.01" y2="11"/><rect x="2" y="6" width="20" height="12" rx="2"/></svg> Игры' : LS.icon('zap',14) + ' Физика'}</div>
          <div class="sim-title">${s.title}</div>
          <div class="sim-desc">${s.desc}</div>
        </div>
        ${!s.id ? '<div class="sim-soon-badge">Скоро</div>' : ''}
      </div>`).join('');
    if (window.lucide) lucide.createIcons();
  }

  /* ════════════════════════════════
     CARD PREVIEW SVGs
  ════════════════════════════════ */
  function _grid(fg='rgba(255,255,255,0.06)') {
    return `<g stroke="${fg}" stroke-width="1">
      <line x1="45" y1="0" x2="45" y2="140"/><line x1="90" y1="0" x2="90" y2="140"/>
      <line x1="135" y1="0" x2="135" y2="140"/><line x1="180" y1="0" x2="180" y2="140"/>
      <line x1="225" y1="0" x2="225" y2="140"/>
      <line x1="0" y1="35" x2="270" y2="35"/><line x1="0" y1="70" x2="270" y2="70"/>
      <line x1="0" y1="105" x2="270" y2="105"/>
    </g>`;
  }
  function _axes() {
    return `<line x1="0" y1="70" x2="262" y2="70" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
    <line x1="135" y1="140" x2="135" y2="6" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
    <polygon points="265,70 258,67 258,73" fill="rgba(255,255,255,0.32)"/>
    <polygon points="135,4 132,11 138,11" fill="rgba(255,255,255,0.32)"/>`;
  }
  function _svg(body) {
    return `<svg class="sim-preview" viewBox="0 0 270 140" xmlns="http://www.w3.org/2000/svg">
      <rect width="270" height="140" fill="#0D0D1A"/>${body}</svg>`;
  }

  /* 1 — Graph */
  const P_GRAPH = _svg(`${_grid()}${_axes()}
    <path d="M 15,132 Q 135,20 255,132" stroke="#9B5DE5" stroke-width="2.5" fill="none"/>
    <path d="M 0,70 C 34,30 56,30 90,70 C 124,110 146,110 180,70 C 214,30 236,30 270,70"
          stroke="#06D6E0" stroke-width="2" fill="none" opacity="0.75"/>`);

  /* 2 — Transform: three shifted/scaled sines */
  const P_TRANSFORM = _svg(`${_grid()}${_axes()}
    <path d="M 0,70 C 34,30 56,30 90,70 C 124,110 146,110 180,70 C 214,30 236,30 270,70"
          stroke="#9B5DE5" stroke-width="2" fill="none" opacity="0.9"/>
    <path d="M 0,53 C 22,24 42,24 67,53 C 92,82 112,82 135,53 C 158,24 178,24 202,53 C 227,82 248,82 270,53"
          stroke="#06D6E0" stroke-width="2" fill="none" opacity="0.65"/>
    <path d="M 0,85 C 45,36 80,36 135,85 C 190,134 225,134 270,85"
          stroke="#F15BB5" stroke-width="2" fill="none" opacity="0.55"/>`);

  /* 3 — Triangle geometry */
  const P_TRIANGLE = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <polygon points="60,115 210,115 135,25" fill="rgba(155,93,229,0.1)" stroke="#9B5DE5" stroke-width="2"/>
    <line x1="60" y1="115" x2="173" y2="70" stroke="rgba(6,214,224,0.5)" stroke-width="1.3" stroke-dasharray="4,3"/>
    <line x1="210" y1="115" x2="98" y2="70" stroke="rgba(6,214,224,0.5)" stroke-width="1.3" stroke-dasharray="4,3"/>
    <line x1="135" y1="25" x2="135" y2="115" stroke="rgba(6,214,224,0.5)" stroke-width="1.3" stroke-dasharray="4,3"/>
    <circle cx="135" cy="78" r="3" fill="#06D6E0"/>
    <rect x="131" y="111" width="8" height="8" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="1.2"/>`);

  /* 4 — Inscribed/circumscribed circles */
  const P_CIRCLES = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <polygon points="80,118 190,118 135,22" fill="rgba(155,93,229,0.08)" stroke="#9B5DE5" stroke-width="1.8"/>
    <circle cx="135" cy="85" r="33" fill="none" stroke="#06D6E0" stroke-width="1.8" stroke-dasharray="5,3" opacity="0.7"/>
    <circle cx="135" cy="55" r="52" fill="none" stroke="#F15BB5" stroke-width="1.5" stroke-dasharray="5,3" opacity="0.5"/>
    <circle cx="135" cy="85" r="3" fill="#06D6E0" opacity="0.8"/>
    <circle cx="135" cy="55" r="3" fill="#F15BB5" opacity="0.8"/>`);

  /* 5 — Quadratic roots: parabola crossing x-axis */
  const P_QUADRATIC = _svg(`${_grid()}${_axes()}
    <path d="M 55,125 Q 135,8 215,125" stroke="#9B5DE5" stroke-width="2.5" fill="none"/>
    <circle cx="85" cy="70" r="5" fill="#F15BB5" stroke="#fff" stroke-width="1.5"/>
    <circle cx="185" cy="70" r="5" fill="#F15BB5" stroke="#fff" stroke-width="1.5"/>
    <line x1="85" y1="68" x2="85" y2="125" stroke="rgba(241,91,181,0.35)" stroke-width="1" stroke-dasharray="3,3"/>
    <line x1="185" y1="68" x2="185" y2="125" stroke="rgba(241,91,181,0.35)" stroke-width="1" stroke-dasharray="3,3"/>
    <text x="135" y="136" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">D = b²− 4ac</text>`);

  /* 6 — 3D geometry: isometric cube */
  const P_3D = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <polygon points="135,20 210,58 210,115 135,77" fill="rgba(155,93,229,0.15)" stroke="#9B5DE5" stroke-width="1.8"/>
    <polygon points="135,20 60,58 60,115 135,77" fill="rgba(155,93,229,0.08)" stroke="#9B5DE5" stroke-width="1.8"/>
    <polygon points="60,58 135,20 210,58 135,96" fill="rgba(155,93,229,0.22)" stroke="#9B5DE5" stroke-width="1.8"/>
    <line x1="135" y1="77" x2="135" y2="96" stroke="#9B5DE5" stroke-width="1.8"/>
    <text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">V = a³</text>`);

  /* 7 — Probability: histogram bars */
  const P_PROB = _svg(`${_grid()}
    <line x1="30" y1="15" x2="30" y2="118" stroke="rgba(255,255,255,0.35)" stroke-width="1.5"/>
    <line x1="28" y1="118" x2="255" y2="118" stroke="rgba(255,255,255,0.35)" stroke-width="1.5"/>
    <rect x="38"  y="90"  width="24" height="28" fill="rgba(155,93,229,0.6)" rx="2"/>
    <rect x="68"  y="72"  width="24" height="46" fill="rgba(155,93,229,0.7)" rx="2"/>
    <rect x="98"  y="44"  width="24" height="74" fill="rgba(155,93,229,0.85)" rx="2"/>
    <rect x="128" y="32"  width="24" height="86" fill="#9B5DE5" rx="2"/>
    <rect x="158" y="50"  width="24" height="68" fill="rgba(155,93,229,0.8)" rx="2"/>
    <rect x="188" y="76"  width="24" height="42" fill="rgba(155,93,229,0.65)" rx="2"/>
    <rect x="218" y="96"  width="24" height="22" fill="rgba(155,93,229,0.5)" rx="2"/>`);

  /* 8 — Normal distribution: bell curve */
  const P_NORMAL = _svg(`${_grid()}
    <line x1="10" y1="118" x2="260" y2="118" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
    <path d="M 10,116 C 50,115 80,110 100,90 C 115,72 125,35 135,22 C 145,35 155,72 170,90 C 190,110 220,115 260,116"
          stroke="#9B5DE5" stroke-width="2.5" fill="none"/>
    <path d="M 100,90 C 115,72 125,35 135,22 C 145,35 155,72 170,90 L 170,118 L 100,118 Z"
          fill="rgba(155,93,229,0.15)"/>
    <line x1="135" y1="22" x2="135" y2="118" stroke="rgba(255,255,255,0.2)" stroke-width="1" stroke-dasharray="3,3"/>
    <text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">μ = 0, σ = 1</text>`);

  /* 8b — Trig circle */
  const P_TRIGCIRCLE = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="30" y1="70" x2="240" y2="70" stroke="rgba(255,255,255,0.25)" stroke-width="1.2"/>
    <line x1="135" y1="8" x2="135" y2="132" stroke="rgba(255,255,255,0.25)" stroke-width="1.2"/>
    <circle cx="135" cy="70" r="52" fill="none" stroke="rgba(255,255,255,0.18)" stroke-width="1.8"/>
    <line x1="135" y1="70" x2="172" y2="33" stroke="rgba(255,255,255,0.45)" stroke-width="1.3"/>
    <line x1="135" y1="70" x2="172" y2="70" stroke="#06D6E0" stroke-width="2.5"/>
    <line x1="172" y1="70" x2="172" y2="33" stroke="#EF476F" stroke-width="2.5"/>
    <circle cx="172" cy="33" r="5" fill="#9B5DE5"/>
    <path d="M 148,70 A 13,13 0 0,0 144,60" stroke="rgba(155,93,229,0.6)" stroke-width="1.5" fill="none"/>
    <text x="135" y="136" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">sin · cos · tg · ctg</text>`);

  /* 9 — Projectile motion */
  const P_PROJECTILE = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="15" y1="118" x2="255" y2="118" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
    <path d="M 20,118 Q 135,18 250,118" stroke="#06D6E0" stroke-width="2.5" fill="none"/>
    <circle cx="20" cy="118" r="5" fill="#06D6E0"/>
    <line x1="20" y1="118" x2="52" y2="80" stroke="rgba(6,214,224,0.8)" stroke-width="1.5"
          marker-end="url(#arr)"/>
    <defs><marker id="arr" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
      <path d="M0,0 L6,3 L0,6 Z" fill="#06D6E0"/>
    </marker></defs>
    <line x1="135" y1="18" x2="135" y2="118" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="3,3"/>
    <text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">x = v₀cos(α)·t</text>`);

  /* 10 — Pendulum */
  const P_PENDULUM = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="135" y1="15" x2="165" y2="95" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
    <circle cx="165" cy="100" r="12" fill="rgba(6,214,224,0.25)" stroke="#06D6E0" stroke-width="2"/>
    <line x1="135" y1="15" x2="95" y2="95" stroke="rgba(255,255,255,0.2)" stroke-width="1.5" stroke-dasharray="4,3"/>
    <circle cx="95" cy="100" r="12" fill="none" stroke="rgba(6,214,224,0.3)" stroke-width="1.5" stroke-dasharray="3,3"/>
    <path d="M 110,60 A 55,55 0 0 1 160,60" fill="none" stroke="rgba(6,214,224,0.4)" stroke-width="1.2" stroke-dasharray="3,3"/>
    <circle cx="135" cy="15" r="4" fill="rgba(255,255,255,0.5)"/>
    <text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">T = 2π√(l/g)</text>`);

  /* 11 — Collision */
  const P_COLLISION = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="15" y1="70" x2="255" y2="70" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
    <circle cx="70" cy="70" r="28" fill="rgba(6,214,224,0.15)" stroke="#06D6E0" stroke-width="2"/>
    <text x="70" y="75" font-size="11" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">m₁</text>
    <line x1="100" y1="70" x2="120" y2="70" stroke="#06D6E0" stroke-width="2" marker-end="url(#a2)"/>
    <circle cx="195" cy="70" r="20" fill="rgba(241,91,181,0.15)" stroke="#F15BB5" stroke-width="2"/>
    <text x="195" y="75" font-size="11" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">m₂</text>
    <line x1="175" y1="70" x2="155" y2="70" stroke="#F15BB5" stroke-width="2" marker-end="url(#a3)"/>
    <defs>
      <marker id="a2" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#06D6E0"/></marker>
      <marker id="a3" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#F15BB5"/></marker>
    </defs>`);


  /* 13 — Electric circuit */
  const P_CIRCUIT = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <rect x="30" y="25" width="210" height="90" fill="none" stroke="rgba(255,255,255,0.25)" stroke-width="1.5" rx="4"/>
    <line x1="30" y1="70" x2="70" y2="70" stroke="#06D6E0" stroke-width="2"/>
    <rect x="70" y="58" width="36" height="24" fill="rgba(6,214,224,0.15)" stroke="#06D6E0" stroke-width="1.8" rx="3"/>
    <text x="88" y="74" font-size="10" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">R₁</text>
    <line x1="106" y1="70" x2="130" y2="70" stroke="#06D6E0" stroke-width="2"/>
    <rect x="130" y="58" width="36" height="24" fill="rgba(6,214,224,0.15)" stroke="#06D6E0" stroke-width="1.8" rx="3"/>
    <text x="148" y="74" font-size="10" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">R₂</text>
    <line x1="166" y1="70" x2="190" y2="70" stroke="#06D6E0" stroke-width="2"/>
    <rect x="190" y="56" width="18" height="28" fill="rgba(241,91,181,0.15)" stroke="#F15BB5" stroke-width="1.8" rx="3"/>
    <line x1="208" y1="70" x2="240" y2="70" stroke="#06D6E0" stroke-width="2"/>
    <text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">I = U/R</text>`);

  /* 14 — Magnetic field */
  const P_MAGNETIC = _svg(`
    <rect width="270" height="140" fill="#05050F"/>
    ${_grid('rgba(155,93,229,0.06)')}
    <defs>
      <radialGradient id="mg1" cx="38%" cy="50%"><stop offset="0%" stop-color="#06D6E0" stop-opacity=".55"/><stop offset="100%" stop-color="#06D6E0" stop-opacity="0"/></radialGradient>
      <radialGradient id="mg2" cx="62%" cy="50%"><stop offset="0%" stop-color="#F15BB5" stop-opacity=".55"/><stop offset="100%" stop-color="#F15BB5" stop-opacity="0"/></radialGradient>
    </defs>
    <rect width="270" height="140" fill="url(#mg1)" opacity=".7"/>
    <rect width="270" height="140" fill="url(#mg2)" opacity=".7"/>
    <ellipse cx="95" cy="70" rx="45" ry="45" fill="none" stroke="#06D6E0" stroke-width="1.4" stroke-dasharray="5,3" opacity=".6"/>
    <ellipse cx="95" cy="70" rx="70" ry="60" fill="none" stroke="#06D6E0" stroke-width="1" stroke-dasharray="4,4" opacity=".3"/>
    <ellipse cx="175" cy="70" rx="45" ry="45" fill="none" stroke="#F15BB5" stroke-width="1.4" stroke-dasharray="5,3" opacity=".6"/>
    <ellipse cx="175" cy="70" rx="70" ry="60" fill="none" stroke="#F15BB5" stroke-width="1" stroke-dasharray="4,4" opacity=".3"/>
    <path d="M95,30 C160,30 110,70 175,70" stroke="rgba(255,255,255,0.25)" stroke-width="1.2" fill="none"/>
    <path d="M95,110 C160,110 110,70 175,70" stroke="rgba(255,255,255,0.25)" stroke-width="1.2" fill="none"/>
    <circle cx="95" cy="70" r="11" fill="rgba(5,5,20,0.9)" stroke="#06D6E0" stroke-width="2.2"/>
    <circle cx="95" cy="70" r="4" fill="#06D6E0"/>
    <circle cx="175" cy="70" r="11" fill="rgba(5,5,20,0.9)" stroke="#F15BB5" stroke-width="2.2"/>
    <line x1="170" y1="65" x2="180" y2="75" stroke="#F15BB5" stroke-width="2"/>
    <line x1="180" y1="65" x2="170" y2="75" stroke="#F15BB5" stroke-width="2"/>
    <text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">B = μ₀I / 2πr</text>`);

  /* 14 — Electric field lines */
  const P_FIELD = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <circle cx="135" cy="70" r="10" fill="rgba(155,93,229,0.3)" stroke="#9B5DE5" stroke-width="2"/>
    <text x="135" y="74" font-size="10" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="800">+</text>
    <g stroke="#9B5DE5" stroke-width="1.3" fill="none" opacity="0.6">
      <path d="M135,60 L135,20"/><path d="M135,80 L135,120"/>
      <path d="M125,63 L95,38"/><path d="M145,63 L175,38"/>
      <path d="M125,77 L95,102"/><path d="M145,77 L175,102"/>
      <path d="M122,70 L80,70"/><path d="M148,70 L190,70"/>
      <path d="M125,64 L102,42"/><path d="M145,64 L168,42"/>
      <path d="M125,76 L102,98"/><path d="M145,76 L168,98"/>
    </g>
    <circle cx="135" cy="20" r="2" fill="#9B5DE5" opacity="0.5"/>
    <circle cx="135" cy="120" r="2" fill="#9B5DE5" opacity="0.5"/>
    <circle cx="80" cy="70" r="2" fill="#9B5DE5" opacity="0.5"/>
    <circle cx="190" cy="70" r="2" fill="#9B5DE5" opacity="0.5"/>`);

  /* 15 — Thin lens */
  const P_LENS = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="10" y1="70" x2="260" y2="70" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
    <path d="M 135,20 Q 155,70 135,120 Q 115,70 135,20" fill="rgba(6,214,224,0.12)" stroke="#06D6E0" stroke-width="2"/>
    <line x1="30" y1="45" x2="135" y2="45" stroke="#9B5DE5" stroke-width="1.8"/>
    <line x1="135" y1="45" x2="230" y2="90" stroke="#9B5DE5" stroke-width="1.8"/>
    <line x1="30" y1="70" x2="230" y2="70" stroke="#06D6E0" stroke-width="1.5" stroke-dasharray="3,3" opacity="0.5"/>
    <line x1="30" y1="95" x2="135" y2="95" stroke="#F15BB5" stroke-width="1.8"/>
    <line x1="135" y1="95" x2="230" y2="55" stroke="#F15BB5" stroke-width="1.8"/>
    <circle cx="30" cy="70" r="5" fill="#9B5DE5" opacity="0.7"/>
    <line x1="30" y1="40" x2="30" y2="100" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>`);

  /* 16 — Refraction */
  const P_REFRACTION = _svg(`
    <rect width="270" height="70" fill="#0D0D1A"/>
    <rect y="70" width="270" height="70" fill="rgba(6,214,224,0.07)"/>
    <line x1="0" y1="70" x2="270" y2="70" stroke="rgba(6,214,224,0.35)" stroke-width="1.5" stroke-dasharray="6,4"/>
    <line x1="135" y1="10" x2="135" y2="130" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="3,3"/>
    <line x1="60" y1="15" x2="135" y2="70" stroke="#9B5DE5" stroke-width="2.5"/>
    <polygon points="135,70 127,50 143,50" fill="#9B5DE5" opacity="0.7"/>
    <line x1="135" y1="70" x2="195" y2="125" stroke="#06D6E0" stroke-width="2.5"/>
    <polygon points="195,125 183,112 196,107" fill="#06D6E0" opacity="0.7"/>
    <path d="M 135,50 A 22,22 0 0 0 118,70" fill="none" stroke="rgba(155,93,229,0.5)" stroke-width="1.2"/>
    <path d="M 135,90 A 28,28 0 0 1 157,70" fill="none" stroke="rgba(6,214,224,0.5)" stroke-width="1.2"/>
    <text x="118" y="63" font-size="9" fill="rgba(155,93,229,0.8)" font-family="Manrope,sans-serif">α</text>
    <text x="152" y="87" font-size="9" fill="rgba(6,214,224,0.8)" font-family="Manrope,sans-serif">β</text>
    <text x="135" y="136" font-size="9" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">n₁sinα = n₂sinβ</text>`);

  /* 17 — Mirrors */
  const P_MIRROR = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="10" y1="70" x2="260" y2="70" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
    <path d="M 200,15 Q 184,70 200,125" fill="none" stroke="#06D6E0" stroke-width="2.5"/>
    <line x1="200" y1="20" x2="210" y2="30" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
    <line x1="200" y1="45" x2="210" y2="55" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
    <line x1="200" y1="70" x2="210" y2="80" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
    <line x1="200" y1="95" x2="210" y2="105" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
    <line x1="200" y1="118" x2="210" y2="128" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
    <circle cx="130" cy="70" r="4" fill="#06D6E0" opacity="0.8"/>
    <text x="130" y="84" text-anchor="middle" font-size="9" fill="#06D6E0" font-family="Manrope,sans-serif">F</text>
    <line x1="50" y1="70" x2="50" y2="30" stroke="#9B5DE5" stroke-width="2"/>
    <polygon points="50,30 44,42 56,42" fill="#9B5DE5"/>
    <line x1="50" y1="30" x2="200" y2="30" stroke="#06D6E0" stroke-width="1.5"/>
    <line x1="200" y1="30" x2="70" y2="105" stroke="#06D6E0" stroke-width="1.5"/>
    <line x1="50" y1="30" x2="200" y2="70" stroke="#7BF5A4" stroke-width="1.5"/>
    <line x1="200" y1="70" x2="70" y2="105" stroke="#7BF5A4" stroke-width="1.5"/>
    <line x1="70" y1="70" x2="70" y2="105" stroke="#EF476F" stroke-width="2"/>
    <polygon points="70,105 64,93 76,93" fill="#EF476F"/>`);

  /* 18 — Isoprocesses */
  const P_ISOPROCESS = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="30" y1="10" x2="30" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <line x1="30" y1="125" x2="265" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <path d="M 50,20 Q 140,60 240,110" fill="none" stroke="#EF476F" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
    <path d="M 50,20 Q 130,80 230,118" fill="none" stroke="#FFD166" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
    <line x1="50" y1="20" x2="50" y2="118" stroke="#06D6E0" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
    <line x1="50" y1="20" x2="230" y2="20" stroke="#7BF5A4" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
    <path d="M 50,20 Q 120,55 220,108" fill="none" stroke="#EF476F" stroke-width="2.5"/>
    <circle cx="50" cy="20" r="5" fill="#9B5DE5"/>
    <circle cx="220" cy="108" r="5" fill="#EF476F"/>
    <text x="240" y="113" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif">2</text>
    <text x="40" y="16" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif">1</text>
    <text x="255" y="128" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">V</text>
    <text x="18" y="12" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">P</text>`);

  /* ── Chemistry / Molecular Physics previews ── */
  const P_GAS = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <rect x="6" y="6" width="258" height="128" rx="4" fill="none" stroke="rgba(155,93,229,0.4)" stroke-width="2"/>
    ${[
      [40,30,'#4CC9F0'],[70,80,'#7BF5A4'],[110,25,'#EF476F'],[150,60,'#FFD166'],[190,30,'#4CC9F0'],
      [220,90,'#EF476F'],[55,110,'#7BF5A4'],[95,65,'#4CC9F0'],[130,110,'#EF476F'],[170,40,'#FFD166'],
      [210,115,'#4CC9F0'],[240,55,'#7BF5A4'],[30,70,'#FFD166'],[80,120,'#EF476F'],[165,95,'#4CC9F0']
    ].map(([x,y,c])=>`<circle cx="${x}" cy="${y}" r="5" fill="${c}" opacity="0.85"/>`).join('')}
    <rect x="6" y="105" width="258" height="29" rx="3" fill="rgba(0,0,0,0.55)"/>
    <rect x="18" y="112" width="40" height="12" rx="2" fill="rgba(155,93,229,0.25)"/>
    <rect x="18" y="112" width="14" height="12" rx="2" fill="rgba(155,93,229,0.6)"/>
    <rect x="70" y="112" width="40" height="12" rx="2" fill="rgba(155,93,229,0.25)"/>
    <rect x="70" y="112" width="22" height="12" rx="2" fill="#7BF5A4" opacity="0.7"/>
    <rect x="122" y="112" width="40" height="12" rx="2" fill="rgba(155,93,229,0.25)"/>
    <rect x="122" y="112" width="30" height="12" rx="2" fill="#EF476F" opacity="0.7"/>
    <text x="202" y="121" font-size="8" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">PV=nRT</text>`);


  /* ── Законы Ньютона ── */
  const P_NEWTON = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <line x1="0" y1="105" x2="270" y2="105" stroke="rgba(255,255,255,0.22)" stroke-width="2"/>
    <rect x="80" y="75" width="50" height="30" rx="5" fill="rgba(6,214,224,0.18)" stroke="#06D6E0" stroke-width="2"/>
    <line x1="130" y1="90" x2="175" y2="90" stroke="#EF476F" stroke-width="2.5" marker-end="url(#na)"/>
    <defs><marker id="na" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#EF476F"/></marker></defs>
    <text x="153" y="84" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif" font-weight="700">F</text>
    <line x1="105" y1="75" x2="105" y2="55" stroke="rgba(255,255,255,0.3)" stroke-width="1.2" stroke-dasharray="3,2"/>
    <line x1="105" y1="55" x2="175" y2="55" stroke="rgba(255,255,255,0.18)" stroke-width="1" stroke-dasharray="3,3"/>
    <circle cx="65" cy="90" r="12" fill="rgba(155,93,229,0.2)" stroke="#9B5DE5" stroke-width="1.8"/>
    <text x="65" y="94" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">m₂</text>
    <line x1="195" y1="90" x2="220" y2="90" stroke="#9B5DE5" stroke-width="2" stroke-dasharray="4,3" marker-end="url(#nb)"/>
    <defs><marker id="nb" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#9B5DE5"/></marker></defs>
    <text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">a = F/m · III законы Ньютона</text>`);

  /* ── Песочница сил ── */
  const P_SANDBOX = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid('rgba(255,255,255,0.03)')}
    <line x1="0" y1="115" x2="270" y2="115" stroke="rgba(155,93,229,0.35)" stroke-width="2"/>
    <rect x="55" y="82" width="44" height="33" rx="6" fill="rgba(239,71,111,0.22)" stroke="#EF476F" stroke-width="1.8"/>
    <text x="77" y="103" font-size="8" fill="#fff" text-anchor="middle" font-family="monospace" font-weight="700">5кг</text>
    <circle cx="180" cy="88" r="18" fill="rgba(76,201,240,0.18)" stroke="#4CC9F0" stroke-width="1.8"/>
    <text x="180" y="92" font-size="8" fill="#fff" text-anchor="middle" font-family="monospace" font-weight="700">8кг</text>
    <line x1="99" y1="95" x2="140" y2="95" stroke="#FFD166" stroke-width="2.2" marker-end="url(#sa)"/>
    <line x1="198" y1="88" x2="238" y2="68" stroke="#7BF5A4" stroke-width="2.2" marker-end="url(#sb)"/>
    <defs>
      <marker id="sa" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#FFD166"/></marker>
      <marker id="sb" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7BF5A4"/></marker>
    </defs>
    <text x="120" y="87" font-size="8" fill="#FFD166" font-family="monospace">F₁</text>
    <text x="225" y="63" font-size="8" fill="#7BF5A4" font-family="monospace">F₂</text>
    <text x="135" y="133" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Песочница сил · F = ma</text>`);

  const P_HYDRO = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid('rgba(255,255,255,0.03)')}
    <!-- left tall beaker with water + submerged body -->
    <path d="M 38,30 L 38,118 Q 38,124 44,124 L 96,124 Q 102,124 102,118 L 102,30" fill="none" stroke="rgba(255,255,255,0.45)" stroke-width="1.6"/>
    <rect x="40" y="56" width="60" height="66" rx="2" fill="rgba(41,121,255,0.32)"/>
    <path d="M 40,56 Q 55,52 70,56 T 100,56 L 100,60 L 40,60 Z" fill="rgba(76,201,240,0.55)"/>
    <text x="70" y="48" font-size="7.5" fill="rgba(76,201,240,0.9)" text-anchor="middle" font-family="monospace">P = ρgh</text>
    <!-- submerged cube + buoyancy arrow up -->
    <rect x="55" y="80" width="28" height="22" rx="3" fill="rgba(255,209,102,0.55)" stroke="#FFD166" stroke-width="1.4"/>
    <line x1="69" y1="80" x2="69" y2="62" stroke="#7BF5A4" stroke-width="2.2" marker-end="url(#hb)"/>
    <text x="79" y="72" font-size="7.5" fill="#7BF5A4" font-family="monospace">F_A</text>
    <!-- U-tube manometer right -->
    <path d="M 150,40 L 150,108 Q 150,116 158,116 L 198,116 Q 206,116 206,108 L 206,40" fill="none" stroke="rgba(255,255,255,0.45)" stroke-width="1.6"/>
    <path d="M 152,80 L 168,80 L 168,114 L 188,114 L 188,68 L 204,68 L 204,80" fill="none" stroke="none"/>
    <rect x="152" y="80" width="16" height="34" fill="rgba(76,201,240,0.55)"/>
    <rect x="188" y="68" width="16" height="46" fill="rgba(76,201,240,0.55)"/>
    <line x1="168" y1="80" x2="188" y2="68" stroke="rgba(76,201,240,0.55)" stroke-width="6" stroke-linecap="round"/>
    <line x1="172" y1="80" x2="184" y2="80" stroke="rgba(255,209,102,0.7)" stroke-width="1" stroke-dasharray="2 2"/>
    <line x1="192" y1="68" x2="204" y2="68" stroke="rgba(255,209,102,0.7)" stroke-width="1" stroke-dasharray="2 2"/>
    <text x="178" y="36" font-size="7.5" fill="rgba(76,201,240,0.9)" text-anchor="middle" font-family="monospace">Δh</text>
    <line x1="178" y1="38" x2="178" y2="66" stroke="rgba(255,209,102,0.85)" stroke-width="1.2" marker-end="url(#hb)"/>
    <!-- communicating vessels (small inline icon) -->
    <path d="M 224,80 L 224,116 L 258,116 L 258,52" fill="none" stroke="rgba(255,255,255,0.45)" stroke-width="1.4"/>
    <rect x="226" y="100" width="6" height="14" fill="rgba(76,201,240,0.55)"/>
    <rect x="250" y="100" width="6" height="14" fill="rgba(76,201,240,0.55)"/>
    <line x1="232" y1="114" x2="250" y2="114" stroke="rgba(76,201,240,0.55)" stroke-width="3" stroke-linecap="round"/>
    <defs>
      <marker id="hb" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7BF5A4"/></marker>
    </defs>
    <text x="135" y="135" font-size="7.5" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">Архимед · Паскаль · капиллярность</text>`);

  /* ── coming soon chem previews (simple) ── */
  const P_KINETICS = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid()}
    <path d="M 20,120 C 60,90 100,50 140,35 S 220,28 260,26" fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round"/>
    <path d="M 20,30 C 60,55 100,100 140,112 S 220,118 260,120" fill="none" stroke="#EF476F" stroke-width="2" stroke-linecap="round"/>
    <circle cx="140" cy="35" r="4" fill="#34d399"/>
    <circle cx="140" cy="112" r="4" fill="#EF476F"/>
    <text x="20" y="18" font-size="9" fill="rgba(52,211,153,0.8)" font-family="Manrope,sans-serif">[C] продукт</text>
    <text x="180" y="130" font-size="9" fill="rgba(239,71,111,0.8)" font-family="Manrope,sans-serif">[A] реагент</text>`);

  const P_EQUILIBRIUM = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <text x="135" y="30" font-size="11" fill="rgba(255,255,255,0.7)" text-anchor="middle" font-family="Manrope,sans-serif">A + B ⇌ C + D</text>
    <rect x="30" y="50" width="60" height="70" rx="4" fill="rgba(155,93,229,0.15)" stroke="rgba(155,93,229,0.4)" stroke-width="1.5"/>
    <rect x="100" y="75" width="70" height="45" rx="4" fill="rgba(6,214,224,0.12)" stroke="rgba(6,214,224,0.35)" stroke-width="1.5"/>
    <rect x="180" y="55" width="60" height="65" rx="4" fill="rgba(241,91,181,0.12)" stroke="rgba(241,91,181,0.35)" stroke-width="1.5"/>
    <text x="60" y="90" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif">A,B</text>
    <text x="135" y="101" font-size="8" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif">K</text>
    <text x="210" y="91" font-size="9" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif">C,D</text>`);

  const P_ELECTROLYSIS = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <rect x="20" y="30" width="230" height="90" rx="6" fill="rgba(6,214,224,0.07)" stroke="rgba(6,214,224,0.2)" stroke-width="1.5"/>
    <rect x="50" y="20" width="12" height="80" rx="3" fill="#9B5DE5" opacity="0.8"/>
    <rect x="208" y="20" width="12" height="80" rx="3" fill="#EF476F" opacity="0.8"/>
    ${[55,58,61,64,67,70].map(x=>`<circle cx="${x}" cy="${110-Math.random()*20|0}" r="2.5" fill="rgba(155,93,229,0.6)"/>`).join('')}
    ${[210,214,218,222,226].map(x=>`<circle cx="${x}" cy="${100-Math.random()*15|0}" r="2.5" fill="rgba(239,71,111,0.6)"/>`).join('')}
    <text x="56" y="15" font-size="8" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif">−</text>
    <text x="214" y="15" font-size="8" fill="#EF476F" text-anchor="middle" font-family="Manrope,sans-serif">+</text>`);

  const P_BOHR = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <circle cx="135" cy="70" r="8" fill="#FFD166" opacity="0.9"/>
    <ellipse cx="135" cy="70" rx="30" ry="10" fill="none" stroke="rgba(155,93,229,0.4)" stroke-width="1.5"/>
    <ellipse cx="135" cy="70" rx="55" ry="18" fill="none" stroke="rgba(6,214,224,0.3)" stroke-width="1.5"/>
    <ellipse cx="135" cy="70" rx="80" ry="27" fill="none" stroke="rgba(241,91,181,0.25)" stroke-width="1.5"/>
    <circle cx="165" cy="70" r="4" fill="#9B5DE5"/>
    <circle cx="90" cy="70" r="4" fill="#06D6E0"/>
    <circle cx="215" cy="70" r="4" fill="#F15BB5"/>
    <line x1="165" y1="60" x2="190" y2="35" stroke="rgba(255,214,0,0.6)" stroke-width="1.5" stroke-dasharray="3,2"/>
    <circle cx="190" cy="35" r="3" fill="#FFD166" opacity="0.8"/>`);

  const P_ORBITALS = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <ellipse cx="135" cy="70" rx="60" ry="25" fill="rgba(155,93,229,0.15)" stroke="rgba(155,93,229,0.5)" stroke-width="1.5"/>
    <ellipse cx="135" cy="70" rx="25" ry="60" fill="rgba(6,214,224,0.1)" stroke="rgba(6,214,224,0.4)" stroke-width="1.5"/>
    <circle cx="135" cy="70" r="6" fill="#FFD166" opacity="0.9"/>
    <circle cx="95" cy="70" r="5" fill="#9B5DE5" opacity="0.8"/>
    <circle cx="175" cy="70" r="5" fill="#9B5DE5" opacity="0.8"/>`);

  const P_PH = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid()}
    <path d="M 20,110 L 60,108 L 100,105 L 120,90 L 130,30 L 140,75 L 180,40 L 220,35 L 260,32"
      fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round"/>
    <line x1="20" y1="70" x2="260" y2="70" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="4,3"/>
    <text x="20" y="18" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">pH</text>
    <text x="240" y="130" font-size="9" fill="rgba(255,255,255,0.4)" font-family="Manrope,sans-serif">V</text>`);

  const P_CHEMSANDBOX = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid()}
    <rect x="85" y="20" width="100" height="70" rx="8" fill="none" stroke="rgba(75,205,155,0.4)" stroke-width="1.5"/>
    <rect x="88" y="55" width="94" height="32" rx="4" fill="rgba(75,205,155,0.15)"/>
    <circle cx="110" cy="71" r="4" fill="rgba(255,200,60,0.5)"/>
    <circle cx="130" cy="68" r="3" fill="rgba(255,255,255,0.3)"/>
    <circle cx="150" cy="73" r="3.5" fill="rgba(90,200,235,0.4)"/>
    <text x="135" y="105" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" text-anchor="middle">A + B <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> C + D</text>
    <rect x="40" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
    <rect x="75" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
    <rect x="110" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
    <rect x="145" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
    <rect x="180" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
    <circle cx="54" cy="121" r="3" fill="#78D278"/><circle cx="89" cy="121" r="3" fill="#7BF5A4"/>
    <circle cx="124" cy="121" r="3" fill="#4CC9F0"/><circle cx="159" cy="121" r="3" fill="#9BB8CC"/>
    <circle cx="194" cy="121" r="3" fill="#FFD166"/>`);

  const P_STOICHIOMETRY = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid()}
    <rect x="18" y="28" width="52" height="68" rx="5" fill="none" stroke="rgba(155,184,204,0.5)" stroke-width="1.5"/>
    <rect x="20" y="58" width="48" height="36" rx="3" fill="rgba(155,184,204,0.12)"/>
    <circle cx="32" cy="74" r="4" fill="rgba(155,184,204,0.7)"/>
    <circle cx="45" cy="70" r="4" fill="rgba(155,184,204,0.7)"/>
    <circle cx="58" cy="76" r="4" fill="rgba(155,184,204,0.7)"/>
    <text x="44" y="46" font-size="9" fill="rgba(155,184,204,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">Zn</text>
    <rect x="78" y="28" width="60" height="68" rx="5" fill="none" stroke="rgba(120,210,120,0.5)" stroke-width="1.5"/>
    <rect x="80" y="58" width="56" height="36" rx="3" fill="rgba(120,210,120,0.12)"/>
    <circle cx="92" cy="72" r="3.5" fill="rgba(120,210,120,0.7)"/>
    <circle cx="103" cy="76" r="3.5" fill="rgba(120,210,120,0.7)"/>
    <circle cx="114" cy="70" r="3.5" fill="rgba(120,210,120,0.7)"/>
    <circle cx="125" cy="74" r="3.5" fill="rgba(120,210,120,0.7)"/>
    <text x="108" y="46" font-size="9" fill="rgba(120,210,120,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">2HCl</text>
    <text x="150" y="68" font-size="14" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" text-anchor="middle">&#8594;</text>
    <rect x="162" y="28" width="52" height="68" rx="5" fill="none" stroke="rgba(76,201,240,0.5)" stroke-width="1.5"/>
    <rect x="164" y="58" width="48" height="36" rx="3" fill="rgba(76,201,240,0.12)"/>
    <circle cx="176" cy="74" r="4" fill="rgba(76,201,240,0.7)"/>
    <circle cx="189" cy="70" r="4" fill="rgba(76,201,240,0.7)"/>
    <circle cx="202" cy="76" r="4" fill="rgba(76,201,240,0.7)"/>
    <text x="184" y="46" font-size="9" fill="rgba(76,201,240,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">ZnCl&#8322;</text>
    <rect x="222" y="28" width="36" height="68" rx="5" fill="none" stroke="rgba(255,209,102,0.5)" stroke-width="1.5"/>
    <circle cx="235" cy="68" r="3" fill="rgba(255,209,102,0.7)"/>
    <circle cx="248" cy="74" r="3" fill="rgba(255,209,102,0.7)"/>
    <text x="240" y="46" font-size="9" fill="rgba(255,209,102,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">H&#8322;</text>
    <text x="135" y="118" font-size="8" fill="rgba(239,71,111,0.7)" font-family="Manrope,sans-serif" text-anchor="middle">&#9679; лимит</text>`);

  /* Periodic Table — 6×4 coloured cell grid */
  const P_PERIODIC = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${(function(){
      const cols=18,rows=4,pad=6,w=(270-pad*2)/cols,h=(140-pad*2)/rows;
      const colors=['#EF476F','#FF6B35','#FFD166','#7BF5A4','#C77DFF','#A8DADC',
                    '#7B8EF7','#06D6E0','#9B5DE5','#F15BB5','#EF476F','#FF6B35',
                    '#06D6E0','#7B8EF7','#FFD166','#C77DFF','#A8DADC','#7BF5A4'];
      let s='';
      for(let r=0;r<rows;r++) for(let c=0;c<cols;c++){
        const x=pad+c*w, y=pad+r*h;
        const skip=(r===0&&c>=2&&c<=15)||(r===1&&c>=2&&c<=11);
        if(!skip) s+=`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${(w-1.5).toFixed(1)}" height="${(h-1.5).toFixed(1)}" rx="2" fill="${colors[c]}" opacity="0.7"/>`;
      }
      return s;
    })()}
    <text x="135" y="134" font-size="8" fill="rgba(255,255,255,0.35)" font-family="Manrope,sans-serif" text-anchor="middle">118 элементов</text>`);

  const P_CRYSTAL = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${[
      [80,40],[135,40],[190,40],
      [55,75],[110,75],[165,75],[220,75],
      [80,110],[135,110],[190,110]
    ].map(([x,y],i)=>`<circle cx="${x}" cy="${y}" r="${i%2===0?7:5}" fill="${i%2===0?'#9B5DE5':'#06D6E0'}" opacity="0.8"/>`).join('')}
    <line x1="80" y1="40" x2="135" y2="40" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="135" y1="40" x2="190" y2="40" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="80" y1="40" x2="55" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="135" y1="40" x2="110" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="190" y1="40" x2="165" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="190" y1="40" x2="220" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="55" y1="75" x2="80" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="110" y1="75" x2="135" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="165" y1="75" x2="190" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="80" y1="110" x2="135" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
    <line x1="135" y1="110" x2="190" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>`);

  const P_CELLDIVISION = _svg(`
    <rect width="270" height="140" fill="#0e0e18"/>
    <ellipse cx="135" cy="70" rx="78" ry="54" fill="rgba(34,211,153,0.07)" stroke="rgba(34,211,153,0.5)" stroke-width="2"/>
    <ellipse cx="135" cy="70" rx="44" ry="28" fill="rgba(155,93,229,0.08)" stroke="rgba(155,93,229,0.3)" stroke-width="1" stroke-dasharray="4,3"/>
    <line x1="55" y1="70" x2="215" y2="70" stroke="rgba(255,214,0,0.35)" stroke-width="1.2" stroke-dasharray="3,2"/>
    <rect x="98" y="57" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
    <rect x="112" y="57" width="9" height="15" rx="2" fill="#FF6B35" opacity="0.9"/>
    <rect x="126" y="57" width="9" height="15" rx="2" fill="#9B5DE5" opacity="0.9"/>
    <rect x="140" y="57" width="9" height="15" rx="2" fill="#FFD166" opacity="0.9"/>
    <rect x="154" y="57" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
    <rect x="98" y="75" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
    <rect x="112" y="75" width="9" height="15" rx="2" fill="#FF6B35" opacity="0.9"/>
    <rect x="126" y="75" width="9" height="15" rx="2" fill="#9B5DE5" opacity="0.9"/>
    <rect x="140" y="75" width="9" height="15" rx="2" fill="#FFD166" opacity="0.9"/>
    <rect x="154" y="75" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
    <line x1="135" y1="16" x2="114" y2="57" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
    <line x1="135" y1="16" x2="135" y2="57" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
    <line x1="135" y1="124" x2="114" y2="90" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
    <line x1="135" y1="124" x2="135" y2="90" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
    <circle cx="135" cy="15" r="5" fill="rgba(255,214,0,0.7)"/>
    <circle cx="135" cy="125" r="5" fill="rgba(255,214,0,0.7)"/>
    <text x="135" y="137" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Метафаза · митоз</text>`);

  const P_PHOTOSYNTHESIS = _svg(`
    <rect width="270" height="140" fill="#0a0e14"/>
    <ellipse cx="135" cy="72" rx="100" ry="48" fill="rgba(34,211,153,0.07)" stroke="rgba(34,211,153,0.45)" stroke-width="2"/>
    <rect x="52" y="60" width="166" height="22" rx="7" fill="rgba(34,211,153,0.18)" stroke="rgba(34,211,153,0.5)" stroke-width="1.5"/>
    <line x1="70" y1="12" x2="79" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
    <line x1="100" y1="8" x2="107" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
    <line x1="135" y1="6" x2="135" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
    <line x1="170" y1="8" x2="163" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
    <circle cx="70" cy="11" r="5" fill="#FFD166" opacity="0.9"/>
    <circle cx="100" cy="7" r="5" fill="#FFD166" opacity="0.9"/>
    <circle cx="135" cy="5" r="5" fill="#FFD166" opacity="0.9"/>
    <circle cx="170" cy="7" r="5" fill="#FFD166" opacity="0.9"/>
    <text x="52" y="98" font-size="8" fill="rgba(6,214,224,0.8)" font-family="Manrope,sans-serif">H₂O</text>
    <text x="90" y="106" font-size="8" fill="rgba(255,255,255,0.45)" font-family="Manrope,sans-serif">CO₂</text>
    <text x="168" y="52" font-size="8" fill="#9B5DE5" font-family="Manrope,sans-serif">ATP</text>
    <text x="185" y="98" font-size="8" fill="#22d399" font-family="Manrope,sans-serif">G3P</text>
    <text x="135" y="135" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Световые реакции · цикл Кальвина</text>`);

  const P_ANGRYBIRDS = _svg(`
    <rect width="270" height="140" fill="#0f1923"/>
    <rect x="0" y="108" width="270" height="32" fill="#3d6b47"/>
    <line x1="0" y1="108" x2="270" y2="108" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
    <rect x="175" y="68" width="22" height="40" fill="#b5651d" stroke="#7a3f0a" stroke-width="1.5"/>
    <rect x="158" y="56" width="56" height="14" fill="#b5651d" stroke="#7a3f0a" stroke-width="1.5"/>
    <rect x="168" y="40" width="18" height="18" fill="#a8d8ea" stroke="#5badd4" stroke-width="1.5"/>
    <rect x="202" y="78" width="16" height="30" fill="#7a7a7a" stroke="#444" stroke-width="1.5"/>
    <circle cx="232" cy="100" r="10" fill="#22c55e" stroke="#166534" stroke-width="1.5"/>
    <circle cx="215" cy="99" r="10" fill="#22c55e" stroke="#166534" stroke-width="1.5"/>
    <path d="M 32,102 Q 90,38 148,98" stroke="#ef476f" stroke-width="2.5" fill="none" stroke-dasharray="4,3"/>
    <circle cx="32" cy="102" r="9" fill="#e63946"/>
    <circle cx="36" cy="98" r="2.5" fill="#fff"/>
    <circle cx="37.5" cy="97.5" r="1.1" fill="#111"/>
    <line x1="28" y1="94" x2="38" y2="98" stroke="#333" stroke-width="1.5" stroke-linecap="round"/>
    <circle cx="21" cy="106" r="6.5" fill="#888" opacity="0.7"/>
    <circle cx="10" cy="106" r="5.5" fill="#ffd166" opacity="0.5"/>
    <line x1="18" y1="93" x2="22" y2="80" stroke="rgba(255,255,255,0.18)" stroke-width="5" stroke-linecap="round"/>
    <line x1="22" y1="80" x2="26" y2="93" stroke="rgba(255,255,255,0.18)" stroke-width="5" stroke-linecap="round"/>
    <text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Физика полёта · импульс · разрушение</text>`);

  const P_WAVES = _svg(`${_grid()}
    <line x1="0" y1="70" x2="270" y2="70" stroke="rgba(255,255,255,0.13)" stroke-width="1" stroke-dasharray="4,3"/>
    <path d="M 0,70 C 17,26 33,26 67,70 C 101,114 117,114 135,70 C 153,26 169,26 202,70 C 236,114 252,114 270,70"
          stroke="#9B5DE5" stroke-width="2" fill="none" opacity="0.7"/>
    <path d="M 0,70 C 22,18 44,18 90,70 C 136,122 158,122 180,70 C 202,18 224,18 270,70"
          stroke="#06D6E0" stroke-width="1.5" fill="none" opacity="0.5"/>
    <path d="M 0,70 C 12,10 28,8 50,40 C 72,72 88,118 112,85 C 136,52 155,18 180,50 C 205,82 240,108 270,70"
          stroke="#F15BB5" stroke-width="2.5" fill="none" opacity="0.9"/>
    <text x="135" y="132" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">v = \u03bbf \u00b7 y = A sin(\u03c9t \u2212 kx) \u00b7 \u0441\u0442\u043e\u044f\u0447\u0438\u0435 \u0432\u043e\u043b\u043d\u044b</text>`);

  /* Radioactive decay preview */
  const P_RADIOACTIVE = _svg(`${_grid()}
    <circle cx="55"  cy="45"  r="5" fill="#9B5DE5" opacity="0.9"/>
    <circle cx="90"  cy="65"  r="5" fill="#9B5DE5" opacity="0.9"/>
    <circle cx="38"  cy="80"  r="5" fill="#9B5DE5" opacity="0.7"/>
    <circle cx="75"  cy="95"  r="5" fill="#EF476F" opacity="0.9"/>
    <circle cx="110" cy="50"  r="5" fill="#EF476F" opacity="0.85"/>
    <circle cx="130" cy="85"  r="5" fill="#4CAF50" opacity="0.85"/>
    <circle cx="155" cy="55"  r="5" fill="#9B5DE5" opacity="0.8"/>
    <circle cx="170" cy="90"  r="5" fill="#4CAF50" opacity="0.75"/>
    <circle cx="200" cy="45"  r="5" fill="#4CAF50" opacity="0.9"/>
    <circle cx="215" cy="80"  r="5" fill="#4CAF50" opacity="0.85"/>
    <circle cx="240" cy="60"  r="5" fill="#9B5DE5" opacity="0.7"/>
    <path d="M 20,115 Q 67,42 135,52 Q 200,62 270,110"
          fill="none" stroke="#9B5DE5" stroke-width="2" opacity="0.55" stroke-dasharray="5,3"/>
    <path d="M 20,115 Q 100,110 175,100 Q 230,92 270,85"
          fill="none" stroke="#4CAF50" stroke-width="1.5" opacity="0.5"/>
    <text x="135" y="132" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">N(t) = N₀·e⁻λt · T½ · цепочки распада</text>`);

  /* Heat Engines preview */
  const P_HEATENGINE = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <line x1="30" y1="10" x2="30" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <line x1="30" y1="125" x2="265" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <path d="M 55,18 Q 100,30 140,75 Q 160,100 190,115" fill="none" stroke="#EF476F" stroke-width="2.2" opacity="0.85"/>
    <path d="M 190,115 Q 205,118 215,110 Q 230,90 225,60" fill="none" stroke="#FFD166" stroke-width="2" opacity="0.8"/>
    <path d="M 225,60 Q 200,40 160,32 Q 110,22 55,18" fill="none" stroke="#06D6E0" stroke-width="2.2" opacity="0.85"/>
    <path d="M 55,18 Q 48,16 44,22 Q 38,38 45,60 Q 50,80 55,18" fill="rgba(155,93,229,0.12)" stroke="#9B5DE5" stroke-width="1" opacity="0.5"/>
    <circle cx="55" cy="18" r="4" fill="#EF476F"/>
    <circle cx="190" cy="115" r="4" fill="#FFD166"/>
    <circle cx="225" cy="60" r="4" fill="#06D6E0"/>
    <text x="44" y="14" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif">A</text>
    <text x="195" y="126" font-size="9" fill="#FFD166" font-family="Manrope,sans-serif">B</text>
    <text x="230" y="58" font-size="9" fill="#06D6E0" font-family="Manrope,sans-serif">C</text>
    <text x="255" y="128" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">V</text>
    <text x="18" y="12" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">P</text>
    <text x="135" y="115" font-size="8" fill="rgba(155,93,229,0.7)" font-family="Manrope,sans-serif" text-anchor="middle">η = 1 − Tc/Th</text>`);

  /* Geometry (planimetry) preview */
  const P_GEOMETRY = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <circle cx="135" cy="70" r="50" fill="rgba(155,93,229,0.07)" stroke="#9B5DE5" stroke-width="1.5"/>
    <polygon points="85,99 185,99 135,20" fill="rgba(6,214,224,0.08)" stroke="#06D6E0" stroke-width="1.8"/>
    <line x1="85" y1="99" x2="162" y2="57" stroke="rgba(241,91,181,0.45)" stroke-width="1.2" stroke-dasharray="4,3"/>
    <line x1="185" y1="99" x2="109" y2="57" stroke="rgba(241,91,181,0.45)" stroke-width="1.2" stroke-dasharray="4,3"/>
    <line x1="135" y1="20" x2="135" y2="99" stroke="rgba(241,91,181,0.45)" stroke-width="1.2" stroke-dasharray="4,3"/>
    <circle cx="135" cy="64" r="4" fill="#06D6E0" opacity="0.9"/>
    <circle cx="85" cy="99" r="4" fill="#9B5DE5"/>
    <circle cx="185" cy="99" r="4" fill="#9B5DE5"/>
    <circle cx="135" cy="20" r="4" fill="#9B5DE5"/>
    <text x="78" y="111" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">A</text>
    <text x="188" y="111" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">B</text>
    <text x="131" y="16" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">C</text>`);


  /* Race sim preview — two objects on a track, x(t) lines */
  const P_RACE = _svg(`${_grid('rgba(255,255,255,0.05)')}
    <line x1="20" y1="75" x2="250" y2="75" stroke="rgba(255,255,255,0.18)" stroke-width="8" stroke-linecap="round"/>
    <line x1="20" y1="75" x2="250" y2="75" stroke="rgba(30,32,50,0.9)" stroke-width="6" stroke-linecap="round"/>
    <circle cx="75" cy="75" r="9" fill="rgba(6,214,224,0.2)" stroke="#06D6E0" stroke-width="2"/>
    <circle cx="185" cy="75" r="9" fill="rgba(239,71,111,0.2)" stroke="#EF476F" stroke-width="2"/>
    <line x1="30" y1="110" x2="130" y2="30" stroke="#06D6E0" stroke-width="2" opacity="0.85"/>
    <line x1="30" y1="30" x2="200" y2="110" stroke="#EF476F" stroke-width="2" opacity="0.85"/>
    <circle cx="107" cy="60" r="5" fill="#FF6B6B" stroke="#fff" stroke-width="1.5"/>
    <text x="115" y="57" font-size="9" fill="#FF6B6B" font-family="Manrope,sans-serif" font-weight="700">встреча</text>
    <text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">x = x₀ + v₀t + at²/2</text>`);

  /* Logic Circuits preview */
  const P_LOGIC = _svg(`${_grid('rgba(255,255,255,0.04)')}
    <rect x="20" y="38" width="60" height="30" fill="rgba(155,93,229,0.12)" stroke="#9B5DE5" stroke-width="1.5" rx="4"/>
    <text x="50" y="57" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">AND</text>
    <rect x="130" y="20" width="60" height="30" fill="rgba(6,214,224,0.12)" stroke="#06D6E0" stroke-width="1.5" rx="4"/>
    <text x="160" y="39" font-size="9" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">XOR</text>
    <rect x="130" y="60" width="60" height="30" fill="rgba(241,91,181,0.12)" stroke="#F15BB5" stroke-width="1.5" rx="4"/>
    <text x="160" y="79" font-size="9" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">AND</text>
    <circle cx="18" cy="45" r="4" fill="#4ADE80"/>
    <circle cx="18" cy="58" r="4" fill="rgba(255,255,255,0.35)"/>
    <line x1="22" y1="45" x2="80" y2="40" stroke="#4ADE80" stroke-width="1.5"/>
    <line x1="22" y1="58" x2="80" y2="55" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <line x1="80" y1="53" x2="112" y2="35" stroke="#4ADE80" stroke-width="1.5"/>
    <line x1="80" y1="53" x2="112" y2="75" stroke="#4ADE80" stroke-width="1.5"/>
    <line x1="190" y1="35" x2="230" y2="35" stroke="#4ADE80" stroke-width="1.5"/>
    <line x1="190" y1="75" x2="230" y2="75" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <circle cx="234" cy="35" r="5" fill="#4ADE80" opacity="0.9"/>
    <circle cx="234" cy="75" r="5" fill="rgba(255,255,255,0.25)"/>
    <text x="240" y="39" font-size="8" fill="#4ADE80" font-family="Manrope,sans-serif" font-weight="700">S</text>
    <text x="240" y="79" font-size="8" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">S = A⊕B · C = A∧B · Таблица истинности`);


  /* Qualitative Analysis preview */
  const P_QUALANALYSIS = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    <rect x="30" y="25" width="30" height="80" rx="6" fill="none" stroke="rgba(200,210,255,0.55)" stroke-width="1.5"/>
    <rect x="32" y="42" width="26" height="60" rx="4" fill="rgba(255,255,255,0.12)"/>
    <rect x="32" y="77" width="26" height="25" rx="3" fill="rgba(100,180,255,0.4)"/>
    <rect x="100" y="25" width="30" height="80" rx="6" fill="none" stroke="rgba(200,210,255,0.55)" stroke-width="1.5"/>
    <rect x="102" y="42" width="26" height="60" rx="4" fill="rgba(255,200,80,0.1)"/>
    <rect x="102" y="62" width="26" height="40" rx="3" fill="rgba(200,20,20,0.55)"/>
    <rect x="170" y="25" width="30" height="80" rx="6" fill="none" stroke="rgba(200,210,255,0.55)" stroke-width="1.5"/>
    <rect x="172" y="42" width="26" height="60" rx="4" fill="rgba(80,200,80,0.1)"/>
    <rect x="172" y="70" width="26" height="32" rx="3" fill="rgba(255,255,150,0.35)"/>
    <text x="45" y="118" font-size="7" fill="rgba(100,180,255,0.9)" text-anchor="middle" font-family="Manrope,sans-serif">Cl</text>
    <text x="115" y="118" font-size="7" fill="rgba(200,80,80,0.9)" text-anchor="middle" font-family="Manrope,sans-serif">Fe(III)</text>
    <text x="185" y="118" font-size="7" fill="rgba(200,200,80,0.9)" text-anchor="middle" font-family="Manrope,sans-serif">SO4</text>
    <text x="135" y="18" font-size="8" fill="rgba(155,93,229,0.7)" text-anchor="middle" font-family="Manrope,sans-serif">AgNO3</text>
    <line x1="45" y1="22" x2="45" y2="27" stroke="rgba(200,200,200,0.5)" stroke-width="1"/>
    <line x1="115" y1="22" x2="115" y2="27" stroke="rgba(200,200,200,0.5)" stroke-width="1"/>
    <line x1="185" y1="22" x2="185" y2="27" stroke="rgba(200,200,200,0.5)" stroke-width="1"/>
    <text x="135" y="136" font-size="8" fill="rgba(255,255,255,0.3)" text-anchor="middle" font-family="Manrope,sans-serif">AgCl / Fe(SCN) / BaSO4`);

  /* Organic Chemistry preview — benzene ring + OH group */
  const P_ORGANIC = _svg(`
    <rect width="270" height="140" fill="#0D0D1A"/>
    ${_grid('rgba(255,255,255,0.03)')}
    <!-- benzene ring -->
    <polygon points="115,44 145,44 160,70 145,96 115,96 100,70" fill="rgba(155,93,229,0.08)" stroke="#9B5DE5" stroke-width="1.8"/>
    <!-- alternating double bonds (inner circle shorthand) -->
    <circle cx="130" cy="70" r="18" fill="none" stroke="#9B5DE5" stroke-width="1.2" stroke-dasharray="5,4" opacity="0.55"/>
    <!-- C labels -->
    <text x="108" y="46" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <text x="145" y="46" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <text x="164" y="73" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <text x="145" y="99" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <text x="108" y="99" font-size="9" font-family="Manrope,sans-serif" font-weight="700" fill="#9B5DE5">C</text>
    <text x="94" y="73" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <!-- -OH substituent -->
    <line x1="160" y1="70" x2="195" y2="55" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
    <circle cx="200" cy="52" r="9" fill="rgba(239,71,111,0.25)" stroke="#EF476F" stroke-width="1.5"/>
    <text x="200" y="56" font-size="9" fill="#EF476F" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">O</text>
    <line x1="209" y1="48" x2="218" y2="40" stroke="rgba(255,255,255,0.3)" stroke-width="1.2"/>
    <circle cx="222" cy="37" r="6" fill="rgba(224,224,224,0.2)" stroke="#E0E0E0" stroke-width="1.2"/>
    <text x="222" y="41" font-size="8" fill="#E0E0E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">H</text>
    <!-- chain fragment on right -->
    <line x1="100" y1="70" x2="65" y2="70" stroke="rgba(255,255,255,0.35)" stroke-width="1.5"/>
    <circle cx="58" cy="70" r="9" fill="rgba(155,93,229,0.2)" stroke="#9B5DE5" stroke-width="1.5"/>
    <text x="58" y="74" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">C</text>
    <text x="135" y="134" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Конструктор · Ряды · Качественные реакции</text>`);

  /* Solutions preview */
  const P_SOLUTIONS = _svg(`
    <rect x="88" y="20" width="58" height="88" rx="3" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="2"/>
    <rect x="89" y="52" width="56" height="55" rx="2" fill="rgba(76,201,240,0.55)"/>
    <path d="M89,52 Q108,47 117,52 Q127,57 145,52" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
    <line x1="90" y1="66" x2="96" y2="66" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
    <line x1="90" y1="81" x2="96" y2="81" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
    <line x1="90" y1="96" x2="96" y2="96" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
    <text x="117" y="80" font-size="15" fill="rgba(255,255,255,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="800">20%</text>
    <path d="M172,32 Q175,24 179,32 Q183,41 179,45 Q175,49 172,45 Q168,41 172,32 Z" fill="#4CC9F0" opacity="0.85"/>
    <line x1="193" y1="20" x2="250" y2="90" stroke="rgba(255,255,255,0.08)" stroke-width="1" stroke-dasharray="3,3"/>
    <text x="221" y="42" font-size="8" fill="rgba(76,201,240,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">ω%</text>
    <text x="221" y="56" font-size="8" fill="rgba(155,93,229,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">C&#x2d;M</text>
    <text x="221" y="70" font-size="8" fill="rgba(241,91,181,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">ν моль</text>
    <text x="135" y="123" font-size="8" fill="rgba(76,201,240,0.75)" text-anchor="middle" font-family="Manrope,sans-serif">ω = m&#8320;/m&#8203; · 100%</text>
    <text x="135" y="135" font-size="7" fill="rgba(255,255,255,0.3)" text-anchor="middle" font-family="Manrope,sans-serif">Калькулятор · Разбавление · Смешивание · S(T)</text>`);

  const SIMS = [
    /* ── Математика ── */
    { id: 'graph', cat: 'math',
      title: 'График функции',
      desc: 'Строй графики функций y = f(x) с параметрами, зумом и курсором координат.',
      preview: P_GRAPH },
    { id: 'graphtransform', cat: 'math',
      title: 'Трансформации графиков',
      desc: 'Наблюдай, как сдвиги, растяжения и отражения меняют вид функции y = a·f(kx+b)+c.',
      preview: P_TRANSFORM },
    { id: 'geometry', cat: 'math',
      title: 'Планиметрия',
      desc: 'Интерактивная среда построений: точки, отрезки, прямые, окружности, многоугольники. Полноценный чертёж с привязкой и измерениями.',
      preview: P_GEOMETRY },
    { id: 'triangle', cat: 'math',
      title: 'Геометрия треугольника',
      desc: 'Интерактивный треугольник: медианы, высоты, биссектрисы, вписанная и описанная окружности.',
      preview: P_TRIANGLE },
    { id: 'quadratic', cat: 'math',
      title: 'Корни квадратного уравнения',
      desc: 'Задай a, b, c ползунками — смотри дискриминант и корни анимированно на числовой оси.',
      preview: P_QUADRATIC },
    { id: 'stereo', cat: 'math',
      title: 'Стереометрия 3D',
      desc: 'Вращаемые объёмные фигуры: куб, пирамида, цилиндр, конус с формулами объёма и площади. Сечения, развёртка, вписанные/описанные сферы.',
      preview: P_3D },
    { id: 'probability', cat: 'math',
      title: 'Теория вероятностей',
      desc: 'Подброс монеты/кубика N раз — гистограмма частот и закон больших чисел в действии.',
      preview: P_PROB },
    { id: 'trigcircle', cat: 'math',
      title: 'Тригонометрическая окружность',
      desc: 'Единичная окружность с sin, cos, tg, ctg. Перетаскивай точку — все функции обновляются мгновенно. График синхронизирован.',
      preview: P_TRIGCIRCLE },
    { id: 'normaldist', cat: 'math',
      title: 'Нормальное распределение',
      desc: 'Двигай μ и σ ползунками — колокол Гаусса и площадь под кривой обновляются мгновенно.',
      preview: P_NORMAL },
    /* ── Физика ── */
    { id: 'projectile', cat: 'phys',
      title: 'Бросок тела',
      desc: 'Задай начальную скорость и угол — симулируй траекторию, дальность и высоту полёта.',
      preview: P_PROJECTILE },
    { id: 'pendulum', cat: 'phys',
      title: 'Маятник',
      desc: 'Регулируй длину и угол отклонения — изучай период колебаний и затухание.',
      preview: P_PENDULUM },
    { id: 'collision', cat: 'phys',
      title: 'Столкновение шаров',
      desc: 'Упругий и неупругий удар двух тел: законы сохранения импульса и энергии.',
      preview: P_COLLISION },
    { id: 'emfield', cat: 'phys',
      title: 'Электромагнитные поля',
      desc: 'Электрическое и магнитное поля в одной симуляции: заряды, токи, силовые линии, эквипотенциали, частица Лоренца.',
      preview: P_MAGNETIC },
    { id: 'circuit', cat: 'phys',
      title: 'Электрические цепи',
      desc: 'Конструктор цепей из резисторов и конденсаторов. Законы Ома и Кирхгофа наглядно.',
      preview: P_CIRCUIT },
    { id: 'hydrostatics', cat: 'phys',
      title: 'Гидростатика',
      desc: 'Давление жидкости P=ρgh, закон Архимеда, сообщающиеся сосуды, поверхностное натяжение и капиллярность.',
      preview: P_HYDRO },
    { id: 'dynamics', cat: 'phys',
      title: 'Динамика',
      desc: 'Законы Ньютона, песочница сил, наклонная плоскость — всё в одном интерактивном модуле.',
      preview: P_SANDBOX },
    { id: 'opticsbench', cat: 'phys',
      title: 'Оптическая скамья',
      desc: 'Линза, зеркала и преломление в одной симуляции: формула линзы, зеркальное отражение, закон Снеллиуса, ПВО, дисперсия.',
      preview: P_LENS },
    { id: 'isoprocess', cat: 'phys',
      title: 'Изопроцессы',
      desc: 'PV-диаграмма для четырёх изопроцессов идеального газа. Расчёт работы, теплоты и внутренней энергии.',
      preview: P_ISOPROCESS },
    { id: 'waves', cat: 'phys',
      title: 'Волны и звук',
      desc: 'Поперечные и продольные волны, суперпозиция, стоячие волны. Частота, амплитуда, фаза, гармоники.',
      preview: P_WAVES },
    { id: 'radioactive', cat: 'phys',
      title: 'Радиоактивный распад',
      desc: 'Период полураспада, цепочки распадов, активность. Визуализация ядер + кривая N(t). Радиоуглеродное датирование.',
      preview: P_RADIOACTIVE },
    { id: 'race', cat: 'phys',
      title: 'Гонка с задачами',
      desc: 'Кинематика 1D: встреча, догон, кто первый. Реши задачу — проверь анимацией и графиком x(t).',
      preview: P_RACE },
    { id: 'heatengine', cat: 'phys',
      title: 'Тепловые двигатели',
      desc: 'Циклы Карно, Отто, Дизеля, Брайтона. PV-диаграмма, поршень, КПД.',
      preview: P_HEATENGINE },
    { id: 'logic', cat: 'phys',
      title: 'Логические схемы',
      desc: 'Конструктор цифровых схем: И/ИЛИ/НЕ/XOR, триггеры, сумматоры. Авто-таблица истинности.',
      preview: P_LOGIC },
    /* ── Химия / Молекулярная физика ── */
    { id: 'molphys', cat: 'chem',
      title: 'Молекулярная физика',
      desc: 'Идеальный газ, броуновское движение, агрегатные состояния и диффузия — всё в одном модуле.',
      preview: P_GAS },
    { id: 'chemistry', cat: 'chem',
      title: 'Химические реакции',
      desc: 'Кинетика реакций, металл + кислота в колбе, ОВР с переносом электронов, ионный обмен — всё в одном модуле.',
      preview: P_KINETICS },
    { id: 'equilibrium', cat: 'chem',
      title: 'Химическое равновесие',
      desc: 'Прямая и обратная реакция, принцип Ле Шателье: изменяй T, P, концентрацию и наблюдай сдвиг.',
      preview: P_EQUILIBRIUM },
    { id: 'electrolysis', cat: 'chem',
      title: 'Электролиз',
      desc: 'Катод и анод в растворе электролита: движение ионов, выделение газа, закон Фарадея.',
      preview: P_ELECTROLYSIS },
    /* ── Скоро: Атомная структура ── */
    { id: 'bohratom', cat: 'chem',
      title: 'Атом Бора',
      desc: 'Электроны на орбитах, квантование энергии, эмиссия и поглощение фотонов при переходах.',
      preview: P_BOHR },
    { id: 'orbitals', cat: 'chem',
      title: 'Молекулярные орбитали',
      desc: 'H₂, H₂O — ковалентная связь, перекрывание орбиталей, 3D-визуализация электронных облаков.',
      preview: P_ORBITALS },
    /* ── Скоро: Визуальная химия ── */
    { id: 'titration', cat: 'chem',
      title: 'pH и кривая титрования',
      desc: 'Добавляй кислоту или щёлочь — наблюдай изменение pH, цвет раствора и кривую нейтрализации.',
      preview: P_PH },
    { id: 'chemsandbox', cat: 'chem',
      title: 'Химическая песочница',
      desc: 'Смешивай реагенты, наблюдай реакции: осадки, газы, изменение цвета. Свободное экспериментирование.',
      preview: P_CHEMSANDBOX },
    { id: 'stoichiometry', cat: 'chem',
      title: 'Стехиометрия',
      desc: 'Расчёты по уравнениям: масса, моль, объём. Лимитирующий реагент, выход. 10 реакций.',
      preview: P_STOICHIOMETRY },
    { id: 'crystal', cat: 'chem',
      title: 'Кристаллическая решётка',
      desc: 'NaCl, алмаз, металл — интерактивная 3D-решётка, типы связей, вращение структуры.',
      preview: P_CRYSTAL },
    { id: 'qualanalysis', cat: 'chem',
      title: 'Качественный анализ',
      desc: 'Определяй катионы и анионы качественными реакциями: осадки, газы, пламя. Два режима: guided и свободный эксперимент.',
      preview: P_QUALANALYSIS },
    { id: 'periodic', cat: 'chem',
      title: 'Периодическая таблица',
      desc: '118 элементов: подсветка по типу/блоку, карточка элемента, боровские оболочки, графики свойств.',
      preview: P_PERIODIC },
    { id: 'organic', cat: 'chem',
      title: 'Органическая химия',
      desc: 'Конструктор молекул с проверкой валентности, гомологические ряды с таблицей свойств, качественные реакции (бромная вода, KMnO₄, зеркало Толленса, Cu(OH)₂, FeCl₃, Na).',
      preview: P_ORGANIC },
    { id: 'solutions', cat: 'chem',
      title: 'Растворы',
      desc: 'Калькулятор раствора: ω, ν, C_M, плотность. Разбавление и смешивание с визуализацией. Кривые растворимости S(T) для 8 веществ + задача на перекристаллизацию.',
      preview: P_SOLUTIONS },
    /* ── Биология ── */
    { id: 'celldivision', cat: 'bio',
      title: 'Деление клетки',
      desc: 'Митоз и мейоз: анимированные фазы, хромосомы, веретено деления, ядерная оболочка.',
      preview: P_CELLDIVISION },
    { id: 'photosynthesis', cat: 'bio',
      title: 'Фотосинтез и дыхание',
      desc: 'Световые реакции в тилакоидах, цикл Кальвина, митохондриальное дыхание — молекулярная анимация.',
      preview: P_PHOTOSYNTHESIS },

    /* ── Игры ── */
    { id: 'angrybirds', cat: 'game',
      title: 'Angry Birds Physics',
      desc: 'Запускай птиц из рогатки, разрушай блоки, побеждай свиней. Реальная физика: гравитация, ветер, импульс. 6 уровней.',
      preview: P_ANGRYBIRDS },
  ];

  var _theoryOpen = false;
  function toggleTheory() {
    _theoryOpen = !_theoryOpen;
    document.getElementById('theory-panel').classList.toggle('open', _theoryOpen);
    const btn = document.getElementById('theory-toggle');
    btn.style.background = _theoryOpen ? 'rgba(155,93,229,0.15)' : '';
    btn.style.borderColor = _theoryOpen ? 'var(--violet)' : '';
    btn.style.color = _theoryOpen ? 'var(--violet)' : '';
  }

  function loadTheory(simId) {
    const t = THEORY[simId];
    const el = document.getElementById('theory-content');
    if (!t) { el.innerHTML = '<div class="tp-text" style="text-align:center;padding:40px 0;color:var(--text-3)">Теория для этой симуляции пока не добавлена</div>'; return; }
    let html = `<div class="tp-title">${LS.icon('book-open',16)} ${t.title}</div>`;
    for (const s of t.sections) {
      html += '<div class="tp-section">';
      if (s.head)    html += `<div class="tp-section-head">${s.head}</div>`;
      if (s.formula) html += `<div class="tp-formula" data-formula="${s.formula.replace(/"/g,'&quot;')}"></div>`;
      if (s.text)    html += `<div class="tp-text">${s.text}</div>`;
      if (s.vars)    html += `<div class="tp-var-list">${s.vars.map(([v,d]) => `<div class="tp-var"><b>${v}</b> — ${d}</div>`).join('')}</div>`;
      html += '</div>';
    }
    el.innerHTML = html;
    // render KaTeX formulas
    el.querySelectorAll('.tp-formula[data-formula]').forEach(div => {
      try { katex.render(div.dataset.formula, div, { displayMode: true, throwOnError: false }); }
      catch(e) { div.textContent = div.dataset.formula; }
    });
  }

  /* ── embed mode + auto-open from ?sim= ── */
  const _qp = new URLSearchParams(location.search);
  var _embedMode = _qp.get('embed') === '1';
  var _autoSim   = _qp.get('sim');

  /* ── Sim state relay (embed mode only) ──────────────────────────────── */
  // Map simId → { getState, applyState } registered by openSim handlers
  const _simStateRegistry = {};

  function _registerSimState(simId, getState, applyState) {
    _simStateRegistry[simId] = { getState, applyState };
  }

  let _lastEmittedState = null;
  let _stateEmitInterval = null;

  function _startStateEmit(simId) {
    if (_stateEmitInterval) clearInterval(_stateEmitInterval);
    _lastEmittedState = null;
    _stateEmitInterval = setInterval(() => {
      const reg = _simStateRegistry[simId];
      if (!reg) return;
      try {
        const state = reg.getState();
        const json  = JSON.stringify(state);
        if (json === _lastEmittedState) return;
        _lastEmittedState = json;
        window.parent.postMessage({ type: 'sim_state', simId, state }, '*');
      } catch {}
    }, 400);
  }

  function _stopStateEmit() {
    if (_stateEmitInterval) { clearInterval(_stateEmitInterval); _stateEmitInterval = null; }
    _lastEmittedState = null;
  }

  // Receive apply_sim_state from parent (students)
  window.addEventListener('message', e => {
    if (!_embedMode) return;
    const d = e.data;
    if (!d || d.type !== 'apply_sim_state') return;
    const reg = _simStateRegistry[_autoSim];
    if (!reg) return;
    try {
      reg.applyState(d.state);
      _lastEmittedState = JSON.stringify(d.state); // suppress echo
    } catch {}
  });

  if (_embedMode) {
    document.querySelector('.sidebar').style.display = 'none';
    document.querySelector('.sb-content').style.marginLeft = '0';
    document.querySelector('.app-layout').classList.add('embed-mode');
    document.getElementById('lab-home').style.display = 'none';
    document.getElementById('theory-toggle').style.display = 'none';
    if (_autoSim) {
      document.getElementById('lab-sim').classList.add('open');
      document.querySelector('.sim-topbar').style.display = 'none';
      // defer until all external scripts are loaded
      window.addEventListener('load', () => openSim(_autoSim));
    }
  } else {
    /* init — fetch sim settings + permissions in parallel, then render */
    const _permFetch = (!isTeacher && !isAdmin)
      ? LS.api('/api/permissions/me').catch(() => null)
      : Promise.resolve(null);

    Promise.all([
      LS.api('/api/settings/sims').catch(() => ({})),
      _permFetch,
    ]).then(([cfg, permData]) => {
      _simModuleDisabled = cfg.module_disabled || false;
      _disabledSimIds    = new Set(cfg.disabled_ids || []);

      // check simulations.access for students
      if (!isTeacher && !isAdmin && permData) {
        const p = permData.permissions?.find(p => p.key === 'simulations.access');
        if (p && p.effective === false) {
          document.getElementById('sim-grid').innerHTML =
            `<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:var(--text-3)">
              <div style="font-size:2rem;margin-bottom:12px"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
              <div style="font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:var(--text);margin-bottom:6px">Доступ к симуляциям закрыт</div>
              <div style="font-size:.88rem">Администратор ограничил доступ к лаборатории</div>
            </div>`;
          return;
        }
        // store quiz permission for later use
        const qp = permData.permissions?.find(p => p.key === 'simulations.quiz');
        window._simQuizAllowed = !qp || qp.effective !== false;
      } else {
        window._simQuizAllowed = true;
      }

      if (_simModuleDisabled) {
        document.getElementById('sim-grid').innerHTML =
          `<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:var(--text-3)">
            <div style="font-size:2rem;margin-bottom:12px"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
            <div style="font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:var(--text);margin-bottom:6px">Модуль симуляций отключён</div>
            <div style="font-size:.88rem">Администратор временно отключил лабораторию</div>
          </div>`;
      } else {
        renderSims();
        if (_autoSim) openSim(_autoSim);
        // hash-router: activate sim from URL fragment after catalogue renders
        else _activateFromHash();
      }
    });
    lucide.createIcons();
    LS.notif.init();
  }

  /* ─── Hash router for sim deep-links ─────────────────────────────────────
     URL pattern: /lab#sim/<name>
     <name> matches SIMS[i].id (e.g. 'projectile', 'graph', 'chemsandbox').
     F5 restores sim. Browser back/forward switches between sims.
     Click on sim-card updates URL via wrapped openSim.
  ──────────────────────────────────────────────────────────────────────── */

  // Build valid-id set from SIMS catalogue (filters out "coming soon" entries)
  const _SIM_HASH_MAP = {};
  SIMS.forEach(function(s) { if (s.id) { _SIM_HASH_MAP[s.id] = s.id; } });
  // backward-compat aliases: old URLs redirect to unified emfield sim
  _SIM_HASH_MAP['magnetic']   = 'magnetic';
  _SIM_HASH_MAP['coulomb']    = 'coulomb';
  // backward-compat aliases: old optics sims redirect to opticsbench
  _SIM_HASH_MAP['thinlens']   = 'opticsbench';
  _SIM_HASH_MAP['mirrors']    = 'opticsbench';
  _SIM_HASH_MAP['refraction'] = 'opticsbench';

  var _routerNavigating = false;

  function _activateFromHash() {
    var m = (location.hash || '').match(/^#sim\/([\w-]+)/);
    if (!m) return false;
    var simName = m[1];
    if (!_SIM_HASH_MAP[simName]) {
      // eslint-disable-next-line no-console
      window.console && window.console.warn('lab-router: unknown sim', simName);
      return false;
    }
    openSim(simName);
    return true;
  }

  // Intercept openSim to push URL hash on user-initiated navigation
  var _origOpenSim = openSim;
  openSim = function(id) {
    _origOpenSim(id);
    if (!_routerNavigating && !_embedMode) {
      var baseId = id.includes(':') ? id.split(':')[0] : id;
      if (_SIM_HASH_MAP[baseId]) {
        _routerNavigating = true;
        location.hash = '#sim/' + baseId;
        // use setTimeout so hashchange fires after flag is set
        setTimeout(function() { _routerNavigating = false; }, 0);
      }
    }
  };

  /* ─── Sim Fade Transition + View Transitions API ─────────────────────────
     Wraps openSim with a fade-out (150ms) → swap → fade-in (200ms) sequence.
     If document.startViewTransition is available it is used for GPU-composited
     cross-fade; otherwise the manual .sim-fading CSS class is toggled.
     The hash-router wrap above runs synchronously during the transition so URL
     updates are not delayed.
  ──────────────────────────────────────────────────────────────────────── */
  var _hashRouterOpenSim = openSim; // reference after hash-router wrap
  openSim = function(id) {
    var labSim = document.getElementById('lab-sim');
    if (!labSim) { _hashRouterOpenSim(id); return; }

    function _doSwitch() {
      labSim.classList.add('sim-fading');
      setTimeout(function() {
        _hashRouterOpenSim(id);
        labSim.classList.remove('sim-fading');
      }, 150);
    }

    if (typeof document.startViewTransition === 'function' && !_embedMode) {
      document.startViewTransition(function() {
        _hashRouterOpenSim(id);
      });
    } else {
      _doSwitch();
    }
  };

  // Intercept closeSim to clear hash when returning to home grid
  var _origCloseSim = closeSim;
  closeSim = function() {
    _origCloseSim();
    if (!_embedMode) {
      _routerNavigating = true;
      history.pushState(null, '', location.pathname + location.search);
      setTimeout(function() { _routerNavigating = false; }, 0);
    }
  };

  // Browser back/forward navigation
  window.addEventListener('hashchange', function() {
    if (_routerNavigating) return;
    var hasHash = _activateFromHash();
    if (!hasHash && document.getElementById('lab-sim').classList.contains('open')) {
      _origCloseSim();
    }
  });
