// bits.jsx — shared bits: cursor, ticker, brand mark, hotkeys, palette swap.

const { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect } = React;

// ── tiny utils ────────────────────────────────────────────────────────────
const rand   = (a, b) => a + Math.random() * (b - a);
const choose = (arr) => arr[Math.floor(Math.random() * arr.length)];
const clamp  = (n, a, b) => Math.max(a, Math.min(b, n));
const lerp   = (a, b, t) => a + (b - a) * t;
const TAU    = Math.PI * 2;

// ── palettes ──────────────────────────────────────────────────────────────
const PALETTES = {
  park: {
    paper:"#F2EBDC", ink:"#0E0E0E",
    cherry:"oklch(0.62 0.22 25)", sun:"oklch(0.84 0.17 85)",
    sky:"oklch(0.6 0.19 240)",   grass:"oklch(0.66 0.15 145)",
    gum:"oklch(0.74 0.17 355)",  plum:"oklch(0.45 0.16 320)",
    label:"daytime",
  },
  "after-hours": {
    paper:"#15141A", ink:"#F2EBDC",
    cherry:"#FF5470", sun:"#FFD166",
    sky:"#26C6DA",   grass:"#A0E548",
    gum:"#F38BA8",   plum:"#A084FF",
    label:"after hours",
  },
  riso: {
    paper:"#F4EFE0", ink:"#1F1A14",
    cherry:"#FA7268", sun:"#F9C66B",
    sky:"#5784BA",   grass:"#74A57F",
    gum:"#D88BB1",   plum:"#7C5C9E",
    label:"risograph",
  },
  concrete: {
    paper:"#E5E2DA", ink:"#181818",
    cherry:"#D55142", sun:"#C8B26B",
    sky:"#5C7DA8",   grass:"#5F8E73",
    gum:"#B97A9A",   plum:"#7D5598",
    label:"concrete",
  },
  // unlocks via konami (▲▲▼▼◀▶◀▶BA)
  void: {
    paper:"#000000", ink:"#FFEA00",
    cherry:"#FF0033", sun:"#00FFD0",
    sky:"#3A6FE0",   grass:"#A4FF00",
    gum:"#FF66E1",   plum:"#A084FF",
    label:"void",
    secret: true,
  },
};

function applyPalette(p) {
  const r = document.documentElement.style;
  r.setProperty("--paper",  p.paper);
  r.setProperty("--ink",    p.ink);
  r.setProperty("--ink-2",  p.ink);
  r.setProperty("--cherry", p.cherry);
  r.setProperty("--sun",    p.sun);
  r.setProperty("--sky",    p.sky);
  r.setProperty("--grass",  p.grass);
  r.setProperty("--gum",    p.gum);
  r.setProperty("--plum",   p.plum);
  document.body.style.background = p.paper;
}

// ── global cursor (hot/grab/pan/sit states) ───────────────────────────────
function useGlobalCursor() {
  useEffect(() => {
    const el = document.getElementById("cursor");
    if (!el) return;
    let raf = 0, x = innerWidth / 2, y = innerHeight / 2;
    let tx = x, ty = y;
    const tick = () => {
      x += (tx - x) * 0.35;
      y += (ty - y) * 0.35;
      el.style.transform = `translate(${x}px, ${y}px)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    let firstMoveSeen = false;
    const onMove = (e) => {
      tx = e.clientX; ty = e.clientY;
      if (!firstMoveSeen) { el.classList.add("is-on"); firstMoveSeen = true; }
      const t = e.target.closest("[data-cursor]");
      el.dataset.state = t ? t.dataset.cursor : "";
    };
    window.addEventListener("mousemove", onMove);
    return () => { cancelAnimationFrame(raf); window.removeEventListener("mousemove", onMove); };
  }, []);
}

// ── frgmt mark — fragmented dot ──────────────────────────────────────────
function FrgmtMark({ size = 24, color = "var(--ink)" }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true">
      <g fill={color}>
        <rect x="3"  y="3"  width="8" height="8" />
        <rect x="13" y="5"  width="6" height="6" />
        <rect x="4"  y="13" width="5" height="5" />
        <rect x="11" y="14" width="9" height="7" />
      </g>
    </svg>
  );
}

// ── ticker / marquee ─────────────────────────────────────────────────────
function Ticker({ items, speed = 60 }) {
  const seq = useMemo(() => [...items, ...items], [items]);
  const dur = `${Math.max(20, items.join("  ").length * (60 / speed))}s`;
  return (
    <div style={{
      position:"fixed", left:0, right:0, bottom:0, height:36,
      borderTop:"1px solid var(--ink)", background:"transparent",
      color:"var(--ink)", overflow:"hidden",
      display:"flex", alignItems:"center",
      fontFamily:"var(--mono)", fontSize:11, letterSpacing:".06em",
      textTransform:"uppercase", zIndex:50, pointerEvents:"none",
      backdropFilter:"blur(2px)",
    }}>
      <div style={{
        display:"inline-flex", whiteSpace:"nowrap",
        animation:`tick ${dur} linear infinite`,
      }}>
        {seq.map((s, i) => (
          <span key={i} style={{ paddingInline:24, display:"inline-flex", alignItems:"center", gap:24 }}>
            <span>{s}</span>
            <span style={{ opacity:.4 }}>✦</span>
          </span>
        ))}
      </div>
    </div>
  );
}

// ── tag pill ──────────────────────────────────────────────────────────────
function Tag({ children, color = "var(--ink)", bg = "transparent", border = true }) {
  return (
    <span style={{
      display:"inline-flex", alignItems:"center", gap:6,
      padding:"4px 10px", borderRadius:99,
      border: border ? `1px solid ${color}` : "none",
      background: bg, color,
      fontFamily:"var(--mono)", fontSize:11, letterSpacing:".06em",
      textTransform:"uppercase", lineHeight:1, whiteSpace:"nowrap",
    }}>{children}</span>
  );
}

// ── hint with kbd ─────────────────────────────────────────────────────────
function Hint({ k, children }) {
  return (
    <div style={{ display:"flex", alignItems:"center", gap:8 }}>
      <kbd className="kbd">{k}</kbd>
      <span style={{ opacity:.85 }}>{children}</span>
    </div>
  );
}

// ── hotkey ────────────────────────────────────────────────────────────────
function useHotkey(map) {
  useEffect(() => {
    const onKey = (e) => {
      if (e.target && e.target.matches && e.target.matches("input,textarea,select")) return;
      const k = e.key.toLowerCase();
      const handler = map[k] || map[e.key];
      if (handler) { handler(e); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [map]);
}

// ── konami ────────────────────────────────────────────────────────────────
function useKonami(onTriggered) {
  useEffect(() => {
    const code = ["arrowup","arrowup","arrowdown","arrowdown","arrowleft","arrowright","arrowleft","arrowright","b","a"];
    let buf = [];
    const onKey = (e) => {
      const k = e.key.toLowerCase();
      buf = [...buf, k].slice(-code.length);
      if (buf.join(",") === code.join(",")) { buf = []; onTriggered(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onTriggered]);
}

// ── word listener ─────────────────────────────────────────────────────────
function useWord(word, onTriggered) {
  useEffect(() => {
    let buf = "";
    const onKey = (e) => {
      if (e.target && e.target.matches && e.target.matches("input,textarea,select")) return;
      if (e.key.length !== 1) return;
      buf = (buf + e.key.toLowerCase()).slice(-word.length);
      if (buf === word) { buf = ""; onTriggered(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [word, onTriggered]);
}

// ── animation frame (broadcast clock) ────────────────────────────────────
function useTick(speed = 1) {
  const [t, setT] = useState(0);
  useEffect(() => {
    let raf, t0 = performance.now();
    const loop = () => {
      setT(((performance.now() - t0) / 1000) * speed);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [speed]);
  return t;
}

// ── small SVG icons used in HUD + scenery ────────────────────────────────
function SunIcon({ size = 80, color = "var(--sun)" }) {
  return (
    <svg width={size} height={size} viewBox="0 0 80 80" aria-hidden="true">
      <circle cx="40" cy="40" r="18" fill={color} stroke="var(--ink)" strokeWidth="2.5" />
      {Array.from({ length: 12 }).map((_, i) => {
        const a = (i / 12) * TAU;
        const x1 = 40 + Math.cos(a) * 26, y1 = 40 + Math.sin(a) * 26;
        const x2 = 40 + Math.cos(a) * 36, y2 = 40 + Math.sin(a) * 36;
        return <line key={i} x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--ink)" strokeWidth="2.5" strokeLinecap="round" />;
      })}
    </svg>
  );
}
function CloudIcon({ size = 100 }) {
  return (
    <svg width={size} height={size * 0.6} viewBox="0 0 100 60" aria-hidden="true">
      <path d="M 18 50 Q 6 50 6 38 Q 6 26 18 26 Q 22 14 36 16 Q 46 6 60 14 Q 74 10 80 24 Q 96 26 96 40 Q 96 52 84 52 Z"
            fill="var(--paper)" stroke="var(--ink)" strokeWidth="2.5" strokeLinejoin="round"/>
    </svg>
  );
}
function BenchIcon({ size = 90 }) {
  return (
    <svg width={size} height={size * 0.55} viewBox="0 0 100 55" aria-hidden="true">
      <rect x="6"  y="20" width="88" height="6" fill="var(--gum)" stroke="var(--ink)" strokeWidth="2"/>
      <rect x="6"  y="30" width="88" height="6" fill="var(--gum)" stroke="var(--ink)" strokeWidth="2"/>
      <line x1="14" y1="36" x2="14" y2="52" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round" />
      <line x1="86" y1="36" x2="86" y2="52" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round" />
      <line x1="50" y1="36" x2="50" y2="52" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round" />
    </svg>
  );
}
function FlagIcon({ size = 60 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 60 60" aria-hidden="true">
      <line x1="14" y1="6" x2="14" y2="56" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round" />
      <path d="M 14 8 L 46 14 L 30 22 L 46 30 L 14 24 Z" fill="var(--cherry)" stroke="var(--ink)" strokeWidth="2" strokeLinejoin="round" />
    </svg>
  );
}
function TreeIcon({ size = 90 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 90 90" aria-hidden="true">
      <rect x="40" y="50" width="10" height="34" fill="var(--ink)" />
      <circle cx="45" cy="38" r="28" fill="var(--grass)" stroke="var(--ink)" strokeWidth="2.5" />
      <circle cx="32" cy="32" r="4" fill="var(--cherry)" />
      <circle cx="56" cy="42" r="3.5" fill="var(--sun)" />
    </svg>
  );
}
function BalloonIcon({ size = 36, color = "var(--cherry)" }) {
  return (
    <svg width={size} height={size * 1.5} viewBox="0 0 36 54" aria-hidden="true">
      <ellipse cx="18" cy="20" rx="14" ry="18" fill={color} stroke="var(--ink)" strokeWidth="2" />
      <polygon points="14,38 22,38 18,42" fill={color} stroke="var(--ink)" strokeWidth="1.5" strokeLinejoin="round" />
      <line x1="18" y1="42" x2="18" y2="52" stroke="var(--ink)" strokeWidth="1.5" />
    </svg>
  );
}
function BirdIcon({ size = 26, color = "var(--ink)" }) {
  return (
    <svg width={size} height={size * 0.6} viewBox="0 0 26 16" aria-hidden="true">
      <path d="M 2 12 Q 7 2 13 8 Q 19 2 24 12" fill="none" stroke={color} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

// ── ShapeSVG used by sandbox ─────────────────────────────────────────────
function ShapeSVG({ shape, color }) {
  const s = "var(--ink)", w = 3;
  const C = color;
  switch (shape) {
    case "square":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <rect x="6" y="6" width="88" height="88" fill={C} stroke={s} strokeWidth={w}/>
      </svg>;
    case "circle":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <circle cx="50" cy="50" r="46" fill={C} stroke={s} strokeWidth={w}/>
      </svg>;
    case "triangle":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <polygon points="50,8 92,90 8,90" fill={C} stroke={s} strokeWidth={w} strokeLinejoin="round"/>
      </svg>;
    case "diamond":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <polygon points="50,6 94,50 50,94 6,50" fill={C} stroke={s} strokeWidth={w} strokeLinejoin="round"/>
      </svg>;
    case "halfcircle":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <path d="M 6 80 A 44 44 0 0 1 94 80 Z" fill={C} stroke={s} strokeWidth={w} strokeLinejoin="round"/>
      </svg>;
    case "cross":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <path d="M 38 6 H 62 V 38 H 94 V 62 H 62 V 94 H 38 V 62 H 6 V 38 H 38 Z" fill={C} stroke={s} strokeWidth={w} strokeLinejoin="round"/>
      </svg>;
    case "arch":
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <path d="M 8 94 V 50 A 42 42 0 0 1 92 50 V 94 Z" fill={C} stroke={s} strokeWidth={w} strokeLinejoin="round"/>
      </svg>;
    case "star": {
      const pts = [];
      for (let i = 0; i < 10; i++) {
        const r = i % 2 === 0 ? 44 : 18;
        const a = -Math.PI / 2 + (Math.PI * 2 * i) / 10;
        pts.push((50 + Math.cos(a) * r).toFixed(1) + "," + (50 + Math.sin(a) * r).toFixed(1));
      }
      return <svg viewBox="0 0 100 100" width="100%" height="100%">
        <polygon points={pts.join(" ")} fill={C} stroke={s} strokeWidth={w} strokeLinejoin="round"/>
      </svg>;
    }
    default: return null;
  }
}

// expose to other files (loaded as separate <script> tags via babel-standalone)
Object.assign(window, {
  PALETTES, applyPalette,
  useGlobalCursor, useHotkey, useKonami, useWord, useTick,
  FrgmtMark, Ticker, Tag, Hint,
  SunIcon, CloudIcon, BenchIcon, FlagIcon, TreeIcon, BalloonIcon, BirdIcon,
  ShapeSVG,
  rand, choose, clamp, lerp, TAU,
});
