/* global React, ReactDOM */
const { useState, useEffect, useRef } = React;

const getPageLang = () => localStorage.getItem('dicefriend-lang') || 'pl';

function usePageLang() {
  const [lang, setLang] = useState(getPageLang());
  useEffect(() => {
    const update = (event) => setLang(event.detail?.lang || getPageLang());
    window.addEventListener('dicefriend:lang', update);
    return () => window.removeEventListener('dicefriend:lang', update);
  }, []);
  return lang;
}

// ────────────────────────────────────────────────────────────
// WIREFRAME DEVICE — outline mesh version of the diceFriend
// Used in the hero (right half) and as decoration.
// viewBox matches the device aspect ratio 1320:1920 = ~132:192
// ────────────────────────────────────────────────────────────
function WireframeDevice({ withMesh = true }) {
  // Coordinates are aligned with the cropped photo (aspect 1378:1858).
  // viewBox is 100 × 135 — features placed so the wireframe overlays
  // pixel-perfect with the realistic photo for the hero half-split.
  const stroke = 'currentColor';

  // device-relative %, then mapped onto 100 × 135 viewBox
  const knobR = 9.2;
  const padR  = 10.5;
  const knobs = [
    [18, 59.2], [50, 59.2], [82, 59.2],
    [18, 85.0], [50, 85.0], [82, 85.0],
  ];
  const pads = [[20, 118], [50, 118], [80, 118]];

  const KnobMesh = ({ cx, cy, r }) => (
    <g>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke={stroke} strokeWidth="0.5" />
      {withMesh && (
        <>
          <circle cx={cx} cy={cy} r={r * 0.7} fill="none" stroke={stroke} strokeWidth="0.25" opacity="0.5" />
          <circle cx={cx} cy={cy} r={r * 0.32} fill="none" stroke={stroke} strokeWidth="0.25" opacity="0.5" />
          {[0, 30, 60, 90, 120, 150].map(a => (
            <line key={a}
              x1={cx + Math.cos(a * Math.PI / 180) * r}
              y1={cy + Math.sin(a * Math.PI / 180) * r}
              x2={cx - Math.cos(a * Math.PI / 180) * r}
              y2={cy - Math.sin(a * Math.PI / 180) * r}
              stroke={stroke} strokeWidth="0.18" opacity="0.45" />
          ))}
        </>
      )}
      <circle cx={cx} cy={cy} r="0.9" fill={stroke} />
      <line x1={cx - r + 1} y1={cy} x2={cx - r * 0.5} y2={cy} stroke={stroke} strokeWidth="0.7" />
    </g>
  );

  const PadMesh = ({ cx, cy, r }) => (
    <g>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke={stroke} strokeWidth="0.5" />
      {withMesh && (
        <>
          <circle cx={cx} cy={cy} r={r * 0.78} fill="none" stroke={stroke} strokeWidth="0.25" opacity="0.45" />
          <circle cx={cx} cy={cy} r={r * 0.45} fill="none" stroke={stroke} strokeWidth="0.25" opacity="0.45" />
        </>
      )}
    </g>
  );

  return (
    <svg viewBox="0 0 100 135" preserveAspectRatio="none"
      style={{ width: '100%', height: '100%', color: 'var(--ink)', display: 'block' }}>
      {/* outer body — wood starts a touch below the top edge,
         where the jack caps protrude above the wooden body */}
      <rect x="0" y="3" width="100" height="132" fill="none" stroke={stroke} strokeWidth="0.6" />
      {/* top panel ends where the brass-pad panel begins ~y=104 */}
      <line x1="0" y1="104" x2="100" y2="104" stroke={stroke} strokeWidth="0.6" />

      {/* subtle mesh background */}
      {withMesh && (
        <g opacity="0.16">
          {Array.from({ length: 11 }).map((_, i) => (
            <line key={`v${i}`} x1={i * 10} y1="0" x2={i * 10} y2="135" stroke={stroke} strokeWidth="0.15" />
          ))}
          {Array.from({ length: 14 }).map((_, i) => (
            <line key={`h${i}`} x1="0" y1={i * 10} x2="100" y2={i * 10} stroke={stroke} strokeWidth="0.15" />
          ))}
        </g>
      )}

      {/* top-edge jacks (small caps protruding above the wood body) */}
      {[28, 43, 56, 70].map((x) => (
        <g key={x}>
          <rect x={x - 1.8} y="0" width="3.6" height="3" fill={stroke} opacity="0.9" />
          <circle cx={x} cy="3" r="1.8" fill="none" stroke={stroke} strokeWidth="0.4" />
        </g>
      ))}

      {/* SHIFT toggle (left of front panel) */}
      <g>
        <circle cx="22" cy="18" r="3.6" fill="none" stroke={stroke} strokeWidth="0.5" />
        <line x1="22" y1="18" x2="22" y2="13.5" stroke={stroke} strokeWidth="0.7" />
        <circle cx="22" cy="13" r="0.9" fill={stroke} />
        {/* mounting holes */}
        <circle cx="14.5" cy="16.5" r="0.5" fill={stroke} opacity="0.6" />
        <circle cx="14.5" cy="20.5" r="0.5" fill={stroke} opacity="0.6" />
      </g>

      {/* USB-C jack housing */}
      <g>
        <ellipse cx="46" cy="18" rx="5" ry="4.6" fill="none" stroke={stroke} strokeWidth="0.5" />
        <rect x="42.5" y="17" width="7" height="2" rx="1" fill={stroke} opacity="0.9" />
      </g>

      {/* DICE button */}
      <g>
        <circle cx="70" cy="18" r="6.2" fill="none" stroke={stroke} strokeWidth="0.7" />
        <circle cx="70" cy="18" r="4.4" fill="none" stroke={stroke} strokeWidth="0.4" opacity="0.6" />
        <circle cx="70" cy="18" r="3.1" fill="none" stroke={stroke} strokeWidth="0.3" opacity="0.4" />
      </g>

      {/* LEDs strip — right */}
      {[15, 20, 25, 30].map(y => (
        <circle key={`lr${y}`} cx="93" cy={y} r="0.6" fill={stroke} />
      ))}
      {/* LEDs strip — left (smaller, near shift) */}
      {[26, 30].map(y => (
        <circle key={`ll${y}`} cx="13" cy={y} r="0.55" fill={stroke} />
      ))}

      {/* Knobs */}
      {knobs.map(([x, y], i) => <KnobMesh key={i} cx={x} cy={y} r={knobR} />)}

      {/* Pads */}
      {pads.map(([x, y], i) => <PadMesh key={i} cx={x} cy={y} r={padR} />)}

      {/* Corner crop ticks (technical drawing feel) */}
      <g stroke={stroke} strokeWidth="0.5">
        <line x1="0" y1="3" x2="5" y2="3" /><line x1="0" y1="3" x2="0" y2="8" />
        <line x1="95" y1="3" x2="100" y2="3" /><line x1="100" y1="3" x2="100" y2="8" />
        <line x1="0" y1="135" x2="5" y2="135" /><line x1="0" y1="130" x2="0" y2="135" />
        <line x1="95" y1="135" x2="100" y2="135" /><line x1="100" y1="130" x2="100" y2="135" />
      </g>
    </svg>
  );
}
window.WireframeDevice = WireframeDevice;

// ────────────────────────────────────────────────────────────
// ANATOMY DIAGRAM — recreated in the page's typography
// uses the device photo as bg + svg lines + html labels
// ────────────────────────────────────────────────────────────
const ANATOMY_LABELS = [
  // ── TOP: jack labels (short, just name + pin type)
  { id: 'gnd',   hot: [38.7, 21.7], label: [39.0, 8.2], side: 'top', short: true,
    name: 'GND' },
  { id: 'audio', hot: [52.1, 21.5], label: [49.8, 8.2], side: 'top', short: true,
    name: 'AUDIO OUT' },
  { id: 'cv2',   hot: [59.9, 21.5], label: [57.0, 8.2],  side: 'top', short: true,
    name: 'CV 2' },
  { id: 'cv1',   hot: [64.6, 21.5], label: [61.7, 8.2], side: 'top', short: true,
    name: 'CV 1' },

  // ── LEFT: full callouts with description
  { id: 'shift',   hot: [39.4, 25.3], label: [19, 28],  side: 'left',
    name: 'SHIFT', tag: '(toggle)',
    sub: 'Przełącza funkcje secondaryjne (tryb SHIFT). Wciśnij, aby aktywować dodatkowe funkcje przypisane do poszczególnych kontrolek i pokręteł.',
    subEn: 'Switches secondary functions (SHIFT mode). Press to activate extra functions assigned to individual controls and knobs.' },
  { id: 'leds-l',  hot: [36.2, 30.4], label: [19, 47],  side: 'left',
    name: 'DIODY LED', nameEn: 'LEDS', tag: '(status shift)',
    sub: 'Sygnalizują aktywny tryb funkcji SHIFT lub stan urządzenia.',
    subEn: 'Signal the active SHIFT layer or current device state.' },
  { id: 'potki',   hot: [39.5, 57.2], label: [19, 68],  side: 'left',
    name: 'POTKI', nameEn: 'KNOBS', tag: '(potentiometers / knobs)',
    sub: 'Pokrętła sterujące parametrami urządzenia. W trybie SHIFT ich funkcje mogą ulec zmianie.',
    subEn: 'Knobs that control device parameters. In SHIFT mode their functions may change.' },

  // ── RIGHT: full callouts
  { id: 'dice',   hot: [58.8, 27.5], label: [81, 31],  side: 'right',
    name: 'DICE', tag: '(button)',
    sub: 'Przycisk losujący. Generuje losową wartość lub wyzwala przypisaną funkcję w zależności od trybu pracy.',
    subEn: 'Randomizing button. Generates a random value or triggers an assigned function depending on the active mode.' },
  { id: 'leds-r', hot: [64.2, 29.2], label: [81, 51],  side: 'right',
    name: 'DIODY LED', nameEn: 'LEDS', tag: '(led diodes)',
    sub: 'Wskazują wyniki losowania lub status parametrów (w zależności od trybu pracy).',
    subEn: 'Show randomization results or parameter status depending on the active mode.' },

  // ── BOTTOM
  { id: 'pads',   hot: [50.0, 86.0], label: [50, 92],  side: 'bottom',
    name: 'PADY DOTYKOWE', nameEn: 'TOUCH PADS', tag: '(touch pads)',
    sub: 'Czułe na dotyk pola sterujące. Używane do wyzwalania dźwięków, zmiany wartości lub sterowania funkcjami w zależności od trybu pracy.',
    subEn: 'Touch-sensitive control fields. Used to trigger sounds, change values, or control functions depending on the active mode.' },
];

function leaderPath(it) {
  const [x1, y1] = it.hot;
  const [lx, ly] = it.label;
  if (it.side === 'top') {
    const stub = ly + 2.5;
    return `M ${x1} ${y1} L ${x1} ${stub} L ${lx} ${stub}`;
  }
  if (it.side === 'bottom') {
    const stub = ly - 2.5;
    return `M ${x1} ${y1} L ${x1} ${stub} L ${lx} ${stub}`;
  }
  if (it.side === 'left') {
    const stub = lx + 3;
    return `M ${x1} ${y1} L ${stub} ${y1} L ${stub} ${ly}`;
  }
  // right
  const stub = lx - 3;
  return `M ${x1} ${y1} L ${stub} ${y1} L ${stub} ${ly}`;
}

const SIDE_TRANSFORM = {
  left:   'translate(-100%, -50%)',
  right:  'translate(0%, -50%)',
  top:    'translate(-50%, -100%)',
  bottom: 'translate(-50%, 0%)',
};
const SIDE_ALIGN = { left: 'right', right: 'left', top: 'center', bottom: 'center' };

function Anatomy() {
  const [hl, setHl] = useState(null);
  const lang = usePageLang();

  return (
    <div className="anatomy">
      <div className="anatomy-frame">
        <div className="anatomy-device" />
        <div className="anatomy-overlay" style={{ color: 'var(--ink)' }}>
          <svg viewBox="0 0 100 100" preserveAspectRatio="none">
            {ANATOMY_LABELS.filter((it) => it.side !== 'top').map((it) => {
              const isActive = hl === it.id;
              return (
                <path key={it.id} d={leaderPath(it)} fill="none"
                  stroke="currentColor"
                  strokeWidth={isActive ? 1.2 : 0.7}
                  opacity={isActive ? 1 : 0.88}
                  vectorEffect="non-scaling-stroke" />
              );
            })}
          </svg>
          {ANATOMY_LABELS.map((it) => (
            <div
              key={`hot-${it.id}`}
              className={`anatomy-hot ${hl === it.id ? 'on' : ''}`}
              style={{ left: `${it.hot[0]}%`, top: `${it.hot[1]}%` }}
              onMouseEnter={() => setHl(it.id)}
              onMouseLeave={() => setHl(null)}
            />
          ))}
          {ANATOMY_LABELS.map((it) => (
            <div
              key={`lab-${it.id}`}
              className={`anatomy-label side-${it.side} ${it.short ? 'short' : ''} ${hl === it.id ? 'on' : ''}`}
              style={{
                left: `${it.label[0]}%`,
                top:  `${it.label[1]}%`,
                transform: SIDE_TRANSFORM[it.side],
                textAlign: SIDE_ALIGN[it.side],
              }}
              onMouseEnter={() => setHl(it.id)}
              onMouseLeave={() => setHl(null)}>
              <div className="name">{it.nameEn && lang === 'en' ? it.nameEn : it.name}</div>
              {it.tag && <div className="tag">{it.tagEn && lang === 'en' ? it.tagEn : it.tag}</div>}
              {it.sub && <div className="sub">{it.subEn && lang === 'en' ? it.subEn : it.sub}</div>}
            </div>
          ))}
        </div>
      </div>
      <div className="anatomy-mobile-list">
        {ANATOMY_LABELS.map((it) => (
          <div key={`mobile-${it.id}`}>
            <b>{it.nameEn && lang === 'en' ? it.nameEn : it.name}</b>
            {it.tag && <span>{it.tagEn && lang === 'en' ? it.tagEn : it.tag}</span>}
          </div>
        ))}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────
// 3D WIREFRAME DICE — slowly rotates, click to "roll"
// ────────────────────────────────────────────────────────────
const PIPS = {
  1: [[50, 50]],
  2: [[25, 25], [75, 75]],
  3: [[25, 25], [50, 50], [75, 75]],
  4: [[25, 25], [75, 25], [25, 75], [75, 75]],
  5: [[25, 25], [75, 25], [50, 50], [25, 75], [75, 75]],
  6: [[25, 25], [75, 25], [25, 50], [75, 50], [25, 75], [75, 75]],
};

function WireframeDice({ size = 240 }) {
  const [roll, setRoll] = useState({ rx: -22, ry: 28, t: 0 });
  const [faces, setFaces] = useState({ front: 1, back: 6, right: 3, left: 4, top: 2, bottom: 5 });

  // ambient slow spin
  useEffect(() => {
    let raf;
    let last = performance.now();
    const tick = (now) => {
      const dt = now - last;
      last = now;
      setRoll(r => ({
        rx: r.rx,
        ry: r.ry + dt * 0.012,
        t: r.t,
      }));
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);

  const handleRoll = () => {
    const sides = [1, 2, 3, 4, 5, 6];
    // Shuffle for entropy
    const shuffled = [...sides].sort(() => Math.random() - 0.5);
    setFaces({
      front: shuffled[0], back: 7 - shuffled[0],
      right: shuffled[1], left: 7 - shuffled[1],
      top:   shuffled[2], bottom: 7 - shuffled[2],
    });
    setRoll(r => ({ rx: r.rx + 360 + Math.random() * 90, ry: r.ry + 540 + Math.random() * 90, t: Date.now() }));
  };

  const half = size / 2;
  const cubeStyle = {
    width: size, height: size,
    transformStyle: 'preserve-3d',
    transform: `rotateX(${roll.rx}deg) rotateY(${roll.ry}deg)`,
    transition: Date.now() - roll.t < 1400 ? 'transform 1.4s cubic-bezier(.2,.7,.2,1)' : 'transform .1s linear',
  };
  const face = (n, t) => (
    <div className="dice-face" style={{
      width: size, height: size,
      position: 'absolute', left: 0, top: 0,
      border: '1.5px solid var(--ink)',
      background: 'rgba(241,237,229,0.5)',
      backdropFilter: 'blur(2px)',
      transform: t,
      display: 'grid', placeItems: 'center',
    }}>
      <div style={{ position: 'relative', width: '100%', height: '100%' }}>
        <svg viewBox="0 0 100 100" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', color: 'var(--ink)' }}>
          {/* pips — solid filled circles */}
          {PIPS[n].map(([cx, cy], i) => (
            <circle key={i} cx={cx} cy={cy} r="7" fill="currentColor" />
          ))}
        </svg>
      </div>
    </div>
  );

  return (
    <div
      onClick={handleRoll}
      style={{
        width: size, height: size,
        perspective: 1200,
        cursor: 'pointer',
        position: 'relative',
      }}>
      <div style={cubeStyle}>
        {face(faces.front,  `translateZ(${half}px)`)}
        {face(faces.back,   `rotateY(180deg) translateZ(${half}px)`)}
        {face(faces.right,  `rotateY(90deg) translateZ(${half}px)`)}
        {face(faces.left,   `rotateY(-90deg) translateZ(${half}px)`)}
        {face(faces.top,    `rotateX(90deg) translateZ(${half}px)`)}
        {face(faces.bottom, `rotateX(-90deg) translateZ(${half}px)`)}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────
// OS PICKER
// ────────────────────────────────────────────────────────────
const OSES = [
  {
    id: 'runos',
    name: 'RunOS',
    desc: 'raw rungler / nonlinear touch voices',
    descEn: 'raw rungler / nonlinear touch voices',
    mode: 'AS + Touch',
    file: 'RunOS_40.hex',
    manual: 'RunOS_40_manual_description.md',
    summary: 'Surowy, elektryczny system oparty o runglerowy silnik BiXo i dotykowe głosy nieliniowe. Dobry do niestabilnych sekwencji, impulsów, folded tones, pingujących filtrów i gestów pomiędzy perkusją, dronem i melodią.',
    summaryEn: 'A raw, electrical system built around the BiXo rungler engine and nonlinear touch voices. Made for unstable sequences, pulses, folded tones, pinging filters, and gestures between percussion, drone, and melody.',
    tags: ['BiXo', 'Trigfeto', 'Feton', 'Artefakto', 'Bulgo'],
  },
  {
    id: 'multos',
    name: 'MultOS',
    desc: 'multi-engine walk / mutation system',
    descEn: 'multi-engine walk / mutation system',
    mode: 'AS Walk + Touch',
    file: 'MultOS_40.hex',
    manual: 'MultOS_40_manual_description.md',
    summary: 'Wielosilnikowy OS performatywny: generatywny walk, skale, drift, mutacje i trzy poziomy losowości. Potrafi przejść od grywalnych klastrów do rozbitej maszyny rytmiczno-tonalnej.',
    summaryEn: 'A multi-engine performance OS: generative walk, scales, drift, mutation, and three randomness depths. It can move from playable clusters into a broken rhythmic-tonal machine.',
    tags: ['Wave Cluster', 'Glass FM', 'Chip Swarm', 'Bounce'],
  },
  {
    id: 'modalos',
    name: 'ModalOS',
    desc: 'resonator bodies / struck modes',
    descEn: 'resonator bodies / struck modes',
    mode: 'AS + Touch',
    file: 'ModalOS_40.hex',
    manual: 'ModalOS_40_manual_description.md',
    summary: 'Resonatory OS tłoczą surową energię kinetyczną systemu bezpośrednio w nieliniowe architektury sygnałowe: od naprężeń Straino i węzłów sieci Net-work, po rozproszenie Distribo i skompresowaną gęstość Multiplio.',
    summaryEn: 'Resonatory OS pushes the system’s raw kinetic energy directly into nonlinear signal architectures: from Straino tensions and Net-work nodes to Distribo dispersion and the compressed density of Multiplio.',
    tags: ['Straino', 'Net-work', 'Distribo', 'Multiplio'],
  },
  {
    id: 'nonlinos',
    name: 'NonLinOS',
    desc: 'nonlinear chaos / folded attractors',
    descEn: 'nonlinear chaos / folded attractors',
    mode: 'AS + Touch',
    file: 'NonLinOS_40.hex',
    manual: 'NonLinOS_40_manual_description.md',
    summary: 'Nieliniowy chaos OS: melodyczne atraktory, wavefolding, rezonansowe pingi, feedback i niestabilne głosy dotykowe. Bardziej agresywny i fizyczny, dobry do połamanych fraz, metalicznych sekwencji, glitchowej perkusji i dronów pod palcami.',
    summaryEn: 'A nonlinear chaos OS: melodic attractors, wavefolding, resonant pinging, feedback, and unstable touch voices. More aggressive and physical, made for bent phrases, metallic sequences, glitch percussion, and touch-shaped drones.',
    tags: ['Lorenz', 'Dadras', 'Duffing', 'Granular', 'LFSR', 'Rossler'],
  },
  {
    id: 'gentexos',
    name: 'GenTexOS',
    desc: 'feedback textures / folded noise beds',
    descEn: 'feedback textures / folded noise beds',
    mode: 'AS Texture + Touch',
    file: 'GenTexOS_40.hex',
    manual: 'GenTexOS_40_manual_description.md',
    summary: 'Nieliniowy generator tekstur do feedbacku, foldingu, filtrowania, micro-delay, smearing i bit reduction. Najlepszy do żywych stereo tekstur, szorstkich szumów i rezonujących kliknięć.',
    summaryEn: 'A nonlinear texture generator for feedback, folding, filtering, micro-delay, smearing, and bit reduction. Built for living stereo textures, rough noise beds, and resonant clicks.',
    tags: ['Freeze', 'Smear', 'Spike', 'Texture Touch'],
  },
];

function OSPicker() {
  const lang = usePageLang();
  const [pick, setPick] = useState(OSES[0]);
  const [copied, setCopied] = useState(false);
  const cmd = `curl -L https://sdrift.net/os/${pick.file} -o /tmp/${pick.file} \\\n  && teensy_loader_cli --mcu=TEENSY40 -w /tmp/${pick.file}`;

  const copy = async () => {
    try { await navigator.clipboard.writeText(cmd); } catch {}
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };

  return (
    <div className="os-layout">
      <div className="os-list">
        {OSES.map((os, i) => (
          <button
            key={os.id}
            className={`os-pick ${pick.id === os.id ? 'active' : ''}`}
            onClick={() => setPick(os)}>
            <div className="idx">0{i + 1}</div>
            <div className="meta">
              <div className="name">{os.name}</div>
              <div className="desc">{lang === 'en' ? os.descEn : os.desc}</div>
            </div>
            <div className="size">{os.mode}</div>
          </button>
        ))}
      </div>
      <div className="terminal">
        <div className="tbar">
          <span>~/sdrift/dicefriend — bash</span>
          <div className="dots">
            <div className="dot" /><div className="dot live" /><div className="dot" />
          </div>
        </div>
        <div className="body">
          <button className={`copy ${copied ? 'copied' : ''}`} onClick={copy}>
            {copied ? '✓ ok' : (lang === 'en' ? 'copy' : 'kopiuj')}
          </button>
          <a className="navi-button" href={`navi.html?os=${pick.id}`}>
            navi
          </a>
          <div className="os-summary">
            <h3>{pick.name}</h3>
            <p>{lang === 'en' ? pick.summaryEn : pick.summary}</p>
            <div className="os-tags">
              {pick.tags.map((tag) => <span key={tag}>{tag}</span>)}
            </div>
          </div>
          <pre><span className="prompt">$</span>{cmd}</pre>
          <div className="meta-line">
            {lang === 'en' ? '// downloads firmware ' : '// pobiera firmware '}<b>{pick.file}</b> · manual: {pick.manual}
          </div>
        </div>
        <div className="steps">
          <div className="step"><span className="n">01</span><span>{lang === 'en' ? 'hold ' : 'przytrzymaj '}<b>SHIFT + DICE</b>{lang === 'en' ? ' for 10 sec' : ' przez 10 sek'} — bootloader mode</span></div>
          <div className="step"><span className="n">02</span><span>{lang === 'en' ? 'choose ' : 'wybierz '}<b>OS</b>{lang === 'en' ? ' from the list' : ' z listy'}</span></div>
          <div className="step"><span className="n">03</span><span>{lang === 'en' ? 'copy the ' : 'skopiuj '}<b>{lang === 'en' ? 'command' : 'komendę'}</b></span></div>
          <div className="step"><span className="n">04</span><span>{lang === 'en' ? 'paste and run it in terminal' : 'wklej i uruchom ją w terminalu'}</span></div>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────
// MOUNT
// ────────────────────────────────────────────────────────────
const mounts = [
  ['anatomy-root',     <Anatomy />],
  ['os-root',          <OSPicker />],
  ['dice-root',        <WireframeDice size={Math.min(420, window.innerWidth * 0.55)} />],
];
mounts.forEach(([id, el]) => {
  const node = document.getElementById(id);
  if (node) ReactDOM.createRoot(node).render(el);
});
