// ============================================================
// THEME — central tokens so all sections stay in lockstep
// ============================================================

const THEMES = {
  midnight: {
    name: "Midnight",
    page: "#08101e",
    surface: "#0c1426",
    surfaceAlt: "#0f1a30",
    card: "rgba(255,255,255,0.025)",
    cardBorder: "rgba(255,255,255,0.08)",
    rule: "#f5c14e",
    ink: "#f4f1e8",
    sub: "rgba(244, 241, 232, 0.72)",
    mute: "rgba(244, 241, 232, 0.5)",
    accent: "#f5c14e",
    accentDeep: "#e2a82a",
    chip: "rgba(255,255,255,0.08)",
    chipBorder: "rgba(255,255,255,0.1)",
    chipText: "rgba(244,241,232,0.88)",
    success: "#5fd28a",
    progress: "#7aa3ff",
    dark: true,
    heroBg: "radial-gradient(ellipse at 30% 20%, #1c2a4a 0%, #0c1226 55%, #060912 100%)",
  },
  light: {
    name: "Bone",
    page: "#f6f1e2",
    surface: "#faf6ea",
    surfaceAlt: "#f1ead4",
    card: "rgba(22,26,43,0.025)",
    cardBorder: "rgba(22,26,43,0.10)",
    rule: "#e2a82a",
    ink: "#161a2b",
    sub: "rgba(22, 26, 43, 0.72)",
    mute: "rgba(22, 26, 43, 0.5)",
    accent: "#e2a82a",
    accentDeep: "#b88313",
    chip: "rgba(22,26,43,0.06)",
    chipBorder: "rgba(22,26,43,0.12)",
    chipText: "rgba(22,26,43,0.82)",
    success: "#1f8a5b",
    progress: "#2a5fc7",
    dark: false,
    heroBg: "radial-gradient(ellipse at 30% 20%, #fbf5e6 0%, #f3ead0 55%, #ead9a8 100%)",
  },
};

// Viewing-preferences scale modifiers. `zoom` is the multiplier applied to
// <html> so text + layout scale together (inline px font-sizes don't inherit
// from body, so a global zoom is the only thing that actually moves the dial).
const TEXT_SCALES = {
  S: { base: 15, line: 1.5, letter: 0, zoom: 0.9 },
  M: { base: 17, line: 1.6, letter: 0, zoom: 1.0 },
  L: { base: 19, line: 1.7, letter: 0.1, zoom: 1.12 },
};

window.THEMES = THEMES;
window.TEXT_SCALES = TEXT_SCALES;

// ============================================================
// VIEWPORT — single source of truth for responsive logic
// Breakpoint: <768px is "mobile" (phones + small tablets portrait)
// ============================================================

function useViewport() {
  const [w, setW] = React.useState(
    typeof window !== "undefined" ? window.innerWidth : 1280
  );
  React.useEffect(() => {
    let raf = 0;
    const onResize = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => setW(window.innerWidth));
    };
    window.addEventListener("resize", onResize);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize);
    };
  }, []);
  return { width: w, isMobile: w < 768, isTablet: w < 1024 };
}

window.useViewport = useViewport;

// ============================================================
// SHARED ATOMS — Section, Eyebrow, Rule, Pill, Placeholder
// ============================================================

function Section({ id, children, theme, padding = "120px 0", mobilePadding, style }) {
  const { isMobile } = useViewport();
  const resolvedPadding = isMobile ? (mobilePadding || "56px 0") : padding;
  return (
    <section
      id={id}
      style={{
        position: "relative",
        padding: resolvedPadding,
        ...style,
      }}
    >
      <div
        style={{
          maxWidth: 1280,
          margin: "0 auto",
          padding: isMobile ? "0 20px" : "0 48px",
          position: "relative",
        }}
      >
        {children}
      </div>
    </section>
  );
}

function Eyebrow({ children, theme, accent }) {
  return (
    <div
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 10,
        fontSize: 12,
        letterSpacing: 1.6,
        textTransform: "uppercase",
        color: accent ? theme.accent : theme.sub,
        fontWeight: 600,
        marginBottom: 28,
      }}
    >
      <span
        style={{
          display: "inline-block",
          width: 22,
          height: 1.5,
          background: theme.accent,
        }}
      />
      {children}
    </div>
  );
}

function Rule({ theme, full }) {
  return (
    <div
      style={{
        height: 1.5,
        background: theme.rule,
        width: full ? "100%" : 56,
        margin: full ? "0" : "0",
        opacity: full ? 1 : 0.9,
      }}
    />
  );
}

function StatusChip({ children, color, theme }) {
  return (
    <span
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 8,
        padding: "5px 11px",
        borderRadius: 999,
        background: theme.chip,
        border: `1px solid ${theme.chipBorder}`,
        color: theme.chipText,
        fontSize: 11.5,
        letterSpacing: 0.4,
        fontFamily: "'JetBrains Mono', 'SF Mono', ui-monospace, monospace",
      }}
    >
      <span
        style={{
          width: 6,
          height: 6,
          borderRadius: "50%",
          background: color,
          boxShadow: `0 0 8px ${color}`,
        }}
      />
      {children}
    </span>
  );
}

// Editorial-style placeholder: dotted underline + small "TO VERIFY" tag.
// Reads as a serious working draft, not broken.
function Placeholder({ children, note, theme }) {
  return (
    <span
      style={{
        position: "relative",
        display: "inline",
        backgroundImage: `linear-gradient(${theme.accent}, ${theme.accent})`,
        backgroundRepeat: "no-repeat",
        backgroundSize: "100% 1.5px",
        backgroundPosition: "0 100%",
        paddingBottom: 2,
      }}
      title={note || "To verify"}
    >
      {children}
      <sup
        style={{
          marginLeft: 6,
          fontSize: 10,
          letterSpacing: 1,
          fontFamily: "'JetBrains Mono', 'SF Mono', ui-monospace, monospace",
          color: theme.accent,
          background: `${theme.accent}1f`,
          padding: "1px 5px",
          borderRadius: 4,
          fontWeight: 600,
          top: -8,
          position: "relative",
        }}
      >
        TO VERIFY
      </sup>
    </span>
  );
}

window.Section = Section;
window.Eyebrow = Eyebrow;
window.Rule = Rule;
window.StatusChip = StatusChip;
window.Placeholder = Placeholder;

// ============================================================
// MOTION PRIMITIVES — Reveal, SpotlightCard, Marquee, CountUp
// transform/opacity only; IO-driven; everything collapses to
// static under dyslexia mode or OS reduced-motion.
// ============================================================

(function injectMotionCSS() {
  if (document.getElementById("brains-motion-css")) return;
  const style = document.createElement("style");
  style.id = "brains-motion-css";
  style.textContent = `
    .bv-reveal {
      opacity: 0;
      transform: translateY(26px);
      transition:
        opacity .9s cubic-bezier(.16,1,.3,1),
        transform .9s cubic-bezier(.16,1,.3,1);
      transition-delay: var(--bv-delay, 0ms);
      will-change: transform, opacity;
    }
    .bv-reveal.bv-in { opacity: 1; transform: none; }

    @keyframes bv-breathe {
      0%, 100% { transform: scale(1); opacity: .9; }
      50% { transform: scale(1.35); opacity: .5; }
    }
    @keyframes bv-caret {
      0%, 49% { opacity: 1; }
      50%, 100% { opacity: 0; }
    }
    .bv-press { transition: transform .15s cubic-bezier(.16,1,.3,1); }
    .bv-press:active { transform: scale(.98); }

    @media (prefers-reduced-motion: reduce) {
      .bv-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
    }
    body.dyslexia-mode .bv-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
    body.dyslexia-mode .bv-marquee-track { transform: none !important; }
  `;
  document.head.appendChild(style);
})();

// Heavy motion (reveals, 3D, count-ups) respects OS reduced-motion + dyslexia.
function brainsMotionOK() {
  if (typeof window === "undefined") return false;
  if (document.body.classList.contains("dyslexia-mode")) return false;
  if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
  return true;
}
// Light decorative motion (marquee) halts only on the explicit dyslexia toggle,
// consistent with the hero constellation behaviour.
function brainsDecorOK() {
  if (typeof window === "undefined") return false;
  return !document.body.classList.contains("dyslexia-mode");
}
window.brainsMotionOK = brainsMotionOK;
window.brainsDecorOK = brainsDecorOK;

function Reveal({ children, delay = 0, style, as = "div" }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (!brainsMotionOK() || !("IntersectionObserver" in window)) {
      el.classList.add("bv-in");
      return;
    }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("bv-in");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const Tag = as;
  return (
    <Tag ref={ref} className="bv-reveal" style={{ "--bv-delay": `${delay}ms`, ...style }}>
      {children}
    </Tag>
  );
}

// Cursor-tracked radial border highlight. Writes CSS vars via ref — no re-renders.
function SpotlightCard({ children, theme, style, radius = 18, padding }) {
  const ref = React.useRef(null);
  const onMove = (e) => {
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    el.style.setProperty("--sx", `${e.clientX - r.left}px`);
    el.style.setProperty("--sy", `${e.clientY - r.top}px`);
    el.style.setProperty("--so", "1");
  };
  const onLeave = () => {
    const el = ref.current;
    if (el) el.style.setProperty("--so", "0");
  };
  return (
    <div
      ref={ref}
      onPointerMove={onMove}
      onPointerLeave={onLeave}
      style={{
        position: "relative",
        borderRadius: radius,
        background: theme.card,
        border: `1px solid ${theme.cardBorder}`,
        boxShadow: "inset 0 1px 0 rgba(255,255,255,0.05)",
        overflow: "hidden",
        padding,
        ...style,
      }}
    >
      <div
        aria-hidden
        style={{
          position: "absolute",
          inset: 0,
          opacity: "var(--so, 0)",
          transition: "opacity .4s ease",
          background: `radial-gradient(280px circle at var(--sx, 50%) var(--sy, 50%), ${theme.accent}14, transparent 65%)`,
          pointerEvents: "none",
        }}
      />
      <div style={{ position: "relative", height: "100%", display: "flex", flexDirection: "column" }}>{children}</div>
    </div>
  );
}

// Kinetic marquee — rAF-driven so it can't be killed by CSS resets.
function Marquee({ items, theme, speed = 34, style }) {
  const trackRef = React.useRef(null);
  const copyRef = React.useRef(null);
  React.useEffect(() => {
    if (!brainsDecorOK()) return;
    const track = trackRef.current;
    const copy = copyRef.current;
    if (!track || !copy) return;
    let raf = 0;
    let x = 0;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      const w = copy.offsetWidth || 1;
      x -= (w / speed) * dt;
      if (x <= -w) x += w;
      track.style.transform = `translate3d(${x}px, 0, 0)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [speed]);

  const row = (key, withRef) => (
    <div key={key} ref={withRef ? copyRef : null} style={{ display: "inline-flex", flexShrink: 0 }}>
      {items.map((it, i) => (
        <span
          key={`${key}-${i}`}
          style={{ display: "inline-flex", alignItems: "center", gap: 28, paddingRight: 28, whiteSpace: "nowrap" }}
        >
          <span
            style={{
              fontSize: "clamp(14px, 1.5vw, 19px)",
              fontWeight: 700,
              letterSpacing: 2.5,
              textTransform: "uppercase",
              color: theme.mute,
            }}
          >
            {it}
          </span>
          <span aria-hidden style={{ color: theme.accent, fontSize: 11 }}>∞</span>
        </span>
      ))}
    </div>
  );

  return (
    <div
      aria-hidden
      style={{
        overflow: "hidden",
        maskImage: "linear-gradient(90deg, transparent, black 12%, black 88%, transparent)",
        WebkitMaskImage: "linear-gradient(90deg, transparent, black 12%, black 88%, transparent)",
        padding: "16px 0",
        ...style,
      }}
    >
      <div className="bv-marquee-track" style={{ display: "inline-flex", willChange: "transform" }} ref={trackRef}>
        {row("a", true)}
        {row("b", false)}
      </div>
    </div>
  );
}

// Animated numeral — counts from 0 to `value` when scrolled into view.
// Renders `prefix`/`suffix` around the number. Static under reduced motion.
function CountUp({ value, prefix = "", suffix = "", duration = 1400, style }) {
  const ref = React.useRef(null);
  const [display, setDisplay] = React.useState(brainsMotionOK() ? 0 : value);
  React.useEffect(() => {
    if (!brainsMotionOK() || !("IntersectionObserver" in window)) {
      setDisplay(value);
      return;
    }
    let raf = 0;
    const io = new IntersectionObserver(
      (es) => {
        es.forEach((e) => {
          if (!e.isIntersecting) return;
          io.disconnect();
          const start = performance.now();
          const step = (now) => {
            const p = Math.min((now - start) / duration, 1);
            const eased = 1 - Math.pow(1 - p, 3);
            setDisplay(Math.round(value * eased));
            if (p < 1) raf = requestAnimationFrame(step);
          };
          raf = requestAnimationFrame(step);
        });
      },
      { threshold: 0.5 }
    );
    if (ref.current) io.observe(ref.current);
    return () => {
      io.disconnect();
      cancelAnimationFrame(raf);
    };
  }, [value, duration]);
  return (
    <span ref={ref} style={style}>
      {prefix}
      {display}
      {suffix}
    </span>
  );
}

// Typewriter loop — cycles through lines char-by-char with a blinking caret.
// Light decorative motion: halts only on the dyslexia toggle (consistent with
// the marquee); renders the first line statically otherwise.
function TypeLoop({ lines, theme, typeMs = 34, holdMs = 1700, style }) {
  const [text, setText] = React.useState(brainsDecorOK() ? "" : lines[0]);
  React.useEffect(() => {
    if (!brainsDecorOK()) return;
    let line = 0;
    let char = 0;
    let deleting = false;
    let timer;
    const step = () => {
      const full = lines[line];
      if (!deleting) {
        char++;
        setText(full.slice(0, char));
        if (char >= full.length) {
          deleting = true;
          timer = setTimeout(step, holdMs);
          return;
        }
        timer = setTimeout(step, typeMs);
      } else {
        char -= 3;
        if (char <= 0) {
          char = 0;
          deleting = false;
          line = (line + 1) % lines.length;
        }
        setText(lines[line].slice(0, Math.max(char, 0)));
        timer = setTimeout(step, deleting ? 14 : 240);
      }
    };
    timer = setTimeout(step, 600);
    return () => clearTimeout(timer);
  }, []);
  return (
    <span style={style}>
      {text}
      <span
        aria-hidden
        style={{
          display: "inline-block",
          width: 8,
          height: "1.05em",
          marginLeft: 3,
          verticalAlign: "text-bottom",
          background: theme.accent,
          animation: brainsDecorOK() ? "bv-caret 1.1s step-end infinite" : "none",
        }}
      />
    </span>
  );
}

// Live evaluation terminal — a frosted mono panel that types a BRAINS
// Benchmark run on loop: a system scored against the neurodivergent
// access criteria. `lines` is an array of strings; `title` is the header.
function EvalTerminal({ theme, title, lines, style }) {
  return (
    <div
      style={{
        borderRadius: 14,
        border: `1px solid ${theme.cardBorder}`,
        background: theme.dark ? "rgba(4,8,18,0.72)" : "rgba(250,246,234,0.82)",
        backdropFilter: "blur(10px)",
        WebkitBackdropFilter: "blur(10px)",
        boxShadow: "inset 0 1px 0 rgba(255,255,255,0.06)",
        overflow: "hidden",
        fontFamily: "'JetBrains Mono', 'SF Mono', ui-monospace, monospace",
        ...style,
      }}
    >
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          padding: "10px 16px",
          borderBottom: `1px solid ${theme.cardBorder}`,
          fontSize: 10.5,
          letterSpacing: 1.4,
          textTransform: "uppercase",
          color: theme.mute,
          fontWeight: 600,
        }}
      >
        <span
          style={{
            width: 7,
            height: 7,
            borderRadius: "50%",
            background: theme.success,
            animation: brainsDecorOK() ? "bv-breathe 2.4s ease-in-out infinite" : "none",
          }}
        />
        {title}
      </div>
      <div style={{ padding: "16px 18px", fontSize: 13, lineHeight: 1.7, color: theme.sub, minHeight: 54 }}>
        <span style={{ color: theme.accent, userSelect: "none" }}>$ </span>
        <TypeLoop lines={lines} theme={theme} style={{ color: theme.ink }} />
      </div>
    </div>
  );
}

window.Reveal = Reveal;
window.SpotlightCard = SpotlightCard;
window.Marquee = Marquee;
window.CountUp = CountUp;
window.TypeLoop = TypeLoop;
window.EvalTerminal = EvalTerminal;
