/* THEM — main app (v2) */

const { useState, useEffect, useRef } = React;

// ---------- ASCII decorative helpers ----------
const A = {
  sparkle: "✦",
  cross: "+",
  star4: "✦",
  tilde: "~",
  slash: "///",
  bracket: "[ ]",
  dash: "---",
  at: "@",
  hash: "#",
  eye: "><",
};

// ---------- Cursor glow ----------
function CursorGlow() {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    const move = (e) => {if (el) {el.style.left = e.clientX + "px";el.style.top = e.clientY + "px";}};
    const out = () => {if (el) el.style.opacity = "0";};
    const over = () => {if (el) el.style.opacity = "1";};
    window.addEventListener("mousemove", move);
    window.addEventListener("mouseleave", out);
    window.addEventListener("mouseenter", over);
    return () => {
      window.removeEventListener("mousemove", move);
      window.removeEventListener("mouseleave", out);
      window.removeEventListener("mouseenter", over);
    };
  }, []);
  return <div ref={ref} className="cursor-glow" />;
}

// ---------- Audio toggle ----------
function AudioToggle() {
  const [on, setOn] = useState(false);
  const ctxRef = useRef(null);
  const nodesRef = useRef(null);

  const start = () => {
    const AC = window.AudioContext || window.webkitAudioContext;
    const ctx = new AC();ctxRef.current = ctx;
    const master = ctx.createGain();master.gain.value = 0;master.connect(ctx.destination);
    const o1 = ctx.createOscillator();o1.type = "sine";o1.frequency.value = 110;
    const o2 = ctx.createOscillator();o2.type = "sine";o2.frequency.value = 164;
    const o3 = ctx.createOscillator();o3.type = "triangle";o3.frequency.value = 55;
    const lp = ctx.createBiquadFilter();lp.type = "lowpass";lp.frequency.value = 600;lp.Q.value = 0.5;
    const g = ctx.createGain();g.gain.value = 0.08;
    o1.connect(g);o2.connect(g);o3.connect(g);g.connect(lp);lp.connect(master);
    const lfo = ctx.createOscillator();lfo.frequency.value = 0.08;
    const lfoGain = ctx.createGain();lfoGain.gain.value = 200;
    lfo.connect(lfoGain);lfoGain.connect(lp.frequency);
    o1.start();o2.start();o3.start();lfo.start();
    master.gain.linearRampToValueAtTime(0.55, ctx.currentTime + 2);
    nodesRef.current = { master, o1, o2, o3, lfo };
  };
  const stop = () => {
    const nodes = nodesRef.current;const ctx = ctxRef.current;
    if (!nodes || !ctx) return;
    nodes.master.gain.linearRampToValueAtTime(0, ctx.currentTime + 1);
    setTimeout(() => {try {nodes.o1.stop();nodes.o2.stop();nodes.o3.stop();nodes.lfo.stop();ctx.close();} catch (e) {}}, 1100);
    nodesRef.current = null;ctxRef.current = null;
  };
  const toggle = () => {if (on) {stop();setOn(false);} else {start();setOn(true);}};

  return (
    <div className={"audio-toggle " + (on ? "playing" : "")} onClick={toggle} title={on ? "mute ambient" : "play ambient hum"}>
      <div className="bars"><span /><span /><span /><span /></div>
    </div>);

}

// ---------- BSOD + FakeMint ----------
function BSOD({ onDone }) {
  const [fading, setFading] = useState(false);
  useEffect(() => {
    // hide nav while BSOD is visible
    const nav = document.querySelector(".nav");
    if (nav) nav.style.opacity = "0";
    const t1 = setTimeout(() => setFading(true), 4600);
    const t2 = setTimeout(() => {
      if (nav) nav.style.opacity = "";
      window.dispatchEvent(new CustomEvent("bsod-done"));
      onDone();
    }, 5000);
    return () => {
      clearTimeout(t1); clearTimeout(t2);
      if (nav) nav.style.opacity = "";
    };
  }, []);
  return (
    <div className={"bsod-overlay" + (fading ? " bsod-fade" : "")}>
      <div className="bsod-text">
        <p className="bsod-title">&nbsp;Windows&nbsp;</p>
        <p>A fatal error has occurred.</p>
        <p>THEM has been shut down to protect your wallet.</p>
        <br/>
        <p>MINT_NOT_READY_YET.exe</p>
        <br/>
        <p>you were too early.</p>
        <p>or maybe exactly on time.</p>
        <p>we don't know yet.</p>
        <br/>
        <p>Technical information:</p>
        <p>*** STOP: 0x0000THEM (0x00000003, 0x0000WANT, 0x0000MINT)</p>
        <br/>
        <p className="bsod-blink">collecting your patience... ████░░░░ 43% _</p>
        <br/>
        <p>ха-ха. mint coming soon. запасись терпением ✦</p>
      </div>
    </div>
  );
}

function FakeMint() {
  const [bsod, setBsod] = useState(false);
  return (
    <>
      <button className="fake-mint-btn" onClick={() => setBsod(true)}>
        ✦ mint now ✦
      </button>
      {bsod && ReactDOM.createPortal(
        <BSOD onDone={() => setBsod(false)} />,
        document.body
      )}
    </>
  );
}

// ---------- Win95 Popup (annoying) ----------
const MSGS = [
  { title: "⚠ warning.exe",      l1: "they found you.",         l2: <>you are one of <em>them</em> now.</>, b1: "i knew it",  b2: "oh no"    },
  { title: "⚠ still here?",      l1: "you can't close this.",   l2: <>the window belongs to <em>them</em>.</>, b1: "yes i can", b2: "fine"    },
  { title: "⚠ last warning",     l1: "ok fine. almost.",        l2: <>just remember us ✦</>,               b1: "bye",        b2: "ok ok"    },
];

function Win95Popup() {
  const [visible, setVisible]   = useState(false);
  const [phase, setPhase]       = useState(0);
  const [glitch, setGlitch]     = useState(false);
  const [btnEsc, setBtnEsc]     = useState({});
  const [pos, setPos]           = useState({ x: null, y: null });
  const dragging = useRef(false);
  const offset   = useRef({ x: 0, y: 0 });
  const popupRef = useRef(null);
  const mouseRef = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });

  useEffect(() => {
    const track = (e) => { mouseRef.current = { x: e.clientX, y: e.clientY }; };
    window.addEventListener("mousemove", track);
    return () => window.removeEventListener("mousemove", track);
  }, []);

  useEffect(() => {
    const t = setTimeout(() => {
      setPos({ x: Math.max(20, Math.floor(window.innerWidth * 0.14)), y: Math.max(60, Math.floor(window.innerHeight * 0.38)) });
      setVisible(true);
    }, 5500);
    return () => clearTimeout(t);
  }, []);

  // dragging
  const onTitleDown = (e) => {
    dragging.current = true;
    const r = popupRef.current.getBoundingClientRect();
    offset.current = { x: e.clientX - r.left, y: e.clientY - r.top };
  };
  useEffect(() => {
    const mv = (e) => { if (dragging.current) setPos({ x: e.clientX - offset.current.x, y: e.clientY - offset.current.y }); };
    const up = () => { dragging.current = false; };
    window.addEventListener("mousemove", mv);
    window.addEventListener("mouseup", up);
    return () => { window.removeEventListener("mousemove", mv); window.removeEventListener("mouseup", up); };
  }, []);

  // random position anywhere on screen (safe margins)
  const randomPos = () => {
    const pw = popupRef.current ? popupRef.current.offsetWidth  : 280;
    const ph = popupRef.current ? popupRef.current.offsetHeight : 160;
    return {
      x: Math.floor(Math.random() * Math.max(40, window.innerWidth  - pw - 40) + 20),
      y: Math.floor(Math.random() * Math.max(40, window.innerHeight - ph - 40) + 40),
    };
  };

  // button escapes on hover — original hard difficulty
  const escape = (id) => setBtnEsc(p => ({
    ...p,
    [id]: { x: (Math.random() - 0.5) * 140, y: (Math.random() - 0.5) * 90 },
  }));

  const tryClose = () => {
    setGlitch(true);
    setTimeout(() => {
      setGlitch(false);
      if (phase < MSGS.length - 1) {
        setVisible(false);
        setBtnEsc({});
        const next = phase + 1;
        setPhase(next);
        const delay = next === 1 ? 2200 : 1400;
        setTimeout(() => {
          setPos(randomPos());   // new random position each time
          setVisible(true);
        }, delay);
      } else {
        setVisible(false);
      }
    }, 480);
  };

  if (!visible) return null;
  const msg = MSGS[phase];
  const wrapStyle = pos.x !== null ? { position: "fixed", left: pos.x, top: pos.y, right: "auto" } : {};

  const btnStyle = (id) => ({
    transform: btnEsc[id] ? `translate(${btnEsc[id].x}px,${btnEsc[id].y}px)` : "none",
    transition: "transform 0.13s ease",
    position: "relative",
    zIndex: 2,
  });

  return (
    <div className={"win95-wrap" + (glitch ? " glitching" : "")} style={wrapStyle} ref={popupRef}>
      <div className="win95-popup">
        <div className="win95-titlebar" onMouseDown={onTitleDown} style={{ cursor: "move" }}>
          <span>{msg.title}</span>
          <button
            className="win95-close"
            style={btnStyle("x")}
            onMouseEnter={() => escape("x")}
            onClick={tryClose}
          >✕</button>
        </div>
        <div className="win95-body">
          <span className="win95-icon">⚠</span>
          <div>
            <p>{msg.l1}</p>
            <p>{msg.l2}</p>
          </div>
        </div>
        <div className="win95-btns">
          <button style={btnStyle("b1")} onMouseEnter={() => escape("b1")} onClick={tryClose}>{msg.b1}</button>
          <button style={btnStyle("b2")} onMouseEnter={() => escape("b2")} onClick={tryClose}>{msg.b2}</button>
        </div>
      </div>
    </div>
  );
}

// ---------- Nav ----------
function Nav({ onLogoClick }) {
  return (
    <nav className="nav">
      <div className="nav-logo" onClick={onLogoClick}>them<sup>01</sup></div>
      <div className="nav-links">
        <a href="#world">world</a>
        <a href="#gallery">faces</a>
        <a href="#phases">mint</a>
        <a href="#roadmap">roadmap</a>
        <a href="#petra">petra</a>
      </div>
      <button className="nav-cta" onClick={() => document.getElementById("phases")?.scrollIntoView({ behavior: "smooth" })}>
        <span className="dot" /> mint · may 2026
      </button>
    </nav>);

}

// ---------- Hero ----------
function Hero() {
  return (
    <section className="hero" data-screen-label="01 hero">
      <div className="wrap">
        <div className="hero-inner">
          <div className="hero-left" style={{ position: "relative" }}>
            <div className="hero-kaomoji">₍ᐢ. .ᐢ₎ ✦ &nbsp; welcome to the world of them</div>
            <h1 className="hero-title">them</h1>
            <p className="hero-tagline">
              strange little entities, our alter egos hiding in oversized hoodies, weird hats, digital noise, and gloomy landscapes
            </p>
            <div className="hero-meta">
              <span>3,333 supply</span>
              <span>400+ traits</span>
              <span>may · 2026</span>
            </div>
            <div className="hero-platform">
              minting on <a href="https://scatter.art" target="_blank" rel="noopener">scatter.art</a>
            </div>
            <FakeMint />

            <div className="ascii-float wiggle" style={{top:"-12px", left:"-36px", fontSize:"36px"}}>✦</div>
            <div className="ascii-float rot-a" style={{top:"30%", right:"-28px", fontSize:"22px", opacity:0.5}}>+--+</div>
            <div className="y2k-note pink" style={{top:"42%", right:"-10px", transform:"rotate(8deg)"}}>so cute *</div>
            <div className="ascii-float spin" style={{bottom:"12%", left:"-44px", fontSize:"28px", opacity:0.6}}>✦</div>
          </div>

          <div className="hero-right">
            <div className="hero-char-stage">
              <div className="hero-char-halo" />
              <div className="hero-char-main"><img src="assets/them-01.jpg" alt="them" /></div>
              <div className="hero-orbit o1"><img src="assets/them-03.jpg" alt="" /></div>
              <div className="hero-orbit o2"><img src="assets/them-04.jpg" alt="" /></div>
              <div className="hero-orbit o3"><img src="assets/them-02.jpg" alt="" /></div>

              <div className="sticker sticker-dark" style={{ bottom: "8%", right: "-2%", transform: "rotate(4deg)" }}>
                ✦ 1 / 3333
              </div>
              <div className="sticker" style={{ top: "44%", left: "-6%", transform: "rotate(-8deg)" }}>
                adopt one
              </div>
              <div className="ascii-float wiggle" style={{top:"-4%", right:"14%", fontSize:"32px"}}>✦</div>
              <div className="ascii-float rot-b" style={{bottom:"-2%", left:"14%", fontSize:"13px", letterSpacing:"3px", opacity:0.5}}>~ ~ ~ ~ ~</div>
              <div className="ascii-float rot-a" style={{top:"8%", left:"-4%", fontSize:"16px", opacity:0.4}}>[ ]</div>
              <div className="y2k-note dark" style={{bottom:"-8%", right:"6%", transform:"rotate(-4deg)"}}>too sweet to be scary</div>
            </div>
          </div>
        </div>
      </div>
    </section>);

}

// ---------- Ticker ----------
function Ticker() {
  const phrase = "too sweet to be scary ✦ too strange to be safe ✦ a bit fragile ✦ a bit naive ✦ mischief is their currency ✦ you might not sleep much ✦ ";
  return (
    <div className="ticker">
      <div className="ticker-track">
        <span>{phrase}{phrase}</span>
        <span>{phrase}{phrase}</span>
      </div>
    </div>);

}

// ---------- World ----------
function World() {
  return (
    <section className="section" id="world" data-screen-label="02 world">
      <div className="wrap" style={{position:"relative"}}>
        <div className="ascii-float rot-a" style={{top:"40px", right:"-20px", fontSize:"16px", opacity:0.5, color:"var(--rose-deep)"}}>--&gt;&gt;</div>
        <div className="y2k-note" style={{top:"60px", left:"-40px", transform:"rotate(-10deg)"}}>peek inside</div>
        <div className="world-grid">
          <div className="world-prose">
            <div className="kicker">the concept</div>
            <h2 className="section-title">the world of them</h2>
            <p className="lead">
              small enough to fit in your pocket, loud enough to haunt your feed.
            </p>
            <p>
              too sweet to be scary, too strange to be safe. they live for harmless chaos, turning quiet rooms into strange little worlds.
            </p>
            <p>
              they'll braid your hair while hiding scissors in their sleeves, paint your walls but spell messages backwards, laugh at things you don't find funny yet.
            </p>
            <div className="pull-msg">
              <div className="pull-msg-title">💬 them.exe — message</div>
              <div className="pull-msg-body">adopt one, and you'll never be bored again. you might not sleep much, either</div>
            </div>
            <div className="ascii-deco">✦ &nbsp; ✦ &nbsp; ✦</div>
          </div>

          <div className="world-collage">
            <div className="world-tile"><img src="assets/them-05.jpg" alt="" /><div className="tile-label">no. 100</div></div>
            <div className="world-tile"><img src="assets/them-06.jpg" alt="" /><div className="tile-label">no. 028</div></div>
            <div className="world-tile"><img src="assets/them-07.jpg" alt="" /><div className="tile-label">no. 045</div></div>
            <div className="world-tile"><img src="assets/them-08.jpg" alt="" /><div className="tile-label">no. 025</div></div>
          </div>
        </div>
      </div>
    </section>);

}

// ---------- Thought bubble ----------
function ThoughtBubble() {
  return (
    <div className="thought-bubble-wrap" aria-label="adopt one, and you'll never be bored again. you might not sleep much, either">
      <svg className="thought-bubble-svg" viewBox="0 0 340 175" xmlns="http://www.w3.org/2000/svg">
        {/* main cloud body — bumpy top edge */}
        <path
          d="M 32,128
             Q  8,128  6,106
             Q  4, 85 22, 80
             Q 16, 54 36, 46
             Q 42, 18 72, 22
             Q 84,  4 112, 11
             Q 132,  0 158, 12
             Q 182,  2 204, 18
             Q 228, 10 234, 36
             Q 256, 32 260, 56
             Q 278, 58 278, 78
             Q 278, 98 262,104
             Q 258,126 234,128
             Z"
          fill="rgba(255,255,255,0.76)"
        />
        {/* thought-bubble trailing dots */}
        <circle cx="215" cy="144" r="8"   fill="rgba(255,255,255,0.68)" />
        <circle cx="232" cy="158" r="5.5" fill="rgba(255,255,255,0.58)" />
        <circle cx="244" cy="169" r="3.5" fill="rgba(255,255,255,0.48)" />
      </svg>
      <p className="thought-bubble-text">
        adopt one, and you'll never be bored again.<br/>you might not sleep much, either ✦
      </p>
    </div>
  );
}

// ---------- Soft clouds background for gallery ----------
// Each cloud: position (x/y %), size (w/h px), drift offset when hidden (tx/ty px),
// idle float duration, stagger delay for appear transition
const CLOUDS_CFG = [
  { w: 220, h: 80,  x: "4%",   y: "8%",  tx: -120, ty: -40, dur: 17, delay: 0.0  },
  { w: 160, h: 65,  x: "68%",  y: "4%",  tx:  130, ty: -30, dur: 21, delay: 0.25 },
  { w: 120, h: 50,  x: "38%",  y: "20%", tx:  -30, ty: -60, dur: 14, delay: 0.5  },
  { w: 190, h: 72,  x: "76%",  y: "48%", tx:  130, ty:  30, dur: 19, delay: 0.15 },
  { w: 140, h: 58,  x: "10%",  y: "62%", tx: -110, ty:  40, dur: 23, delay: 0.4  },
  { w: 100, h: 42,  x: "52%",  y: "72%", tx:   50, ty:  60, dur: 16, delay: 0.6  },
  { w: 240, h: 88,  x: "22%",  y: "38%", tx:  -50, ty: -25, dur: 25, delay: 0.35 },
  { w: 130, h: 52,  x: "58%",  y: "85%", tx:   90, ty:  40, dur: 18, delay: 0.1  },
];

function GalleryFog() {
  const [visible, setVisible] = useState(false);
  // refs for direct-DOM parallax (avoids React re-renders on every scroll tick)
  const parallaxRefs = useRef([]);

  useEffect(() => {
    const el = document.getElementById("gallery");
    if (!el) return;

    // ── appear/disappear via IntersectionObserver ──
    const obs = new IntersectionObserver(
      ([entry]) => setVisible(entry.isIntersecting),
      { threshold: 0.08, rootMargin: "0px 0px -60px 0px" }
    );
    const raf = requestAnimationFrame(() => obs.observe(el));

    // ── parallax: direct DOM transform on scroll (no React state = no lag) ──
    const onScroll = () => {
      const rect  = el.getBoundingClientRect();
      const vh    = window.innerHeight;
      // progress 0 → top of section at bottom of screen; 1 → bottom of section at top
      const prog  = (-rect.top + vh) / (rect.height + vh);
      const base  = (prog - 0.5) * 140; // ±70 px total range

      CLOUDS_CFG.forEach((c, i) => {
        const ref = parallaxRefs.current[i];
        if (!ref) return;
        // alternating direction + depth via size — bigger clouds move less (further away)
        const dir   = i % 2 === 0 ? 1 : -1;
        const speed = 0.35 + (c.w / 800);
        ref.style.transform = `translateY(${base * speed * dir}px)`;
      });
    };

    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll(); // set initial position

    return () => {
      cancelAnimationFrame(raf);
      obs.disconnect();
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return (
    <div className="gallery-fog" aria-hidden="true">
      <div className="gallery-sky" style={{
        opacity: visible ? 1 : 0,
        transition: "opacity 1.6s ease",
      }} />
      {CLOUDS_CFG.map((c, i) => (
        // OUTER DIV — React appear animation (opacity + slide in from edge)
        <div
          key={i}
          style={{
            position: "absolute",
            left: c.x,
            top:  c.y,
            opacity:    visible ? 1 : 0,
            transform:  visible ? "translate(0,0)" : `translate(${c.tx}px,${c.ty}px)`,
            transition: `opacity 1.5s ${c.delay}s ease, transform 1.8s ${c.delay}s cubic-bezier(.15,.8,.2,1)`,
            pointerEvents: "none",
            willChange: "opacity, transform",
          }}
        >
          {/* MIDDLE DIV — direct-DOM parallax, no CSS transition so it's instant+smooth */}
          <div ref={el => parallaxRefs.current[i] = el} style={{ willChange: "transform" }}>
            {/* INNER DIV — CSS idle float keyframe */}
            <div
              className="gallery-cloud-inner"
              style={{ width: c.w, height: c.h, animationDuration: c.dur + "s" }}
            />
          </div>
        </div>
      ))}
    </div>
  );
}

// ---------- Gallery ----------
function Gallery() {
  const items = [
    { img: "assets/them-09.jpg",  num: "847"  },
    { img: "assets/them-10.jpg",  num: "1203" },
    { img: "assets/them-11.jpg",  num: "2891" },
    { img: "assets/them-12.jpg",  num: "44"   },
    { img: "assets/them-01.jpg",  num: "1567" },
    { img: "assets/them-14.jpg",  num: "729"  },
    { img: "assets/them-15.jpg",  num: "2013" },
    { img: "assets/them-16.jpg",  num: "388"  },
    { img: "assets/them-03.jpg",  num: "1901" },
    { img: "assets/them-18.jpg",  num: "642"  },
  ];

  return (
    <section className="section" id="gallery" data-screen-label="03 gallery">
      <GalleryFog />
      <div className="wrap">
        <div className="kicker">meet the entities</div>
        <h2 className="section-title">faces in the fog</h2>
        <div className="gallery-strip">
          {items.map((it, i) => (
            <div key={i} className="gallery-card">
              <img src={it.img} alt={"them " + it.num} />
              <div className="num">#{it.num}</div>
              <div className="heart">♡</div>
            </div>
          ))}
        </div>
        <div className="gallery-strip gallery-secret">
          {[
            { oneOfOne: true,  bg: "assets/angel_in_clouds.png" },
            { oneOfOne: false, bg: "assets/them-20.jpg" },
            { oneOfOne: true,  bg: "assets/sailor_moon.png" },
            { oneOfOne: false, bg: "assets/them-22.jpg" },
            { oneOfOne: true,  bg: "assets/miku.png" },
          ].map((card, i) => (
            <div key={i} className="gallery-card gallery-classified">
              <img src={card.bg} className="classified-bg" alt="" />
              <div className="classified-overlay" />
              <div className="classified-label">???</div>
              <div className="num">#???</div>
              {card.oneOfOne && <div className="one-of-one">1/1</div>}
            </div>
          ))}
        </div>
        <div className="gallery-meta">
          <span>first glimpse · 10 of <strong>3,333</strong> revealed</span>
          <span>400+ traits</span>
        </div>
      </div>
    </section>
  );
}

// ---------- Foggy countdown digit — static value, just heavily blurred ----------
function FoggyNum({ value, foggy }) {
  return <div className={"countdown-num" + (foggy ? " foggy" : "")}>{value}</div>;
}

// ---------- Vault countdown (real → May 21 2026 15:00 UTC) ----------
function Vault() {
  const target  = Date.UTC(2026, 4, 21, 15, 0, 0); // May 21 2026 15:00 UTC
  const reveal  = Date.UTC(2099, 0, 1, 0, 0, 0);   // far future — date stays hidden until manually opened
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);
  const diff = Math.max(0, target - now);
  const d = Math.floor(diff / (1000 * 60 * 60 * 24));
  const h = Math.floor(diff / (1000 * 60 * 60) % 24);
  const m = Math.floor(diff / (1000 * 60) % 60);
  const s = Math.floor(diff / 1000 % 60);
  const pad = (n) => n.toString().padStart(2, "0");
  const foggy = now < reveal; // blur until May 1

  return (
    <div className="vault">
      <div>
        <div className="vault-label">✦ the date is sealed</div>
        <div className="vault-title">soon, my loves</div>
        <div className="vault-sub">the vault opens in may. the exact hour is hidden behind fog. it will reveal itself when the moment is right.</div>
      </div>
      <div className="countdown">
        <div className="countdown-box"><FoggyNum value={pad(d)} foggy={foggy} /><div className="countdown-label">days</div></div>
        <div className="countdown-box"><FoggyNum value={pad(h)} foggy={foggy} /><div className="countdown-label">hours</div></div>
        <div className="countdown-box"><FoggyNum value={pad(m)} foggy={foggy} /><div className="countdown-label">minutes</div></div>
        <div className="countdown-box"><FoggyNum value={pad(s)} foggy={foggy} /><div className="countdown-label">seconds</div></div>
      </div>
    </div>);
}

// ---------- Phases ----------
function Phases() {
  const phases = [
  { n: "phase 01", name: "guaranteed", sub: <span>owners of previous collections (<a href="https://opensea.io/collection/babes2024" target="_blank" rel="noopener">babes</a> <em style={{fontStyle:"normal",opacity:0.7,fontSize:"0.9em"}}>(min. 20)</em>, <a href="https://element.market/collections/boys?search%5Btoggles%5D%5B0%5D=ALL" target="_blank" rel="noopener">boys</a>, <a href="https://rarible.com/collection/0xd51b912190d55f9e817ae9d1d33e747566011ab7/drops" target="_blank" rel="noopener">online angels</a>), giveaway winners, puzzle solvers, and discord role holders. <em style={{fontStyle:"normal",opacity:0.6,fontSize:"0.9em"}}>snapshot: may 9.</em></span>, status: "open" },
  { n: "phase 02", name: "allowlist", sub: <span>first come, first served. <a href="#recs" onClick={e => { e.preventDefault(); document.querySelector('.section.recs').scrollIntoView({ behavior: 'smooth' }); }}>holders of friendly collections</a> are eligible.</span>, status: "open" },
  { n: "phase 03", name: "public", sub: "doors open wide. mint until the last one finds a home.", status: "locked" }];

  return (
    <section className="section" id="phases" data-screen-label="04 phases">
      <div className="wrap">
        <div className="kicker">the mint · three phases</div>
        <h2 className="section-title">how to mint</h2>

        <div className="phases-grid">
          {phases.map((p, i) =>
          <div key={i} className="phase-card">
              <div>
                <div className="phase-num"><span className={p.status === "open" ? "lock open" : "lock"} /> {p.n}</div>
                <div className="phase-name">{p.name}</div>
                <div className="phase-name-sub">{p.sub}</div>
              </div>
              <div className="phase-reveal">
                <span>{p.status === "open" ? "revealed" : "date revealing soon"}</span>
                <span className="status" data-status={p.status}>{p.status}</span>
              </div>
            </div>
          )}
        </div>

        <Vault />
      </div>
    </section>);

}

// ---------- Wallet Checker ----------
// To activate: flip CHECKER_ACTIVE = true (whitelist.json is already in assets/)
const CHECKER_ACTIVE = false;

const CHECKER_CLOUDS = [
  { w: 200, h: 72,  x: "3%",  y: "10%", tx: -110, ty: -35, dur: 19, delay: 0.0  },
  { w: 140, h: 52,  x: "62%", y: "6%",  tx:  120, ty: -25, dur: 23, delay: 0.25 },
  { w: 110, h: 44,  x: "32%", y: "52%", tx:  -40, ty:  55, dur: 16, delay: 0.5  },
  { w: 170, h: 62,  x: "78%", y: "55%", tx:  130, ty:  35, dur: 21, delay: 0.15 },
  { w: 90,  h: 36,  x: "18%", y: "68%", tx:  -85, ty:  45, dur: 17, delay: 0.4  },
];

function CheckerFog() {
  const [visible, setVisible] = useState(false);
  const parallaxRefs = useRef([]);

  useEffect(() => {
    const el = document.getElementById("checker");
    if (!el) return;

    // appear via IntersectionObserver
    const obs = new IntersectionObserver(
      ([entry]) => setVisible(entry.isIntersecting),
      { threshold: 0.06 }
    );
    const raf = requestAnimationFrame(() => obs.observe(el));

    // parallax: direct DOM, no React state → no lag
    const onScroll = () => {
      const rect  = el.getBoundingClientRect();
      const vh    = window.innerHeight;
      const prog  = (-rect.top + vh) / (rect.height + vh);
      const base  = (prog - 0.5) * 120;
      CHECKER_CLOUDS.forEach((c, i) => {
        const ref = parallaxRefs.current[i];
        if (!ref) return;
        const dir   = i % 2 === 0 ? 1 : -1;
        const speed = 0.3 + (c.w / 800);
        ref.style.transform = `translateY(${base * speed * dir}px)`;
      });
    };

    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();

    return () => {
      cancelAnimationFrame(raf);
      obs.disconnect();
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return (
    <div className="checker-fog" aria-hidden="true">
      <div className="checker-fog-sky" style={{ opacity: visible ? 1 : 0, transition: "opacity 1.8s ease" }} />
      {CHECKER_CLOUDS.map((c, i) => (
        // OUTER — React appear animation
        <div key={i} style={{
          position: "absolute", left: c.x, top: c.y,
          opacity:   visible ? 1 : 0,
          transform: visible ? "translate(0,0)" : `translate(${c.tx}px,${c.ty}px)`,
          transition: `opacity 1.5s ${c.delay}s ease, transform 2s ${c.delay}s cubic-bezier(.15,.8,.2,1)`,
          pointerEvents: "none",
          willChange: "opacity, transform",
        }}>
          {/* MIDDLE — direct-DOM parallax */}
          <div ref={el => parallaxRefs.current[i] = el} style={{ willChange: "transform" }}>
            {/* INNER — CSS idle float */}
            <div className="gallery-cloud-inner" style={{ width: c.w, height: c.h, animationDuration: c.dur + "s" }} />
          </div>
        </div>
      ))}
    </div>
  );
}

function WalletChecker() {
  const [wallet, setWallet]   = useState("");
  const [result, setResult]   = useState(null);
  const [loading, setLoading] = useState(false);

  const check = async () => {
    if (!wallet.trim()) return;
    setLoading(true); setResult(null);
    try {
      const res  = await fetch("assets/whitelist.json");
      const data = await res.json();
      const addr = wallet.trim().toLowerCase();
      if ((data.guaranteed || []).map(a => a.toLowerCase()).includes(addr)) {
        setResult("guaranteed");
      } else if ((data.allowlist || []).map(a => a.toLowerCase()).includes(addr)) {
        setResult("allowlist");
      } else {
        setResult("none");
      }
    } catch (e) {
      setResult("error");
    }
    setLoading(false);
  };

  const RESULT_MSG = {
    guaranteed: { icon: "✦", label: "guaranteed",  text: "you're on the guaranteed list. phase 01 is yours." },
    allowlist:  { icon: "♡", label: "allowlist",   text: "you're on the allowlist. phase 02, first come first served." },
    none:       { icon: "·", label: "not listed",  text: "this wallet isn't on the list. phase 03 public mint will be open to everyone." },
    error:      { icon: "·", label: "error",       text: "couldn't load the list. try again later." },
  };

  return (
    <section className="section recs" id="checker" data-screen-label="wallet checker">
      <CheckerFog />
      <div className="wrap" style={{position:"relative"}}>
        <div className="checker-window">
          <div className="checker-titlebar">
            <span>🔍 C:\them\access — checker.exe</span>
            <div className="recs-win-btns">
              <span title="minimize">_</span>
              <span title="maximize">□</span>
              <span title="close">×</span>
            </div>
          </div>
          <div className={"checker-winbody" + (!CHECKER_ACTIVE ? " is-locked" : "")}>
            <h2 className="section-title">wallet checker</h2>
            <p className="recs-intro">
              enter your wallet address to see if you're on the guaranteed or allowlist.
            </p>

            {!CHECKER_ACTIVE && (
              <div className="checker-locked-popup">
                <div className="checker-locked-title">
                  <span>⚠ access.exe</span>
                  <div className="recs-win-btns"><span>_</span><span>□</span><span>×</span></div>
                </div>
                <div className="checker-locked-body">
                  <span className="checker-locked-icon">🔒</span>
                  <div>
                    <p>this function is not available yet.</p>
                    <p>check back closer to mint date.</p>
                  </div>
                </div>
                <div className="checker-locked-btns"><button>ok</button></div>
              </div>
            )}

            <div className={"checker-fields" + (!CHECKER_ACTIVE ? " checker-inactive" : "")}>
              <div className="checker-row">
                <input
                  className="checker-input"
                  type="text"
                  placeholder="0x..."
                  value={wallet}
                  onChange={e => { setWallet(e.target.value); setResult(null); }}
                  onKeyDown={e => e.key === "Enter" && check()}
                  disabled={!CHECKER_ACTIVE}
                  spellCheck={false}
                  autoComplete="off"
                />
                <button
                  className="checker-btn"
                  onClick={check}
                  disabled={!CHECKER_ACTIVE || loading || !wallet.trim()}
                >
                  {loading ? "..." : "check"}
                </button>
              </div>

              {result && RESULT_MSG[result] && (
                <div className={"checker-result checker-result-" + result}>
                  <span className="checker-result-icon">{RESULT_MSG[result].icon}</span>
                  <div>
                    <div className="checker-result-label">{RESULT_MSG[result].label}</div>
                    <div className="checker-result-text">{RESULT_MSG[result].text}</div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- Recommended collections ----------
function Recs() {
  const list = [
    ["Milady Maker","https://opensea.io/collection/milady"],
    ["Redacted Remilio Babies","https://opensea.io/collection/remilio-babies"],
    ["Milady Aura","https://opensea.io/collection/miladyaura"],
    ["Retart","https://opensea.io/collection/retarted"],
    ["Radbro Webring","https://opensea.io/collection/radbro-webring"],
    ["Schizoposters","https://opensea.io/collection/schizoposters"],
    ["Pixelady Maker","https://opensea.io/collection/pixeladymaker/explore"],
    ["World Computer Netizens","https://opensea.io/collection/world-computer-netizens-megaeth"],
    ["Mi Note 2","https://opensea.io/collection/mi-note2"],
    ["Kemonokaki","https://opensea.io/collection/kemonokaki"],
    ["Yumemono","https://opensea.io/collection/yumemono"],
    ["Skrumpeys","https://opensea.io/collection/skrumpeys"],
    ["babes (min. 3)","https://opensea.io/collection/babes2024"],
    ["Miawlady Maker","https://opensea.io/collection/miawlady-maker"],
    ["MoeMoe LLC","https://opensea.io/collection/moemoe-llc"],
    ["Oh, I've seen","https://opensea.io/collection/oh-i-ve-seen"],
    ["Normies","https://opensea.io/collection/normies"],
    ["Mezzanottes","https://opensea.io/collection/mezzanottes"],
    ["Monafuku Cafe Monad","https://opensea.io/collection/monafuku-cafe-monad"],
    ["Love ♡ Idols","https://opensea.io/collection/love-idols"],
    ["snow melts into spring...","https://opensea.io/collection/snow-melts-into-spring-because-god-loves-you"],
    ["Lightka","https://opensea.io/collection/lightka"],
    ["Steady Teddys","https://opensea.io/collection/steady-teddys-bera"],
    ["Camel Cabal","https://opensea.io/collection/camelcabal"],
    ["r3tards","https://opensea.io/collection/r3tardsnft"],
    ["Shishi","https://opensea.io/collection/shishi520"],
    ["Ahh","https://opensea.io/collection/ahh-"],
    ["reCAPTCHACHIBIS","https://opensea.io/collection/recaptchachibis"],
  ];
  return (
    <section className="section recs" data-screen-label="05 recs">
      <div className="wrap" style={{position:"relative"}}>
        <div className="recs-window">
          <div className="recs-titlebar">
            <span>📁 C:\them\friends — bookmarks.exe</span>
            <div className="recs-win-btns">
              <span title="minimize">_</span>
              <span title="maximize">□</span>
              <span title="close">×</span>
            </div>
          </div>
          <div className="recs-winbody">
            <h2 className="section-title">friends of them</h2>
            <p className="recs-intro">
              <span className="hand">phase 02 · allowlist ♡</span>
              holders of these collections are eligible for phase 02 allowlist. first come, first served.
            </p>
            <div className="recs-list">
              {list.map(([name, url], i) => (
                <React.Fragment key={i}>
                  <a href={url} target="_blank" rel="noopener">
                    <span className="recs-icon">📄</span>{name}
                  </a>
                  {i < list.length - 1 && <span className="star">·</span>}
                </React.Fragment>
              ))}
            </div>
          </div>
        </div>
        <div className="scrap" style={{top:-28, right:80, transform:"rotate(4deg)", fontSize:"18px"}}>wink wink</div>
      </div>
    </section>
  );
}

// ---------- GameFog ----------
const GAME_CLOUDS = [
  { w: 260, h: 80,  x: "2%",  y: "8%",  tx: -130, ty: -40, dur: 22, delay: 0.0  },
  { w: 180, h: 60,  x: "65%", y: "4%",  tx:  130, ty: -30, dur: 26, delay: 0.2  },
  { w: 140, h: 50,  x: "38%", y: "55%", tx:  -50, ty:  60, dur: 18, delay: 0.45 },
  { w: 200, h: 70,  x: "75%", y: "50%", tx:  140, ty:  40, dur: 24, delay: 0.1  },
  { w: 100, h: 38,  x: "15%", y: "65%", tx:  -90, ty:  50, dur: 19, delay: 0.35 },
  { w: 160, h: 55,  x: "48%", y: "18%", tx:   60, ty: -50, dur: 21, delay: 0.25 },
];

function GameFog() {
  const [visible, setVisible] = useState(false);
  const parallaxRefs = useRef([]);

  useEffect(() => {
    const el = document.getElementById("game");
    if (!el) return;
    const obs = new IntersectionObserver(
      ([entry]) => setVisible(entry.isIntersecting),
      { threshold: 0.06 }
    );
    const raf = requestAnimationFrame(() => obs.observe(el));
    const onScroll = () => {
      const rect = el.getBoundingClientRect();
      const vh   = window.innerHeight;
      const prog = (-rect.top + vh) / (rect.height + vh);
      const base = (prog - 0.5) * 120;
      GAME_CLOUDS.forEach((c, i) => {
        const ref = parallaxRefs.current[i];
        if (!ref) return;
        const dir   = i % 2 === 0 ? 1 : -1;
        const speed = 0.3 + (c.w / 800);
        ref.style.transform = `translateY(${base * speed * dir}px)`;
      });
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => { cancelAnimationFrame(raf); obs.disconnect(); window.removeEventListener("scroll", onScroll); };
  }, []);

  return (
    <div className="game-fog" aria-hidden="true">
      <div className="game-fog-sky" style={{ opacity: visible ? 1 : 0, transition: "opacity 1.8s ease" }} />
      {GAME_CLOUDS.map((c, i) => (
        <div key={i} style={{
          position: "absolute", left: c.x, top: c.y,
          opacity: visible ? 1 : 0,
          transform: visible ? "translate(0,0)" : `translate(${c.tx}px,${c.ty}px)`,
          transition: `opacity 1.5s ${c.delay}s ease, transform 2s ${c.delay}s cubic-bezier(.15,.8,.2,1)`,
          pointerEvents: "none", willChange: "opacity, transform",
        }}>
          <div ref={el => parallaxRefs.current[i] = el} style={{ willChange: "transform" }}>
            <div className="game-cloud-inner" style={{ width: c.w, height: c.h, animationDuration: c.dur + "s" }} />
          </div>
        </div>
      ))}
    </div>
  );
}

// ---------- Game ----------
const GAME_DEMONS = [
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="4" y="0" width="1" height="3" fill="#8b2252"/><rect x="3" y="1" width="1" height="2" fill="#8b2252"/><rect x="11" y="0" width="1" height="3" fill="#8b2252"/><rect x="12" y="1" width="1" height="2" fill="#8b2252"/><rect x="3" y="3" width="10" height="9" fill="#220d38"/><rect x="2" y="4" width="12" height="7" fill="#220d38"/><rect x="4" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="9" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="5" y="6" width="2" height="2" fill="#d4748c"/><rect x="10" y="6" width="2" height="2" fill="#d4748c"/><rect x="5" y="6" width="1" height="1" fill="#ffb8cc"/><rect x="10" y="6" width="1" height="1" fill="#ffb8cc"/><rect x="3" y="8" width="2" height="1" fill="#d4748c" opacity="0.4"/><rect x="11" y="8" width="2" height="1" fill="#d4748c" opacity="0.4"/><rect x="5" y="10" width="6" height="1" fill="#0d0518"/><rect x="4" y="9" width="1" height="1" fill="#0d0518"/><rect x="11" y="9" width="1" height="1" fill="#0d0518"/><rect x="4" y="12" width="2" height="2" fill="#220d38"/><rect x="10" y="12" width="2" height="2" fill="#220d38"/></svg>`,
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="5" y="0" width="2" height="3" fill="#9b2262"/><rect x="9" y="0" width="2" height="3" fill="#9b2262"/><rect x="3" y="3" width="10" height="9" fill="#1c0a30"/><rect x="2" y="4" width="12" height="7" fill="#1c0a30"/><rect x="4" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="9" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="5" y="6" width="2" height="2" fill="#d4748c"/><rect x="10" y="6" width="2" height="2" fill="#d4748c"/><rect x="5" y="6" width="1" height="1" fill="#ffb8cc"/><rect x="10" y="6" width="1" height="1" fill="#ffb8cc"/><rect x="3" y="7" width="2" height="1" fill="#d4748c" opacity="0.35"/><rect x="11" y="7" width="2" height="1" fill="#d4748c" opacity="0.35"/><rect x="5" y="9" width="6" height="2" fill="#08030f"/><rect x="6" y="9" width="1" height="1" fill="#f0e8d8"/><rect x="9" y="9" width="1" height="1" fill="#f0e8d8"/><rect x="4" y="12" width="3" height="1" fill="#1c0a30"/><rect x="9" y="12" width="3" height="1" fill="#1c0a30"/></svg>`,
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="4" y="0" width="1" height="2" fill="#7a1a3a"/><rect x="11" y="0" width="1" height="2" fill="#7a1a3a"/><rect x="3" y="2" width="10" height="10" fill="#18092a"/><rect x="2" y="3" width="12" height="8" fill="#18092a"/><rect x="4" y="4" width="3" height="3" fill="#f0e8d8"/><rect x="9" y="4" width="3" height="3" fill="#f0e8d8"/><rect x="4" y="4" width="3" height="1" fill="#18092a"/><rect x="9" y="4" width="3" height="1" fill="#18092a"/><rect x="5" y="5" width="2" height="2" fill="#d4748c"/><rect x="10" y="5" width="2" height="2" fill="#d4748c"/><rect x="5" y="5" width="1" height="1" fill="#ffb8cc"/><rect x="10" y="5" width="1" height="1" fill="#ffb8cc"/><rect x="3" y="7" width="2" height="1" fill="#d4748c" opacity="0.4"/><rect x="11" y="7" width="2" height="1" fill="#d4748c" opacity="0.4"/><rect x="6" y="9" width="4" height="1" fill="#0d0518"/><rect x="4" y="11" width="2" height="1" fill="#18092a"/><rect x="10" y="11" width="2" height="1" fill="#18092a"/></svg>`,
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="3" y="0" width="2" height="3" fill="#8b2252"/><rect x="11" y="0" width="2" height="3" fill="#8b2252"/><rect x="3" y="3" width="10" height="9" fill="#200a34"/><rect x="2" y="4" width="12" height="7" fill="#200a34"/><rect x="4" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="9" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="4" y="5" width="3" height="1" fill="#200a34"/><rect x="9" y="5" width="3" height="1" fill="#200a34"/><rect x="5" y="6" width="2" height="2" fill="#d4748c"/><rect x="10" y="6" width="2" height="2" fill="#d4748c"/><rect x="5" y="6" width="1" height="1" fill="#ff8099"/><rect x="10" y="6" width="1" height="1" fill="#ff8099"/><rect x="5" y="9" width="6" height="2" fill="#0d0518"/><rect x="6" y="9" width="1" height="1" fill="#f0e8d8"/><rect x="9" y="9" width="1" height="1" fill="#f0e8d8"/><rect x="4" y="11" width="2" height="1" fill="#200a34"/><rect x="10" y="11" width="2" height="1" fill="#200a34"/></svg>`,
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="5" y="0" width="6" height="1" fill="#d8d090"/><rect x="4" y="1" width="1" height="1" fill="#d8d090"/><rect x="11" y="1" width="1" height="1" fill="#d8d090"/><rect x="5" y="2" width="6" height="1" fill="#d8d090"/><rect x="3" y="3" width="10" height="9" fill="#1c1430"/><rect x="2" y="4" width="12" height="7" fill="#1c1430"/><rect x="4" y="5" width="3" height="3" fill="#f4f0e0"/><rect x="9" y="5" width="3" height="3" fill="#f4f0e0"/><rect x="5" y="6" width="2" height="2" fill="#c8c090"/><rect x="10" y="6" width="2" height="2" fill="#c8c090"/><rect x="5" y="6" width="1" height="1" fill="#f0e8b0"/><rect x="10" y="6" width="1" height="1" fill="#f0e8b0"/><rect x="6" y="10" width="4" height="1" fill="#0e0c20"/><rect x="4" y="12" width="2" height="1" fill="#1c1430"/><rect x="10" y="12" width="2" height="1" fill="#1c1430"/></svg>`,
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="4" y="0" width="1" height="3" fill="#8b2252"/><rect x="3" y="1" width="1" height="2" fill="#8b2252"/><rect x="11" y="0" width="1" height="3" fill="#8b2252"/><rect x="12" y="1" width="1" height="2" fill="#8b2252"/><rect x="3" y="3" width="10" height="9" fill="#220d38"/><rect x="2" y="4" width="12" height="7" fill="#220d38"/><rect x="4" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="9" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="5" y="6" width="2" height="2" fill="#d4748c"/><rect x="10" y="6" width="2" height="2" fill="#d4748c"/><rect x="5" y="6" width="1" height="1" fill="#ffb8cc"/><rect x="10" y="6" width="1" height="1" fill="#ffb8cc"/><rect x="5" y="10" width="6" height="1" fill="#0d0518"/><rect x="4" y="12" width="2" height="2" fill="#220d38"/><rect x="10" y="12" width="2" height="2" fill="#220d38"/></svg>`,
  `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" width="100%" height="100%"><rect x="5" y="0" width="2" height="3" fill="#9b2262"/><rect x="9" y="0" width="2" height="3" fill="#9b2262"/><rect x="3" y="3" width="10" height="9" fill="#1c0a30"/><rect x="2" y="4" width="12" height="7" fill="#1c0a30"/><rect x="4" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="9" y="5" width="3" height="3" fill="#f0e8d8"/><rect x="5" y="6" width="2" height="2" fill="#d4748c"/><rect x="10" y="6" width="2" height="2" fill="#d4748c"/><rect x="3" y="7" width="2" height="1" fill="#d4748c" opacity="0.35"/><rect x="11" y="7" width="2" height="1" fill="#d4748c" opacity="0.35"/><rect x="5" y="9" width="6" height="2" fill="#08030f"/><rect x="4" y="12" width="3" height="1" fill="#1c0a30"/><rect x="9" y="12" width="3" height="1" fill="#1c0a30"/></svg>`,
];

function Game() {
  return (
    <section className="section" id="game" data-screen-label="game">
      <div className="wrap">
        <div className="game-card">
          <div className="game-card-eyebrow">✦ daily leaderboard ✦</div>
          <h2 className="game-card-title">daily hunt</h2>
          <p className="game-card-sub">
            demons escape every 30 minutes.<br/>
            catch them, climb the board, come back, repeat.
          </p>
          <a href="https://them.petravoice.art/game/" target="_blank" rel="noopener" className="game-btn-go">
            enter the hunt
          </a>
          <div className="game-hung-row">
            {GAME_DEMONS.map((svg, i) => (
              <div key={i} className="game-hung" style={{"--sway-delay": `${(i * 0.41).toFixed(2)}s`, "--sway-dir": i % 2 === 0 ? "1" : "-1"}}>
                <div className="game-hung-string" />
                <div className="game-hung-creature" dangerouslySetInnerHTML={{__html: svg}} />
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- Roadmap ----------
function Roadmap() {
  const items = [
  { title: "koakúma", em: "2024", desc: "a single viral jpeg titled Koakúma. sold as a 1/1 on foundation. millions of views across instagram and pinterest, the seed of everything that became them.", badge: "origin", active: false },
  { title: "first whispers", em: "sep 2025", desc: "the collection begins taking shape through mood boards, tests, sleepless nights. slowly, the voice of them starts to form.", badge: "started", active: false },
  { title: "the reveal", em: "now", desc: "the site you're looking at right now. early faces, a story forming, a community gathering around it.", badge: "we are here", active: true },
  { title: "the mint", em: "may · 2026", desc: "three phases, 3,333 them. exact date sealed behind fog, watch the vault.", badge: "coming", active: false },
  { title: "after", em: "soon + soon + soon", desc: "this is just the beginning ♡ more worlds, more entities, more strange little rooms. the collection will keep evolving.", badge: "forever", active: false }];

  return (
    <section className="section" id="roadmap" data-screen-label="05 roadmap">
      <div className="wrap">
        <div className="kicker">roadmap</div>
        <h2 className="section-title">this is just the beginning</h2>
        <div className="road-track">
          {items.map((it, i) =>
          <div key={i} className={"road-item " + (it.active ? "active" : "")}>
              <div className="road-dot" />
              <div>
                <div className="road-title">{it.title} <em>{it.em}</em></div>
                <div className="road-desc">{it.desc}</div>
              </div>
              <div className="road-badge">{it.badge}</div>
            </div>
          )}
        </div>
      </div>
    </section>);

}

// ---------- Petra ----------
function Petra() {
  return (
    <section className="section" id="petra" data-screen-label="06 petra">
      <div className="wrap">
        <div className="petra-grid">
          <div className="petra-portrait">
            <img src="assets/petra.jpg" alt="petra voice" />
          </div>

          <div className="petra-prose">
            <div className="kicker">about artist</div>
            <h2 className="petra-name">Petra Voice</h2>
            <p className="petra-subname">multidisciplinary artist, creator, inventor and dreamer</p>

            <p>
              her work is all about <em>atmosphere</em>. you might know her from the viral <em>babes</em>, dreamy blurry portraits in aerograph style, the ones many of you use as pfps.
            </p>
            <p>
              there's no real "team" behind <em>them</em>. it's just petra and the people who resonate with it. every detail in this collection is made by her: from the visuals to the code that holds everything together, even this website.
            </p>

            <div className="petra-creds">
              <div className="petra-cred">
                <div className="k">years in nft</div>
                <div className="v">4+</div>
              </div>
              <div className="petra-cred">
                <div className="k">previous drops</div>
                <div className="v">
                  <a href="https://opensea.io/collection/babes2024" target="_blank" rel="noopener"><em>babes</em></a>
                  {" · "}
                  <a href="https://element.market/collections/boys?search%5Btoggles%5D%5B0%5D=ALL" target="_blank" rel="noopener"><em>boys</em></a>
                  {" · "}
                  <a href="https://rarible.com/collection/0xd51b912190d55f9e817ae9d1d33e747566011ab7/drops" target="_blank" rel="noopener"><em>online angels</em></a>
                </div>
              </div>
              <div className="petra-cred">
                <div className="k">collabs</div>
                <div className="v">zora · zerion · mantle · base</div>
              </div>
              <div className="petra-cred">
                <div className="k">exhibitions</div>
                <div className="v">miami art basel · louvre carousel · unit london · crypto art seoul</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>);

}

// ---------- Footer ----------
function Footer() {
  return (
    <footer className="footer">
      <div className="wrap">
        <div className="footer-grid">
          <div className="footer-brand">
            <div className="mark">them</div>
            <p>a collection of strange little entities by Petra Voice</p>
          </div>
          <div>
            <h4>them</h4>
            <ul>
              <li><a href="https://discord.gg/thembypetra" target="_blank" rel="noopener">discord</a></li>
              <li><a href="https://x.com/thembypetra" target="_blank" rel="noopener">x / twitter</a></li>
            </ul>
          </div>
          <div>
            <h4>petra</h4>
            <ul>
              <li><a href="https://petravoice.art/" target="_blank" rel="noopener">petravoice.art</a></li>
              <li><a href="https://x.com/petravoice" target="_blank" rel="noopener">x / twitter</a></li>
              <li><a href="https://www.instagram.com/petra_voice" target="_blank" rel="noopener">instagram</a></li>
              <li><a href="https://petravoice.darkroom.com/" target="_blank" rel="noopener">prints · darkroom</a></li>
            </ul>
          </div>
          <div>
            <h4>mint</h4>
            <ul>
              <li><a href="#phases">phases</a></li>
              <li><a href="#roadmap">roadmap</a></li>
            </ul>
          </div>
        </div>
        <div className="footer-legal">
          <div>© 2026 petra voice · all entities belong to themselves</div>
          <div className="kao">✦ . . . ✦</div>
        </div>
      </div>
    </footer>);

}

// ---------- Easter egg ----------
function EasterEgg({ active, onClose }) {
  return (
    <div className={"egg " + (active ? "active" : "")} onClick={onClose}>
      <div className="egg-card" onClick={(e) => e.stopPropagation()}>
        <div className="glyph">them</div>
        <p>you found it. you're already one of <em>them</em>. the quiet parts, the meaning that isn't obvious at first glance. you noticed.</p>
        <small onClick={onClose}>close · keep it secret</small>
      </div>
    </div>);

}

// ---------- Collabs ----------
function Collabs() {
  const cards = [
    { img: "assets/collab-moemoe.jpg",    name: "them × MoeMoe",    sub: "them in the moemoe universe",   href: "https://opensea.io/collection/moemoe-llc" },
    { img: "assets/collab-skrumpeys.jpg", name: "them × Skrumpeys", sub: "them in the skrumpeys universe", href: "https://opensea.io/collection/skrumpeys" },
    { img: "assets/collab-yumemono.jpg",  name: "them × yumemono",  sub: "them in the yumemono universe",  href: "https://opensea.io/collection/yumemono" },
  ];
  return (
    <section className="section" id="collabs" data-screen-label="06 collabs">
      <div className="wrap">
        <div className="kicker">special editions</div>
        <h2 className="section-title">them in other worlds</h2>
        <p className="collabs-intro">
          when them wanders into other universes. limited crossover pieces made with friends we love.
        </p>
        <div className="collabs-grid">
          {cards.map((c, i) => (
            <a key={i} className="collab-card" href={c.href} target="_blank" rel="noopener">
              <div className="collab-img-wrap">
                <img src={c.img} alt={c.name} className="collab-img" />
                <div className="collab-shimmer" />
              </div>
              <div className="collab-info">
                <div className="collab-name">{c.name}</div>
                <div className="collab-sub">{c.sub}</div>
                <div className="collab-arrow">→</div>
              </div>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

// ---------- RPG Dialogue ----------
const SPRITE_SOUND = {
  "sprite-surprised.png":  { freq: 540, variation: 50,  glitch: false },
  "sprite-wink.png":       { freq: 580, variation: 45,  glitch: false },
  "sprite-closedcute.png": { freq: 420, variation: 20,  glitch: false },
  "sprite-neutral.png":    { freq: 520, variation: 40,  glitch: false },
  "sprite-haha.png":       { freq: 680, variation: 80,  glitch: false },
  "sprite-bored.png":      { freq: 380, variation: 15,  glitch: false },
  "sprite-sad.png":        { freq: 320, variation: 10,  glitch: false },
  "sprite-blushed.png":    { freq: 620, variation: 55,  glitch: false },
  "sprite-glitch.png":     { freq: 0,   variation: 0,   glitch: true  },
  "sprite-what.png":       { freq: 450, variation: 30,  glitch: false },
  "sprite-smile.png":      { freq: 600, variation: 50,  glitch: false },
  "sprite-cheers.png":     { freq: 640, variation: 60,  glitch: false },
  "sprite-actually.png":   { freq: 500, variation: 40,  glitch: false },
  "sprite-smirk.png":      { freq: 560, variation: 35,  glitch: false },
};

// ---------- Mood map (sprite path + chirp params) ----------
const MOODS = {
  surprised:   { sprite: "assets/sprite-surprised.png",  freq: 540, variation: 50  },
  wink:        { sprite: "assets/sprite-wink.png",       freq: 580, variation: 45  },
  closedcute:  { sprite: "assets/sprite-closedcute.png", freq: 420, variation: 20  },
  questioning: { sprite: "assets/sprite-questioning.png",freq: 450, variation: 30  },
  haha:        { sprite: "assets/sprite-haha.png",       freq: 680, variation: 80  },
  bored:       { sprite: "assets/sprite-bored.png",      freq: 380, variation: 15  },
  crying:      { sprite: "assets/sprite-crying.png",     freq: 300, variation: 10  },
  inlove:      { sprite: "assets/sprite-blushed.png",    freq: 620, variation: 55  },
  eww:         { sprite: "assets/sprite-eww.png",        freq: 350, variation: 20  },
  what:        { sprite: "assets/sprite-what.png",       freq: 450, variation: 30  },
  happy:       { sprite: "assets/sprite-smile.png",      freq: 600, variation: 50  },
  cheers:      { sprite: "assets/sprite-cheers.png",     freq: 640, variation: 60  },
  actually:    { sprite: "assets/sprite-actually.png",   freq: 500, variation: 40  },
  smirk:       { sprite: "assets/sprite-smirk.png",      freq: 560, variation: 35  },
  blushed:     { sprite: "assets/sprite-blushed.png",    freq: 560, variation: 40  },
  neutral:     { sprite: "assets/sprite-neutral.png",    freq: 520, variation: 40  },
};

// flat per-line tour — each entry has its own mood + optional section scroll
const TOUR = [
  { mood: "surprised",   section: null,      line: "...oh. you actually came." },
  { mood: "wink",        section: null,      line: "fine. i'll show you around." },
  { mood: "wink",        section: null,      line: "i wasn't doing anything important anyway." },
  { mood: "closedcute",  section: "world",   line: "this is our world." },
  { mood: "closedcute",  section: null,      line: "we exist without permission." },
  { mood: "closedcute",  section: null,      line: "you do too, by the way. you just don't think about it." },
  { mood: "questioning", section: "gallery", line: "sometimes i think the world on the other side of the screen is much less vivid than ours." },
  { mood: "questioning", section: null,      line: "just saying." },
  { mood: "haha",        section: null,      line: "don't worry. we're just keeping your data here." },
  { mood: "wink",        section: null,      line: "and a few of your dreams." },
  { mood: "smirk",       section: "game",    line: "the dark ones keep slipping out of our world. little troublemakers." },
  { mood: "questioning", section: null,      line: "someone has to catch them. might as well be you." },
  { mood: "questioning", section: null,      line: "so many of you racing for first place. i wonder what you're after." },
  { mood: "bored",       section: "roadmap", line: "please don't close the tab too quickly." },
  { mood: "crying",      section: null,      line: "it gets very dark in here." },
  { mood: "wink",        section: "checker",  line: "there's a list down here. you might be on it." },
  { mood: "smirk",       section: null,       line: "can't check right now though. not yet open." },
  { mood: "inlove",      section: "petra",   line: "this is the one who made us." },
  { mood: "closedcute",  section: null,      line: "she thinks she's in control." },
  { mood: "haha",        section: null,      line: "...that's cute." },
  { mood: "eww",         section: null,      line: "okay. you've seen enough.",              glitch: true },
  { mood: "eww",         section: null,      line: "or maybe we've seen enough of you.",      glitch: true },
  { mood: "eww",         section: null,      line: "\u2593\u2592\u2591",                       glitch: true },
];

// encore — plays when user clicks the mini sprite after tour ends
const ENCORE = [
  { mood: "what",        line: "oh. you're still here?" },
  { mood: "happy",       line: "look at you. stubborn." },
  { mood: "cheers",      line: "i like it \u2665" },
  { mood: "actually",    line: "fine. let me tell you a secret." },
  { mood: "haha",        line: "we already know which one you'll pick. we picked it first." },
  { mood: "haha",        line: "axaxaxa" },
  { mood: "closedcute",  line: "okay, bye. don't get into trouble!" },
];

// loop — plays when user clicks mini sprite a third time and beyond
const LOOP = [
  { mood: "smirk",      line: "you again." },
  { mood: "haha",       line: "axaxaxa, predictable. i love it." },
  { mood: "blushed",    line: "okay, fine. maybe i missed you a little." },
  { mood: "wink",       line: "but only a little." },
  { mood: "closedcute", line: "go on now. mint something. shoo." },
];

const BSOD_TALK = [
  { mood: "haha",       line: "oh. you actually tried." },
  { mood: "blushed",    line: "i love the confidence, genuinely." },
  { mood: "smirk",      line: "but it's not time yet, love." },
  { mood: "closedcute", line: "come on. let me show you around first." },
];

function RPGDialogue() {
  const [idx,       setIdx]       = useState(0);
  const [displayed, setDisplayed] = useState("");
  const [done,      setDone]      = useState(false);
  const [visible,   setVisible]   = useState(true);
  const [mini,      setMini]      = useState(false);
  const [muted,     setMuted]     = useState(false);
  const [mode,      setMode]      = useState("tour"); // "tour" | "bsod" | "encore"

  const timerRef = useRef(null);
  const audioCtx = useRef(null);
  const mutedRef = useRef(true);

  useEffect(() => { mutedRef.current = muted; }, [muted]);

  const getCtx = () => {
    if (!audioCtx.current) {
      const AC = window.AudioContext || window.webkitAudioContext;
      if (AC) audioCtx.current = new AC();
    }
    return audioCtx.current;
  };

  const chirp = (moodKey) => {
    if (mutedRef.current) return;
    try {
      const ctx = getCtx(); if (!ctx) return;
      if (ctx.state === "suspended") ctx.resume();
      const cfg = MOODS[moodKey] || MOODS.neutral;
      const freq = cfg.freq + (Math.random() - 0.5) * 2 * cfg.variation;
      const o = ctx.createOscillator(); const g = ctx.createGain();
      o.type = "sine"; o.frequency.value = Math.max(80, freq);
      g.gain.setValueAtTime(0.08, ctx.currentTime);
      g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.08);
      o.connect(g); g.connect(ctx.destination);
      o.start(); o.stop(ctx.currentTime + 0.08);
    } catch(e) {}
  };

  const startTyping = (text, moodKey) => {
    setDisplayed(""); setDone(false);
    let i = 0;
    if (timerRef.current) clearInterval(timerRef.current);
    timerRef.current = setInterval(() => {
      if (i >= text.length) { clearInterval(timerRef.current); setDone(true); return; }
      const ch = text[i++];
      setDisplayed(text.slice(0, i));
      if (/[a-zA-Zа-яА-Я0-9\u2591\u2592\u2593\u2665]/.test(ch)) chirp(moodKey);
    }, 40);
  };

  const getScript = (m) => {
    if (m === "bsod")   return BSOD_TALK;
    if (m === "encore") return ENCORE;
    if (m === "loop")   return LOOP;
    return TOUR;
  };

  useEffect(() => {
    startTyping(TOUR[0].line, TOUR[0].mood);
    return () => clearInterval(timerRef.current);
  }, []);

  // listen for bsod-done event
  useEffect(() => {
    const onBsodDone = () => {
      clearInterval(timerRef.current);
      setMode("bsod"); setIdx(0);
      setVisible(true); setMini(false);
      startTyping(BSOD_TALK[0].line, BSOD_TALK[0].mood);
    };
    window.addEventListener("bsod-done", onBsodDone);
    return () => window.removeEventListener("bsod-done", onBsodDone);
  }, []);

  const script  = getScript(mode);
  const current = script[idx];
  const mood    = current.mood;
  const isLast  = idx === script.length - 1;

  const advance = () => {
    if (!done) {
      clearInterval(timerRef.current);
      setDisplayed(current.line); setDone(true);
      return;
    }
    if (!isLast) {
      const next = idx + 1;
      const entry = script[next];
      if (entry.section) document.getElementById(entry.section)?.scrollIntoView({ behavior: "smooth" });
      setIdx(next);
      startTyping(entry.line, entry.mood);
    } else if (mode === "bsod") {
      // after BSOD talk → start tour, skip first 2 intro lines (avoid repetition)
      setMode("tour"); setIdx(2);
      startTyping(TOUR[2].line, TOUR[2].mood);
    } else {
      setVisible(false); setMini(true);
    }
  };

  // clicking mini sprite → cycles: tour→encore→loop→loop→…
  const reopenMini = () => {
    setMini(false); setVisible(true);
    const next = mode === "tour" ? "encore" : "loop";
    const script = next === "encore" ? ENCORE : LOOP;
    setMode(next); setIdx(0);
    startTyping(script[0].line, script[0].mood);
  };

  // clicking the sprite itself inside dialogue → restart tour from beginning
  const reopenSprite = () => {
    setMode("tour"); setIdx(0);
    startTyping(TOUR[0].line, TOUR[0].mood);
  };

  if (!visible && !mini) return null;

  if (mini) {
    return (
      <div className="rpg-mini" onClick={reopenMini} title="click to reopen">
        <img src={MOODS.wink.sprite} alt="them" className="rpg-sprite-mini-img" />
        <div className="rpg-mini-hint">[ click ]</div>
      </div>
    );
  }

  const spriteUrl = (MOODS[mood] || MOODS.neutral).sprite;

  return (
    <div className="rpg-dialogue">
      <img
        src={spriteUrl}
        alt="them"
        className={"rpg-sprite" + (current.glitch ? " rpg-glitch" : "")}
        onClick={reopenSprite}
        title="restart"
      />
      <div className={"rpg-box" + (current.glitch ? " box-glitch" : "")} onClick={advance}>
        <div className="rpg-noise" />
        <div className="rpg-header">
          <span className="rpg-name">them</span>
          <button className="rpg-mute" onClick={e => { e.stopPropagation(); setMuted(m => !m); }} title={muted ? "unmute" : "mute"}>
            {muted ? "\uD83D\uDD07" : "\uD83D\uDD0A"}
          </button>
        </div>
        <div className="rpg-text">
          {displayed}
          {!done && <span className="rpg-cursor">▋</span>}
        </div>
        {done && !isLast && <div className="rpg-prompt">▼</div>}
        {done && isLast  && <div className="rpg-prompt">▶</div>}
      </div>
      <button className="rpg-close" onClick={e => { e.stopPropagation(); setVisible(false); setMini(true); }}>✕</button>
    </div>
  );
}

// ---------- Tweaks ----------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "pink",
  "grain": 0.32,
  "scanlines": false,
  "glow": true
} /*EDITMODE-END*/;

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [clicks, setClicks] = useState(0);
  const [egg, setEgg] = useState(false);
  const clickTimer = useRef(null);

  const logoClick = () => {
    const next = clicks + 1;
    setClicks(next);
    if (next >= 3) {setEgg(true);setClicks(0);return;}
    if (clickTimer.current) clearTimeout(clickTimer.current);
    clickTimer.current = setTimeout(() => setClicks(0), 1200);
  };

  // apply palette + body bg
  useEffect(() => {
    const root = document.documentElement;
    const palettes = {
      pink: {
        "--fog": "#efe4e8", "--mist": "#e0ccd4", "--rose": "#e8c6cc", "--rose-deep": "#c69aa5",
        "--plum": "#6a4a5c", "--plum-deep": "#3e2a36", "--bruise": "#8a6c7c", "--ink": "#1d141a",
        "--accent": "#d88b94", "--paper": "#f5ecee", "--text": "#1d141a", "--text-soft": "#4a3a44", "--text-dim": "#7a6c74",
        body: "radial-gradient(ellipse at 15% 10%, #f5dce4 0%, transparent 40%), radial-gradient(ellipse at 85% 25%, #e8d0dc 0%, transparent 45%), radial-gradient(ellipse at 30% 60%, #d4bcc8 0%, transparent 50%), radial-gradient(ellipse at 85% 85%, #b89aa8 0%, transparent 55%), linear-gradient(180deg, #efe4e8 0%, #d8c4cc 60%, #a08898 100%)"
      },
      fog: {
        "--fog": "#e8e4e1", "--mist": "#d4ccc8", "--rose": "#c9bdc3", "--rose-deep": "#a89aa0",
        "--plum": "#5a4e58", "--plum-deep": "#2e2630", "--bruise": "#756a74", "--ink": "#12100f",
        "--accent": "#a8909a", "--paper": "#edeae6", "--text": "#12100f", "--text-soft": "#423a40", "--text-dim": "#6a626a",
        body: "radial-gradient(ellipse at 20% 15%, #edeae6 0%, transparent 45%), radial-gradient(ellipse at 80% 40%, #d4ccc8 0%, transparent 50%), radial-gradient(ellipse at 50% 90%, #b0a8a8 0%, transparent 60%), linear-gradient(180deg, #e8e4e1 0%, #9a9098 100%)"
      },
      dusk: {
        "--fog": "#1a141c", "--mist": "#3a2c38", "--rose": "#8a6c7c", "--rose-deep": "#6a4a5c",
        "--plum": "#d6b8c0", "--plum-deep": "#a8909a", "--bruise": "#c6a6b0", "--ink": "#ede0e4",
        "--accent": "#e8a0ac", "--paper": "#0e0a10", "--text": "#ede0e4", "--text-soft": "#c6b0bc", "--text-dim": "#8a7a84",
        body: "radial-gradient(ellipse at 15% 10%, #3a2030 0%, transparent 40%), radial-gradient(ellipse at 85% 25%, #2a1a28 0%, transparent 45%), radial-gradient(ellipse at 50% 80%, #1a1218 0%, transparent 55%), linear-gradient(180deg, #1d141a 0%, #0e0a10 100%)"
      }
    };
    const chosen = palettes[tweaks.palette] || palettes.pink;
    Object.entries(chosen).forEach(([k, v]) => {
      if (k !== "body") root.style.setProperty(k, v);
    });
    document.body.style.background = chosen.body;
    document.body.style.backgroundAttachment = "fixed";
  }, [tweaks.palette]);

  // swap display font
  useEffect(() => {
    const fonts = {
      fraunces: '"Fraunces", serif',
      cormorant: '"Cormorant Garamond", serif',
      blackletter: '"UnifrakturMaguntia", "Fraunces", serif',
      script: '"Pinyon Script", "Fraunces", serif',
    };
    document.documentElement.style.setProperty("--display", fonts[tweaks.display] || fonts.fraunces);
  }, [tweaks.display]);

  return (
    <>
      {tweaks.glow && <CursorGlow />}
      <div className="grain" style={{ opacity: tweaks.grain }} />
      <div className="vhs-overlay" />
      {tweaks.scanlines && <div className="scanlines" />}
      {/* AudioToggle removed per request */}

      <Win95Popup />
      <Nav onLogoClick={logoClick} />
      <Hero />
      <Ticker />
      <World />
      <Gallery />
      <Game />
      <Phases />
      <Recs />
      <Collabs />
      <Roadmap />
      <WalletChecker />
      <Petra />
      <Footer />
      <RPGDialogue />

      <EasterEgg active={egg} onClose={() => setEgg(false)} />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Mood · palette" />
        <TweakRadio label="palette" value={tweaks.palette}
        options={[
        { value: "pink", label: "pink" },
        { value: "fog", label: "fog" },
        { value: "dusk", label: "dusk" }]
        }
        onChange={(v) => setTweak("palette", v)} />

        <TweakSection label="Atmosphere" />
        <TweakSlider label="grain" value={tweaks.grain} min={0} max={0.8} step={0.02}
        onChange={(v) => setTweak("grain", v)} />
        <TweakToggle label="scanlines / crt" value={tweaks.scanlines}
        onChange={(v) => setTweak("scanlines", v)} />
        <TweakToggle label="cursor glow" value={tweaks.glow}
        onChange={(v) => setTweak("glow", v)} />

        <TweakSection label="Easter egg" />
        <div style={{ fontSize: 11, color: "var(--text-dim)", fontFamily: "var(--mono)", lineHeight: 1.5, letterSpacing: "0.05em", padding: "6px 0" }}>
          click "them" in the nav 3 times ♡
        </div>
      </TweaksPanel>
    </>);

}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
