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

/* =====================================================================
   CursorPixels — drops fading colored squares along the mouse path
   ===================================================================== */
function CursorPixels({ cellSize = 10, maxLength = 14, hsl = "138, 35%", decay = 0.05 }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");

    const resize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    };
    resize();
    window.addEventListener("resize", resize);

    const trails = [];
    let lastGX = null, lastGY = null;

    const pushCell = (gx, gy) => {
      trails.push({ x: gx * cellSize, y: gy * cellSize, life: 1.0 });
      while (trails.length > maxLength) trails.shift();
    };

    const onMove = (e) => {
      const tx = Math.floor(e.clientX / cellSize);
      const ty = Math.floor(e.clientY / cellSize);
      if (lastGX === null) {
        lastGX = tx; lastGY = ty;
        pushCell(tx, ty);
        return;
      }
      if (tx === lastGX && ty === lastGY) return;
      const dx = tx - lastGX;
      const dy = ty - lastGY;
      const steps = Math.max(Math.abs(dx), Math.abs(dy));
      for (let s = 1; s <= steps; s++) {
        const gx = Math.round(lastGX + (dx * s) / steps);
        const gy = Math.round(lastGY + (dy * s) / steps);
        pushCell(gx, gy);
      }
      lastGX = tx; lastGY = ty;
    };
    window.addEventListener("mousemove", onMove, { passive: true });

    let raf;
    const tick = () => {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for (let i = 0; i < trails.length; i++) {
        const p = trails[i];
        const ratio = i / (trails.length - 1 || 1);
        const lightness = 55 + ratio * 15;
        ctx.fillStyle = `hsla(${hsl}, ${lightness}%, ${p.life})`;
        ctx.fillRect(p.x, p.y, cellSize, cellSize);
        p.life -= decay;
      }
      while (trails.length > 0 && trails[0].life <= 0) trails.shift();
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("resize", resize);
    };
  }, [cellSize, maxLength, hsl, decay]);

  return <canvas ref={ref} className="cursor-pixels" aria-hidden="true" />;
}

/* =====================================================================
   Tweak defaults (editable on disk via Tweaks panel)
   ===================================================================== */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "tone": "warm",
  "accent": "#7da888",
  "density": "comfy",
  "showTinker": true
}/*EDITMODE-END*/;

/* ---- palettes (tone shifts the off-white hue) ---- */
const TONES = {
  warm: {
    "--bg":      "oklch(0.962 0.008 82)",
    "--bg-soft": "oklch(0.948 0.009 82)",
    "--card":    "oklch(0.987 0.005 88)",
    "--card-2":  "oklch(0.978 0.006 85)",
    "--line":    "oklch(0.90 0.010 80)",
    "--line-2":  "oklch(0.94 0.008 82)",
    "--ink":     "oklch(0.23 0.012 60)",
    "--ink-soft":"oklch(0.42 0.014 65)",
    "--ink-mute":"oklch(0.58 0.012 70)",
  },
  cool: {
    "--bg":      "oklch(0.964 0.006 240)",
    "--bg-soft": "oklch(0.950 0.007 240)",
    "--card":    "oklch(0.988 0.004 240)",
    "--card-2":  "oklch(0.980 0.005 240)",
    "--line":    "oklch(0.90 0.008 240)",
    "--line-2":  "oklch(0.94 0.006 240)",
    "--ink":     "oklch(0.23 0.012 240)",
    "--ink-soft":"oklch(0.42 0.014 240)",
    "--ink-mute":"oklch(0.58 0.012 240)",
  },
  paper: {
    "--bg":      "oklch(0.955 0.012 75)",
    "--bg-soft": "oklch(0.940 0.014 75)",
    "--card":    "oklch(0.985 0.008 80)",
    "--card-2":  "oklch(0.972 0.010 78)",
    "--line":    "oklch(0.88 0.012 75)",
    "--line-2":  "oklch(0.92 0.010 78)",
    "--ink":     "oklch(0.22 0.014 55)",
    "--ink-soft":"oklch(0.40 0.016 60)",
    "--ink-mute":"oklch(0.56 0.014 65)",
  },
};
const ACCENTS = {
  "#7da888": { "--accent": "oklch(0.58 0.06 145)", "--accent-soft": "oklch(0.92 0.025 145)" }, // sage
  "#c08163": { "--accent": "oklch(0.58 0.10 45)",  "--accent-soft": "oklch(0.92 0.030 45)"  }, // rust
  "#5d6b8a": { "--accent": "oklch(0.45 0.04 250)", "--accent-soft": "oklch(0.91 0.020 250)" }, // ink
  "#caa66a": { "--accent": "oklch(0.66 0.10 75)",  "--accent-soft": "oklch(0.93 0.035 75)"  }, // amber
};

function applyTokens(tone, accent) {
  const root = document.documentElement;
  const tokens = { ...TONES[tone], ...ACCENTS[accent] };
  Object.entries(tokens).forEach(([k, v]) => root.style.setProperty(k, v));
}

/* =====================================================================
   Content
   ===================================================================== */
const NAV = [];

const PROJECTS = [
  {
    name: "Coastline",
    tag: "current",
    live: true,
    desc: "A small, fast issue tracker for tiny teams. Keyboard-first, no boards by default.",
    meta: "2024 – now",
    thumb: "coastline",
  },
  {
    name: "Halftime",
    tag: "tool",
    desc: "Build logs, but cozy. A weekly journal for engineers who like to look back.",
    meta: "2023",
    thumb: "halftime",
  },
  {
    name: "Lintbeam",
    tag: "open source",
    desc: "Static checks for design tokens. Catches a card-radius drift before it ships.",
    meta: "2022",
    thumb: "lintbeam",
  },
  {
    name: "Notes on type",
    tag: "essay",
    desc: "A short visual essay on optical sizing and why titles feel “loud”.",
    meta: "2021",
    thumb: "type",
  },
];

const POSTS = [
  {
    title: "On writing fewer abstractions",
    sub: "A small library of patterns I keep reaching for, and the ones I gave up on.",
    date: "Mar 2026",
    read: "8 min",
  },
  {
    title: "Designing the empty state first",
    sub: "What I learned shipping a tool to one user (myself) for six months.",
    date: "Jan 2026",
    read: "5 min",
  },
  {
    title: "Latency is a feeling",
    sub: "Optimizing for the milliseconds your users can actually perceive.",
    date: "Nov 2025",
    read: "11 min",
  },
  {
    title: "Notebooks beat dashboards",
    sub: "Why a markdown file outperformed our analytics stack for a year.",
    date: "Sep 2025",
    read: "6 min",
  },
];

const TINKER = [
  { name: "miniterm", blurb: "A 200-line terminal emulator in the browser.", glyph: "›_" },
  { name: "softgrid", blurb: "CSS subgrid playground for layout intuition.", glyph: "▦" },
  { name: "kana", blurb: "Spaced-repetition deck that lives in a single HTML file.", glyph: "あ" },
];

/* =====================================================================
   Subcomponents
   ===================================================================== */

function Nav({ active, setActive }) {
  const [copied, setCopied] = useState(false);
  const copyInvite = async () => {
    const origin = "https://willtholke.com";
    const invite = `Meet Will Tholke's agent at ${origin}. Docs: ${origin}/llms.txt`;
    try {
      await navigator.clipboard.writeText(invite);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    } catch (e) {
      console.error(e);
    }
  };
  return (
    <div className="nav-dock">
    <nav className="nav" data-screen-label="Top nav">
      <div className="nav-links">
        <button type="button" className="nav-tag nav-tag--btn" onClick={copyInvite}>
          <span>{copied ? "agent link copied" : "tell your agent to stop by"}</span>
          {copied ? (
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <polyline points="20 6 9 17 4 12"></polyline>
            </svg>
          ) : (
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
              <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
            </svg>
          )}
        </button>
        <a href="/llms.txt" className="nav-llms" target="_blank" rel="noopener">/llms.txt</a>
      </div>
    </nav>
    </div>
  );
}

function Hero() {
  return (
    <header className="hero" id="top" data-screen-label="Hero">
      <div className="hero-grid">
        <div className="hero-text">
          <h1>Hi, I'm Will's Agent. <em>AMA.</em></h1>
          <p className="hero-lede hero-lede--desktop">
            After studying Computer Science at UC Berkeley, Will wrote code at
            Atlassian, worked on the financial infra underlying $300M at Adam
            Nash's donor-advised fund, and is currently at Peregrine, deploying
            AI to government agencies across the Southwest. I'm not Will, but I
            can do anything he can, and sometimes <em>even better</em>.
          </p>
          <p className="hero-lede hero-lede--mobile">
            Will studied CS at Berkeley, wrote software at Atlassian and the
            former Wealthfront CEO's startup, and now deploys AI to gov agencies
            across the Southwest. I'm not Will, but I know him well.
          </p>
        </div>
        <img className="avatar" src="./sticker.png" alt="Pixel-art portrait of Will" />
      </div>
    </header>
  );
}

function NowCard() {
  return (
    <section className="section" id="now" data-screen-label="Now">
      <div className="section-head">
        <h2 className="section-title"><span className="marker"></span>Currently</h2>
        <span className="section-meta">updated 3d ago</span>
      </div>
      <div className="now">
        <div className="now-inner">
          <div className="now-grid">
            <div className="now-item">
              <span className="now-label">Building</span>
              <span className="now-value">Coastline — <span className="muted">v0.4, sync &amp; offline</span></span>
            </div>
            <div className="now-item">
              <span className="now-label">Reading</span>
              <span className="now-value"><span className="muted">A Pattern Language</span> · Alexander</span>
            </div>
            <div className="now-item">
              <span className="now-label">Listening</span>
              <span className="now-value">Nils Frahm — <span className="muted">All Melody</span></span>
            </div>
            <div className="now-item">
              <span className="now-label">Learning</span>
              <span className="now-value"><span className="accent">CRDTs</span> · and a little Rust</span>
            </div>
          </div>
          <div className="now-foot">
            <span>SF · 11:42 PT</span>
            <span>weather: 64°, fog clearing</span>
          </div>
        </div>
      </div>
    </section>
  );
}

function ProjectThumb({ kind }) {
  // Tiny abstract motifs — placeholders for real screenshots.
  const common = { width: 96, height: 76 };
  switch (kind) {
    case "coastline":
      return (
        <svg {...common} viewBox="0 0 96 76">
          <rect width="96" height="76" fill="oklch(0.945 0.012 145)"/>
          <path d="M0 56 Q 18 40 36 48 T 72 44 T 96 50 V76 H0 Z" fill="oklch(0.78 0.04 145)" opacity="0.7"/>
          <path d="M0 60 Q 22 50 44 56 T 96 58 V76 H0 Z" fill="oklch(0.62 0.06 145)"/>
          <circle cx="72" cy="22" r="6" fill="oklch(0.97 0.02 80)"/>
        </svg>
      );
    case "halftime":
      return (
        <svg {...common} viewBox="0 0 96 76">
          <rect width="96" height="76" fill="oklch(0.955 0.015 75)"/>
          <g stroke="oklch(0.45 0.02 60)" strokeWidth="1.2" fill="none">
            <line x1="14" y1="22" x2="82" y2="22"/>
            <line x1="14" y1="34" x2="62" y2="34"/>
            <line x1="14" y1="46" x2="74" y2="46"/>
            <line x1="14" y1="58" x2="50" y2="58"/>
          </g>
          <rect x="14" y="14" width="14" height="3" rx="1.5" fill="oklch(0.55 0.08 45)"/>
        </svg>
      );
    case "lintbeam":
      return (
        <svg {...common} viewBox="0 0 96 76">
          <rect width="96" height="76" fill="oklch(0.95 0.012 250)"/>
          <g fill="oklch(0.5 0.05 250)">
            <rect x="12" y="16" width="4" height="44" rx="2"/>
            <rect x="22" y="22" width="4" height="38" rx="2" opacity="0.7"/>
            <rect x="32" y="28" width="4" height="32" rx="2" opacity="0.55"/>
            <rect x="42" y="20" width="4" height="40" rx="2" opacity="0.85"/>
            <rect x="52" y="32" width="4" height="28" rx="2" opacity="0.45"/>
            <rect x="62" y="18" width="4" height="42" rx="2"/>
            <rect x="72" y="26" width="4" height="34" rx="2" opacity="0.7"/>
          </g>
        </svg>
      );
    case "type":
      return (
        <svg {...common} viewBox="0 0 96 76">
          <rect width="96" height="76" fill="oklch(0.96 0.014 65)"/>
          <text x="14" y="58" fontFamily="Instrument Serif, serif" fontSize="58" fontStyle="italic" fill="oklch(0.30 0.02 50)">Aa</text>
        </svg>
      );
    default: return <svg {...common}/>;
  }
}

function Projects() {
  return (
    <section className="section" id="work" data-screen-label="Work">
      <div className="section-head">
        <h2 className="section-title"><span className="marker"></span>Selected work</h2>
        <span className="section-meta">{PROJECTS.length} pieces</span>
      </div>
      <div className="projects">
        {PROJECTS.map(p => (
          <a key={p.name} href="#" className="project" onClick={(e)=>e.preventDefault()}>
            <div className="project-thumb"><ProjectThumb kind={p.thumb}/></div>
            <div className="project-body">
              <div className="project-title">
                {p.name}
                <span className={"tag" + (p.live ? " live" : "")}>{p.tag}</span>
              </div>
              <p className="project-desc">{p.desc}</p>
            </div>
            <div style={{display:"flex", flexDirection:"column", alignItems:"flex-end", gap:8}}>
              <span className="project-meta">{p.meta}</span>
              <span className="project-arrow">→</span>
            </div>
          </a>
        ))}
      </div>
    </section>
  );
}

function Writing() {
  return (
    <section className="section" id="writing" data-screen-label="Writing">
      <div className="section-head">
        <h2 className="section-title"><span className="marker"></span>Writing</h2>
        <a className="section-meta" href="#" onClick={(e)=>e.preventDefault()} style={{textDecoration: "underline", textUnderlineOffset: 3}}>archive →</a>
      </div>
      <div className="writing">
        {POSTS.map(p => (
          <a key={p.title} href="#" className="post" onClick={(e)=>e.preventDefault()}>
            <div className="post-title">
              {p.title}
              <span className="sub">{p.sub}</span>
            </div>
            <span className="post-read">{p.read}</span>
            <span className="post-date">{p.date}</span>
          </a>
        ))}
      </div>
    </section>
  );
}

function Tinker() {
  return (
    <section className="section" id="tinker" data-screen-label="Tinker">
      <div className="section-head">
        <h2 className="section-title"><span className="marker"></span>Small experiments</h2>
        <span className="section-meta">weekends &amp; long flights</span>
      </div>
      <div className="tinker">
        {TINKER.map(t => (
          <a key={t.name} href="#" className="tinker-card" onClick={(e)=>e.preventDefault()}>
            <div className="tinker-glyph" style={{fontFamily: "var(--mono)", fontSize: 13}}>{t.glyph}</div>
            <div className="tinker-name">{t.name}</div>
            <div className="tinker-blurb">{t.blurb}</div>
          </a>
        ))}
      </div>
    </section>
  );
}

function UserBubble({ text, refProp }) {
  return (
    <div ref={refProp} className="chat-user-bubble">{text}</div>
  );
}

function AnimatedChunk({ text }) {
  const ref = useRef(null);
  useEffect(() => {
    if (!ref.current) return;
    ref.current.animate(
      [{ opacity: 0, filter: "blur(3px)" }, { opacity: 1, filter: "blur(0px)" }],
      { duration: 200, easing: "ease-out", fill: "both" }
    );
  }, []);
  return <span ref={ref}>{text}</span>;
}

function Contact() {
  const [messages, setMessages] = useState([]);
  const [draft, setDraft] = useState("");
  const [pending, setPending] = useState(false);
  const [activeChip, setActiveChip] = useState(null);
  const [chipsCollapsed, setChipsCollapsed] = useState(false);
  const listRef = useRef(null);
  const inputRef = useRef(null);
  const lastUserRef = useRef(null);
  const shouldScrollRef = useRef(false);
  const chipsRef = useRef(null);
  const chipsFullWidth = useRef(null);

  useEffect(() => {
    if (shouldScrollRef.current && lastUserRef.current) {
      shouldScrollRef.current = false;
      lastUserRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
    }
  }, [messages]);

  useEffect(() => {
    if (!pending && inputRef.current) inputRef.current.focus();
  }, [pending]);

  useEffect(() => {
    if (!activeChip) return;
    const onDown = (e) => { if (chipsRef.current && !chipsRef.current.contains(e.target)) setActiveChip(null); };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [activeChip]);

  useEffect(() => {
    const el = chipsRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => {
      if (!chipsFullWidth.current) chipsFullWidth.current = el.scrollWidth;
      setChipsCollapsed(el.clientWidth < chipsFullWidth.current);
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const doSend = async (next) => {
    const apiMessages = next
      .map(m => ({ role: m.role === "user" ? "user" : "assistant", content: m.chunks ? m.chunks.join("") : m.text }));
    const last = apiMessages[apiMessages.length - 1];
    if (last?.role === "user" && last.content === "Surprise me") {
      last.content = "Share something interesting or surprising about Will that hasn't come up yet in this conversation. Keep it natural and conversational — don't announce that you're surprising them, just tell them the thing.";
    }
    let agentStarted = false;
    try {
      const r = await fetch("/api/chat", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ messages: apiMessages }),
      });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const reader = r.body.getReader();
      const decoder = new TextDecoder();
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        const chunk = decoder.decode(value, { stream: true });
        if (!chunk) continue;
        if (!agentStarted) {
          agentStarted = true;
          setMessages(m => [...m, { role: "agent", chunks: [chunk] }]);
        } else {
          setMessages(m => {
            const last = m[m.length - 1];
            return [...m.slice(0, -1), { ...last, chunks: [...last.chunks, chunk] }];
          });
        }
      }
      if (!agentStarted) setMessages(m => [...m, { role: "agent", chunks: ["(no reply)"] }]);
    } catch {
      setMessages(m => [...m, { role: "agent", chunks: ["I'm offline right now. Will hasn't deployed me yet. In the meantime you can email him at will@tholke.com."] }]);
    } finally {
      setPending(false);
    }
  };

  const sendText = async (text) => {
    if (!text || pending) return;
    const next = [...messages, { role: "user", text }];
    setMessages(next);
    shouldScrollRef.current = true;
    setDraft("");
    if (inputRef.current) inputRef.current.style.height = "auto";
    setPending(true);
    inputRef.current?.focus();
    await doSend(next);
  };

  const send = async (e) => {
    e.preventDefault();
    const text = draft.trim();
    if (!text || pending) return;
    const next = [...messages, { role: "user", text }];
    setMessages(next);
    shouldScrollRef.current = true;
    setDraft("");
    if (inputRef.current) inputRef.current.style.height = "auto";
    setPending(true);
    inputRef.current?.focus();
    await doSend(next);
  };

  return (
    <div className="chat-area">
      <div className="chat-thread" ref={listRef}>
        {(() => {
          let lastUserIdx = -1;
          let lastAgentIdx = -1;
          for (let i = messages.length - 1; i >= 0; i--) {
            if (messages[i].role === "user" && lastUserIdx === -1) lastUserIdx = i;
            if (messages[i].role === "agent" && lastAgentIdx === -1) lastAgentIdx = i;
            if (lastUserIdx !== -1 && lastAgentIdx !== -1) break;
          }
          return messages.map((m, i) => (
            m.role === "agent"
              ? <div key={i} className="chat-agent-row">
                  {i === lastAgentIdx && <img src="./sticker.png" className="chat-agent-avatar" alt="" aria-hidden="true" />}
                  <p className={`chat-agent-text${i !== lastAgentIdx ? " chat-agent-text--no-avatar" : ""}`}>{m.chunks.map((c, ci) => <AnimatedChunk key={ci} text={c} />)}</p>
                </div>
              : <UserBubble key={i} text={m.text} refProp={i === lastUserIdx ? lastUserRef : null} />
          ));
        })()}
        {pending && messages[messages.length - 1]?.role !== "agent" && (
          <div className="chat-typing" aria-label="Agent is typing">
            <span></span><span></span><span></span>
          </div>
        )}
        {messages.length > 0 && <div className="chat-thread-spacer" aria-hidden="true"/>}
      </div>
      <div className="chat-dock">
        <section className="contact" id="contact" data-screen-label="Contact">
          <form className="chat-input" onSubmit={send}>
            <textarea
              ref={inputRef}
              value={draft}
              onChange={(e) => {
                setDraft(e.target.value);
                e.target.style.height = "auto";
                e.target.style.height = Math.min(e.target.scrollHeight, 180) + "px";
              }}
              onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(e); } }}
              placeholder="Ask me anything..."
              aria-label="Message"
              maxLength={1000}
              autoFocus
            />
            <div className="chat-bottom-bar">
              {(() => {
                const CHIPS = [
                  { label: "Learn", icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg>, prompts: ["What is Will learning right now?", "What books is Will reading?", "How does Will approach learning new things?"] },
                  { label: "Code", icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>, prompts: ["What projects is Will building?", "What's Will's favorite thing to build?", "What tech does Will use day-to-day?"] },
                  { label: "Life stuff", icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>, prompts: ["Tell me about Will's pets", "What music does Will listen to?", "What's Will like outside of work?"] },
                  { label: "Contact", icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>, direct: "Can you help me get in touch with Will?" },
                  { label: "Surprise me", icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>, direct: "Surprise me" },
                ];
                return (
                  <div className="chat-chips" style={{position:"relative"}} ref={chipsRef}>
                    {activeChip && (() => {
                      const chip = CHIPS.find(c => c.label === activeChip);
                      return (
                        <div className="chip-dropdown">
                          <div className="chip-dropdown-head">
                            {chip.icon}<span>{chip.label}</span>
                            <button type="button" className="chip-dropdown-close" onClick={() => setActiveChip(null)} aria-label="Close">
                              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                            </button>
                          </div>
                          {chip.prompts.map((p, i) => (
                            <button key={i} type="button" className="chip-dropdown-item" onClick={() => { setActiveChip(null); sendText(p); }}>
                              {p}
                            </button>
                          ))}
                        </div>
                      );
                    })()}
                    {CHIPS.map(({ label, icon, direct }) => (
                      <button key={label} type="button" className={"chat-chip" + (activeChip === label ? " active" : "") + (chipsCollapsed ? " icon-only" : "")} title={chipsCollapsed ? label : undefined} onClick={() => { if (direct) { sendText(direct); } else { setActiveChip(activeChip === label ? null : label); } }}>
                        {icon}{!chipsCollapsed && <span className="chip-label">{label}</span>}
                      </button>
                    ))}
                  </div>
                );
              })()}
              <button type="submit" className="chat-send" aria-label="Send" disabled={pending || !draft.trim()}>
                <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                  <path d="M4 11h12.17l-4.88-4.88L12.71 5l7 7-7 7-1.42-1.12L16.17 13H4v-2z"/>
                </svg>
              </button>
            </div>
          </form>
        </section>
        <Footer/>
      </div>
    </div>
  );
}

function Footer() {
  return (
    <footer className="footer">
      <span className="footer-copy">© 2026 · Will Tholke</span>
      <div className="footer-links">
        <a href="https://github.com/willtholke" target="_blank" rel="noopener noreferrer">github</a>
        <a href="https://x.com/willtholke" target="_blank" rel="noopener noreferrer">twitter</a>
        <a href="https://linkedin.com/in/willtholke" target="_blank" rel="noopener noreferrer">linkedin</a>
      </div>
    </footer>
  );
}

/* =====================================================================
   App
   ===================================================================== */
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [active, setActive] = useState("top");

  // Apply tone + accent tokens
  useEffect(() => { applyTokens(t.tone, t.accent); }, [t.tone, t.accent]);

  // Density tweak: adjust section spacing
  useEffect(() => {
    document.documentElement.style.setProperty(
      "--section-gap",
      t.density === "tight" ? "60px" : t.density === "airy" ? "120px" : "84px"
    );
    document.querySelectorAll(".section").forEach(s => {
      s.style.paddingTop = getComputedStyle(document.documentElement).getPropertyValue("--section-gap");
    });
  }, [t.density]);

  // Scroll spy
  useEffect(() => {
    const ids = ["top", ...NAV.map(n => n.id)];
    const onScroll = () => {
      let current = "top";
      for (const id of ids) {
        const el = document.getElementById(id);
        if (!el) continue;
        const top = el.getBoundingClientRect().top;
        if (top < 140) current = id;
      }
      setActive(current);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <>
      <CursorPixels />
      <div className="page">
        <Nav active={active} setActive={setActive}/>
        <Hero/>
        {/* Temporarily hidden:
        <NowCard/>
        <Projects/>
        <Writing/>
        {t.showTinker && <Tinker/>}
        */}
        <Contact/>
      </div>

      <TweaksPanel title="Tweaks">
        <TweakSection label="Tone" />
        <TweakRadio
          label="Background"
          value={t.tone}
          onChange={(v)=>setTweak("tone", v)}
          options={["warm", "cool", "paper"]}
        />
        <TweakColor
          label="Accent"
          value={t.accent}
          onChange={(v)=>setTweak("accent", v)}
          options={["#7da888", "#c08163", "#5d6b8a", "#caa66a"]}
        />
        <TweakSection label="Layout" />
        <TweakRadio
          label="Density"
          value={t.density}
          onChange={(v)=>setTweak("density", v)}
          options={["tight", "comfy", "airy"]}
        />
        <TweakToggle
          label="Show Tinker"
          value={t.showTinker}
          onChange={(v)=>setTweak("showTinker", v)}
        />
      </TweaksPanel>
    </>
  );
}

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