// In Turn — main app.
// Sections: Nav, Hero, Live Board demo, Features, Team, Brand, Waitlist, Footer.
// Uses window.IN_TURN_LOGOS / IN_TURN_LISTINGS / IN_TURN_CITIES / IN_TURN_TRACKS.

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

// ─── Tokens (single source of truth, also surfaced via Tweaks) ──────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "activeLogo": "rotate",
  "accent": "#D85F2D",
  "heroVariant": "marquee",
  "chartDensity": "cozy"
}/*EDITMODE-END*/;

// Pre-curated accent swatches (warm palette per brief)
const ACCENT_OPTIONS = ["#D85F2D", "#C8923B", "#9C3A1B", "#3F6B4E"];

// Tagline ticker words that animate on a slot machine in the hero — each completes "It's your ___"
const HERO_WORDS = ["turn.", "shot.", "summer.", "big break.", "first gig.", "move."];

// ─── Launch + contact config — EDIT THESE BEFORE LAUNCH ──────────────────────
// One place to update dates, contact email, and social links.
const CONTACT_EMAIL = "inturninquiries@gmail.com";
// Paste full profile URLs to surface these links; leave "" to hide them (no dead links).
const SOCIAL = { linkedin: "", instagram: "" };
// Public-facing launch copy.
const LAUNCH = { window: "September 2026", firstBrief: "Tue, Sep 8" };

// ─── Supabase waitlist integration ───────────────────────────────────────
// Signups are inserted straight into your Supabase table from the browser.
// The anon (publishable) key is SAFE to ship publicly — Row Level Security
// restricts it to inserts only. Fill these in from:
//   Supabase → Project Settings → API  (Project URL + anon public key)
// Table + policy SQL is in the chat. Until configured, the modal still works
// (it just won't persist).
const SUPABASE = {
  url: "",                 // ← e.g. https://abcdyourproject.supabase.co
  anonKey: "",             // ← anon / publishable key (safe to expose with RLS)
  table: "waitlist",
};

// Fire-and-forget insert. Never blocks the success animation.
async function saveWaitlistSignup(form, position) {
  if (!SUPABASE.url || !SUPABASE.anonKey) return;
  try {
    await fetch(`${SUPABASE.url}/rest/v1/${SUPABASE.table}`, {
      method: "POST",
      headers: {
        "apikey": SUPABASE.anonKey,
        "Authorization": `Bearer ${SUPABASE.anonKey}`,
        "Content-Type": "application/json",
        "Prefer": "return=minimal",
      },
      body: JSON.stringify({
        name: form.name,
        email: form.email,
        school: form.school,
        position: position,
      }),
    });
  } catch (e) {
    /* never block the UX on a network hiccup */
  }
}

// ─── Helpers ────────────────────────────────────────────────────────────────
function classNames(...xs) { return xs.filter(Boolean).join(" "); }

function useSlotMachine(words, intervalMs = 2200) {
  const [i, setI] = useState(0);
  useEffect(() => {
    const t = setInterval(() => setI((x) => (x + 1) % words.length), intervalMs);
    return () => clearInterval(t);
  }, [words.length, intervalMs]);
  return [words[i], i];
}

// ─── Nav ────────────────────────────────────────────────────────────────────
function Nav({ Logo, accent, onJoin }) {
  return (
    <header className="it-nav">
      <a className="it-nav-brand" href="#top">
        <Logo size={28} accent={accent} />
      </a>
      <nav className="it-nav-links">
        <a href="#board">Board</a>
        <a href="#features">How it works</a>
        <a href="#team">Team</a>
      </nav>
      <button className="it-nav-cta" onClick={onJoin} style={{ background: accent }}>
        Join the waitlist
        <span aria-hidden="true">→</span>
      </button>
    </header>
  );
}

// ─── Hero ───────────────────────────────────────────────────────────────────
function Hero({ accent, variant, onJoin }) {
  const [word] = useSlotMachine(HERO_WORDS, 2400);
  return (
    <section className="it-hero" id="top">
      <div className="it-hero-eyebrow">
        <span className="it-dot" style={{ background: accent }} />
        <span>Now indexing 100+ Canadian communications internships</span>
      </div>

      <h1 className="it-hero-title">
        <span>It&rsquo;s your</span>{" "}
        <span className="it-hero-slot">
          <span className="it-hero-slot-track" key={word}>
            <em>{word}</em>
          </span>
        </span>
      </h1>

      <p className="it-hero-sub">
        A curated job board for Canadian students and new grads chasing roles in
        PR, editorial, brand, and social. One inbox. No noise. One application
        away.
      </p>

      <div className="it-hero-cta">
        <button className="it-btn it-btn-primary it-btn-xl" onClick={onJoin} style={{ background: accent }}>
          Join the waitlist
          <span className="it-btn-arrow" aria-hidden="true">→</span>
        </button>
        <span className="it-hero-cta-note">2,400+ Canadian students already in</span>
      </div>

      {variant === "marquee" ? <CompanyMarquee /> : <CompanyGrid />}
    </section>
  );
}

function CompanyMarquee() {
  const names = [
    "Shopify", "Lululemon", "CBC", "Hootsuite", "Air Canada",
    "Aritzia", "MLSE", "RBC", "The Globe & Mail", "Bell Media",
    "Roots", "Canada Goose",
  ];
  const row = [...names, ...names];
  return (
    <div className="it-marquee" aria-hidden="true">
      <div className="it-marquee-track">
        {row.map((n, i) => (
          <span className="it-marquee-item" key={i}>
            <span className="it-marquee-dot" />
            {n}
          </span>
        ))}
      </div>
    </div>
  );
}

function CompanyGrid() {
  const names = [
    "Shopify", "Lululemon", "CBC", "Hootsuite", "Air Canada", "Aritzia",
  ];
  return (
    <div className="it-cgrid">
      {names.map((n) => (
        <div className="it-cgrid-cell" key={n}>{n}</div>
      ))}
    </div>
  );
}

// ─── The Chart — Top 100 ranked listings (interactive, kept for reference) ───────────
function Chart({ accent, density }) {
  const listings = window.IN_TURN_LISTINGS;
  const meta = window.IN_TURN_CHART;
  const cities = window.IN_TURN_CITIES;
  const tracks = window.IN_TURN_TRACKS;
  const modes = window.IN_TURN_MODES;

  const [q, setQ] = useState("");
  const [city, setCity] = useState("All cities");
  const [track, setTrack] = useState("All tracks");
  const [mode, setMode] = useState("Any");
  const [sort, setSort] = useState("rank");
  const [openId, setOpenId] = useState(null);
  const [saved, setSaved] = useState(() => new Set());

  function toggleSave(id) {
    setSaved((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }

  const filtered = useMemo(() => {
    const ql = q.trim().toLowerCase();
    let out = listings.filter((l) => {
      if (city !== "All cities") {
        if (city === "Remote") { if (l.mode !== "Remote") return false; }
        else if (l.city !== city) return false;
      }
      if (track !== "All tracks" && !l.tags.includes(track)) return false;
      if (mode !== "Any" && l.mode !== mode) return false;
      if (ql) {
        const hay = (l.role + " " + l.company + " " + l.tags.join(" ")).toLowerCase();
        if (!hay.includes(ql)) return false;
      }
      return true;
    });
    if (sort === "pay") {
      out = [...out].sort((a, b) => parseInt(b.pay.slice(1)) - parseInt(a.pay.slice(1)));
    } else if (sort === "deadline") {
      out = [...out].sort((a, b) => deadlineKey(a.deadline) - deadlineKey(b.deadline));
    }
    return out;
  }, [q, city, track, mode, sort, listings]);

  const activeFilterCount =
    (city !== "All cities" ? 1 : 0) +
    (track !== "All tracks" ? 1 : 0) +
    (mode !== "Any" ? 1 : 0) +
    (q.trim() ? 1 : 0);

  function clearAll() {
    setQ(""); setCity("All cities"); setTrack("All tracks"); setMode("Any");
  }

  return (
    <section className="it-section" id="board">
      <SectionHeader
        kicker="The chart"
        title={<>The Top 100, refreshed <em>every Tuesday.</em></>}
        sub="An AI scraper crawls Canadian career sites, agency boards, and newsrooms every week. Our editors rank what's left. This is the live working chart — try it."
      />

      <ChartMeta meta={meta} accent={accent} />

      <div className="it-chart">
        <div className="it-chart-toolbar">
          <div className="it-search">
            <span className="it-search-icon" aria-hidden="true">⌕</span>
            <input
              type="text"
              value={q}
              onChange={(e) => setQ(e.target.value)}
              placeholder="Search role, company, or tag…"
            />
            {q && (
              <button className="it-search-clear" onClick={() => setQ("")}>Clear</button>
            )}
          </div>
          <Dropdown label="City" value={city} options={cities} onChange={setCity} accent={accent} />
          <Dropdown label="Track" value={track} options={tracks} onChange={setTrack} accent={accent} />
          <Dropdown label="Mode" value={mode} options={modes} onChange={setMode} accent={accent} />
          <div className="it-chart-spacer" />
          <SortDropdown value={sort} onChange={setSort} />
        </div>

        <div className="it-chart-status">
          <span>
            Showing <strong>{filtered.length}</strong> of 100
            {activeFilterCount > 0 && (
              <span className="it-chart-status-filters">
                {" "}· {activeFilterCount} filter{activeFilterCount === 1 ? "" : "s"} active
              </span>
            )}
          </span>
          {activeFilterCount > 0 && (
            <button className="it-link" onClick={clearAll}>Reset filters</button>
          )}
        </div>

        <ol className={classNames("it-list", density === "compact" && "is-compact")}>
          {filtered.map((l) => (
            <RankRow
              key={l.id}
              listing={l}
              open={openId === l.id}
              saved={saved.has(l.id)}
              onToggle={() => setOpenId(openId === l.id ? null : l.id)}
              onSave={() => toggleSave(l.id)}
              accent={accent}
            />
          ))}
          {filtered.length === 0 && (
            <li className="it-list-empty">
              No matches. <button className="it-link" onClick={clearAll}>Reset filters</button>
            </li>
          )}
        </ol>
      </div>
    </section>
  );
}

function deadlineKey(s) {
  // "Mar 24" → 100 * monthIdx + day; works for sort order.
  const months = { Jan:1, Feb:2, Mar:3, Apr:4, May:5, Jun:6, Jul:7, Aug:8, Sep:9, Oct:10, Nov:11, Dec:12 };
  const [m, d] = s.split(" ");
  return (months[m] || 0) * 100 + parseInt(d, 10);
}

function ChartMeta({ meta, accent }) {
  return (
    <div className="it-cmeta">
      <div className="it-cmeta-row">
        <div className="it-cmeta-issue">
          <span className="it-cmeta-k">Issue</span>
          <span className="it-cmeta-v">Week {meta.week}</span>
        </div>
        <div className="it-cmeta-issue">
          <span className="it-cmeta-k">Refreshed</span>
          <span className="it-cmeta-v">{meta.issued}</span>
        </div>
        <div className="it-cmeta-issue">
          <span className="it-cmeta-k">Next crawl</span>
          <span className="it-cmeta-v">{meta.nextRefresh}</span>
        </div>
        <div className="it-cmeta-issue">
          <span className="it-cmeta-k">Sources</span>
          <span className="it-cmeta-v">{meta.sources}</span>
        </div>
        <div className="it-cmeta-status">
          <span className="it-cmeta-pulse" style={{ background: "#3F8A5B" }} aria-hidden="true" />
          <span>Scraper {meta.scraperStatus}</span>
        </div>
      </div>
      <div className="it-cmeta-stats">
        <span><strong>{meta.reviewedThisWeek.toLocaleString()}</strong> listings scanned this week</span>
        <span className="it-cmeta-sep">·</span>
        <span><strong style={{ color: accent }}>{meta.newThisWeek}</strong> new entries</span>
        <span className="it-cmeta-sep">·</span>
        <span><strong>{meta.movedThisWeek}</strong> moved up</span>
      </div>
    </div>
  );
}

function Dropdown({ label, value, options, onChange, accent }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    function onDoc(e) {
      if (ref.current && !ref.current.contains(e.target)) setOpen(false);
    }
    if (open) document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const isOn = value !== options[0];
  return (
    <div className="it-dd" ref={ref}>
      <button
        className={classNames("it-dd-btn", isOn && "is-on")}
        onClick={() => setOpen((o) => !o)}
        style={isOn ? { borderColor: accent, color: accent } : undefined}
      >
        <span className="it-dd-label">{label}</span>
        <span className="it-dd-value">{value}</span>
        <span className="it-dd-caret" aria-hidden="true">▾</span>
      </button>
      {open && (
        <div className="it-dd-menu" role="listbox">
          {options.map((o) => (
            <button
              key={o}
              role="option"
              aria-selected={o === value}
              className={classNames("it-dd-opt", o === value && "is-on")}
              onClick={() => { onChange(o); setOpen(false); }}
            >
              <span>{o}</span>
              {o === value && <span style={{ color: accent }}>✓</span>}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function SortDropdown({ value, onChange }) {
  const labels = { rank: "Rank", pay: "Pay", deadline: "Deadline" };
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    if (open) document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  return (
    <div className="it-dd it-dd-sort" ref={ref}>
      <button className="it-dd-btn" onClick={() => setOpen((o) => !o)}>
        <span className="it-dd-label">Sort</span>
        <span className="it-dd-value">{labels[value]}</span>
        <span className="it-dd-caret" aria-hidden="true">▾</span>
      </button>
      {open && (
        <div className="it-dd-menu" role="listbox">
          {Object.entries(labels).map(([k, l]) => (
            <button
              key={k}
              className={classNames("it-dd-opt", k === value && "is-on")}
              onClick={() => { onChange(k); setOpen(false); }}
            >{l}</button>
          ))}
        </div>
      )}
    </div>
  );
}

function CompanyLogo({ company, size = 52 }) {
  const slug = company
    .toLowerCase()
    .replace(/&/g, "and")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "");
  const [errored, setErrored] = useState(false);
  const initials = company
    .split(/\s+/)
    .filter(Boolean)
    .map((w) => w[0])
    .join("")
    .slice(0, 2)
    .toUpperCase();
  return (
    <div
      className={classNames("it-li-detail-logo", !errored && "has-img")}
      style={{ width: size, height: size }}
      aria-hidden="true"
    >
      {errored ? (
        <span>{initials}</span>
      ) : (
        <img
          src={`logos/${slug}.png`}
          alt=""
          className="it-li-detail-logo-img"
          onError={() => setErrored(true)}
        />
      )}
    </div>
  );
}

function RankRow({ listing, open, saved, onToggle, onSave, accent, demoTag, saveTag, demoCompact }) {
  const isTop10 = listing.rank <= 10;
  return (
    <li className={classNames("it-li", open && "is-open", isTop10 && "is-top10")} data-demo={demoTag || undefined}>
      <button className="it-li-row" onClick={onToggle} aria-expanded={open}>
        <span className="it-li-rank">
          {String(listing.rank).padStart(2, "0")}
        </span>
        <span className="it-li-chg"><ChangeChip change={listing.change} accent={accent} /></span>
        <span className="it-li-co">{listing.company}</span>
        <span className="it-li-role">{listing.role}</span>
        <span className="it-li-city">{listing.city}</span>
        <span className="it-li-mode">{listing.mode}</span>
        <span className="it-li-pay">{listing.pay}</span>
        <span className="it-li-dl">{listing.deadline}</span>
        <span className="it-li-badges">
          {listing.featured && <span className="it-star" style={{ color: accent }} title="Featured" aria-label="Featured">★</span>}
          {listing.closing && <span className="it-tag it-tag-warn">Closing</span>}
        </span>
        <span className="it-li-caret" aria-hidden="true">{open ? "–" : "+"}</span>
      </button>
      {open && (
        <div className={classNames("it-li-detail", demoCompact && "is-compact")}>
          <div className="it-li-detail-co">
            <div className="it-li-detail-co-meta">
              <div className="it-li-detail-co-name">{listing.company}</div>
              <div className="it-li-detail-co-about">{listing.about}</div>
            </div>
          </div>

          <div className="it-li-detail-section">
            <div className="it-li-detail-label">Job description</div>
            <p className="it-li-detail-text">{listing.job.overview}</p>
            <div className="it-li-quals-sub" style={{ marginTop: 4 }}>What you'll do</div>
            <ul className="it-li-detail-list">
              {(demoCompact ? listing.job.resp.slice(0, 3) : listing.job.resp).map((r, i) => <li key={i}>{r}</li>)}
            </ul>
          </div>

          {!demoCompact && listing.keySkills && listing.keySkills.length > 0 && (
            <div className="it-li-detail-section">
              <div className="it-li-detail-label">Key skills</div>
              <div className="it-li-detail-skills">
                {listing.keySkills.map((s, i) => (
                  <span key={i} className="it-li-detail-skill">{s}</span>
                ))}
              </div>
            </div>
          )}

          {!demoCompact && (
            <div className="it-li-detail-section">
              <div className="it-li-detail-label">Qualifications</div>
              <div className="it-li-quals">
                <div>
                  <div className="it-li-quals-sub">Required</div>
                  <ul className="it-li-detail-list">
                    {listing.qualifications.required.map((q, i) => <li key={i}>{q}</li>)}
                  </ul>
                </div>
                <div>
                  <div className="it-li-quals-sub">Preferred</div>
                  <ul className="it-li-detail-list">
                    {listing.qualifications.preferred.map((q, i) => <li key={i}>{q}</li>)}
                  </ul>
                </div>
              </div>
            </div>
          )}

          {!demoCompact && listing.job.offer && (
            <div className="it-li-detail-section">
              <div className="it-li-detail-label">What we offer</div>
              <ul className="it-li-detail-list">
                {listing.job.offer.map((o, i) => <li key={i}>{o}</li>)}
              </ul>
            </div>
          )}

          <div className="it-li-detail-grid">
            <DetailField k="Term" v={listing.term} />
            <DetailField k="Tracks" v={listing.tags.join(" · ")} />
            <DetailField k="Deadline" v={listing.deadline} />
            <DetailField k="Pay" v={listing.pay} />
          </div>

          <div className="it-li-detail-cta">
            <button className="it-btn it-btn-primary it-btn-sm" style={{ background: accent }}>
              Apply on company website ↗
            </button>
            <button
              className={classNames("it-btn it-btn-ghost it-btn-sm", saved && "is-saved")}
              onClick={(e) => { e.stopPropagation(); onSave(); }}
              style={saved ? { borderColor: accent, color: accent } : undefined}
              data-demo={saveTag || undefined}
            >
              {saved ? "✓ Saved" : "Save to list"}
            </button>
          </div>
        </div>
      )}
    </li>
  );
}

function ListHeader() {
  return (
    <div className="it-list-hdr" aria-hidden="true">
      <span>Rank</span>
      <span>Move</span>
      <span>Company</span>
      <span>Role</span>
      <span>City</span>
      <span>Mode</span>
      <span>Pay</span>
      <span>Deadline</span>
      <span>Status</span>
      <span></span>
    </div>
  );
}

function ChangeChip({ change, accent }) {
  if (change.dir === "new") {
    return <span className="it-chg is-new" style={{ color: accent, borderColor: accent }}>NEW</span>;
  }
  if (change.dir === "flat") {
    return <span className="it-chg is-flat" title="No change">—</span>;
  }
  if (change.dir === "up") {
    return <span className="it-chg is-up" title={`Up ${change.n}`}>▲{change.n}</span>;
  }
  return <span className="it-chg is-down" title={`Down ${change.n}`}>▼{change.n}</span>;
}

function DetailField({ k, v }) {
  return (
    <div className="it-li-field">
      <div className="it-li-field-k">{k}</div>
      <div className="it-li-field-v">{v}</div>
    </div>
  );
}

// ─── ChartDemo — scripted, looping product demo (replaces live Chart on the marketing page) ──
function ChartDemo({ accent, density }) {
  const listings = window.IN_TURN_LISTINGS;
  const meta = window.IN_TURN_CHART;
  const cities = window.IN_TURN_CITIES;
  const tracks = window.IN_TURN_TRACKS;
  const modes = window.IN_TURN_MODES;

  // Controlled state — driven entirely by the timeline below.
  const [q, setQ] = useState("");
  const [city, setCity] = useState("All cities");
  const [track, setTrack] = useState("All tracks");
  const [mode, setMode] = useState("Any");
  const [sort, setSort] = useState("rank");
  const [openId, setOpenId] = useState(null);
  const [saved, setSaved] = useState(() => new Set());
  const [openDD, setOpenDD] = useState(null);
  const [saveFlash, setSaveFlash] = useState(0);

  // Cursor + loop state
  const [cursor, setCursor] = useState({ x: 80, y: 80, visible: false, clicking: false });
  const [progress, setProgress] = useState(0);
  const [tick, setTick] = useState(0);
  const [paused, setPaused] = useState(false);

  const containerRef = useRef(null);

  // Same filtering logic as Chart, but always rank-sorted.
  const filtered = useMemo(() => {
    return listings.filter((l) => {
      if (city !== "All cities") {
        if (city === "Remote") { if (l.mode !== "Remote") return false; }
        else if (l.city !== city) return false;
      }
      if (track !== "All tracks" && !l.tags.includes(track)) return false;
      if (mode !== "Any" && l.mode !== mode) return false;
      return true;
    });
  }, [city, track, mode, listings]);

  const activeFilterCount =
    (city !== "All cities" ? 1 : 0) +
    (track !== "All tracks" ? 1 : 0) +
    (mode !== "Any" ? 1 : 0);

  // Pre-compute the listing we'll feature in the demo (first Toronto entry).
  const featured = useMemo(() => listings.find((l) => l.city === "Toronto, ON"), [listings]);

  // ── Timeline ───────────────────────────────────────────────────────────
  useEffect(() => {
    let cancelled = false;
    let progressInterval = null;
    const LOOP_MS = 10500;

    function wait(ms) {
      return new Promise((resolve) => {
        const start = performance.now();
        function tickWait() {
          if (cancelled) return resolve();
          if (paused) { requestAnimationFrame(tickWait); return; }
          if (performance.now() - start >= ms) return resolve();
          requestAnimationFrame(tickWait);
        }
        tickWait();
      });
    }

    function getCenter(selector) {
      const root = containerRef.current;
      if (!root) return null;
      const el = root.querySelector(selector);
      if (!el) return null;
      const c = root.getBoundingClientRect();
      const r = el.getBoundingClientRect();
      // Clamp cursor coordinates to stay inside the chart's visible bounds.
      const rawX = r.left - c.left + r.width / 2;
      const rawY = r.top - c.top + r.height / 2;
      return {
        x: Math.max(20, Math.min(c.width - 20, rawX)),
        y: Math.max(20, Math.min(c.height - 20, rawY)),
      };
    }

    async function moveTo(selector, dwellMs = 750) {
      // Retry briefly while React commits any pending state changes.
      let pt = null;
      for (let i = 0; i < 8 && !cancelled; i++) {
        pt = getCenter(selector);
        if (pt) break;
        await wait(40);
      }
      if (cancelled || !pt) return;
      // Wait one frame and re-measure so we use the fully-settled rect (post-scroll, post-layout).
      await new Promise((r) => requestAnimationFrame(r));
      const pt2 = getCenter(selector);
      if (pt2) pt = pt2;
      setCursor((c) => ({ ...c, x: pt.x, y: pt.y, visible: true, clicking: false }));
      await wait(dwellMs);
    }

    async function clickFx() {
      if (cancelled) return;
      setCursor((c) => ({ ...c, clicking: true }));
      await wait(240);
      setCursor((c) => ({ ...c, clicking: false }));
      await wait(120);
    }

    async function smoothScrollList(target) {
      const root = containerRef.current;
      const list = root && root.querySelector(".it-list");
      if (!list) return;
      const clamped = Math.max(0, Math.min(target, list.scrollHeight - list.clientHeight));
      const start = list.scrollTop;
      const delta = clamped - start;
      if (Math.abs(delta) < 2) return;
      const dur = 260;
      const t0 = performance.now();
      await new Promise((resolve) => {
        function step(now) {
          if (cancelled) return resolve();
          const t = Math.min(1, (now - t0) / dur);
          const eased = 1 - Math.pow(1 - t, 3);
          list.scrollTop = start + delta * eased;
          if (t < 1) requestAnimationFrame(step);
          else requestAnimationFrame(() => resolve());
        }
        requestAnimationFrame(step);
      });
    }

    async function ensureVisible(selector) {
      const root = containerRef.current;
      if (!root) return;
      const list = root.querySelector(".it-list");
      if (!list) return;
      for (let attempt = 0; attempt < 3; attempt++) {
        await new Promise((r) => requestAnimationFrame(r));
        await new Promise((r) => requestAnimationFrame(r));
        const el = root.querySelector(selector);
        if (!el) return;
        const elRect = el.getBoundingClientRect();
        const listRect = list.getBoundingClientRect();
        // Already comfortably visible?
        if (elRect.bottom <= listRect.bottom - 24 && elRect.top >= listRect.top + 16) {
          return;
        }
        // Compute target so element is positioned ~1/3 from the top of the visible area.
        const elOffsetInList = elRect.top - listRect.top + list.scrollTop;
        const target = Math.max(0, elOffsetInList - list.clientHeight / 3);
        await smoothScrollList(target);
      }
    }

    function resetState() {
      setQ(""); setCity("All cities"); setTrack("All tracks"); setMode("Any");
      setOpenDD(null); setOpenId(null); setSaved(new Set()); setSaveFlash(0);
      const root = containerRef.current;
      const list = root && root.querySelector(".it-list");
      if (list) list.scrollTop = 0;
    }

    async function runOnce() {
      const start = performance.now();
      progressInterval = setInterval(() => {
        if (cancelled) { clearInterval(progressInterval); return; }
        if (paused) return;
        const elapsed = performance.now() - start;
        setProgress(Math.min(1, elapsed / LOOP_MS));
      }, 60);

      resetState();
      setCursor({ x: 120, y: 90, visible: false, clicking: false });
      await wait(400);
      if (cancelled) return;

      // ① Filter by city → Toronto
      await moveTo('[data-demo="cityDD"]', 550);
      await clickFx();
      setOpenDD("city");
      await wait(320);
      await moveTo('[data-demo="cityOpt-Toronto, ON"]', 480);
      await clickFx();
      setCity("Toronto, ON");
      setOpenDD(null);

      // ② Open the top Toronto row
      await wait(350);
      await moveTo('[data-demo="firstRow"]', 600);
      await clickFx();
      if (featured) setOpenId(featured.id);

      // ③ Pause to read
      await wait(2200);
      if (cancelled) return;

      // ④ Save it
      await moveTo('[data-demo="saveBtn"]', 600);
      await clickFx();
      if (featured) {
        setSaved(new Set([featured.id]));
        setSaveFlash((n) => n + 1);
      }
      await wait(900);

      // ⑤ Reset filters → loop
      await moveTo('[data-demo="resetLink"]', 500);
      await clickFx();
      resetState();
      await wait(550);
      setCursor((c) => ({ ...c, visible: false }));
      await wait(350);

      clearInterval(progressInterval);
    }

    (async () => {
      while (!cancelled) { await runOnce(); }
    })();

    return () => {
      cancelled = true;
      if (progressInterval) clearInterval(progressInterval);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tick]);

  return (
    <section className="it-section" id="board">
      <SectionHeader
        kicker="The job board"
        title={<>The Top 100, refreshed <em>every Tuesday.</em></>}
        sub="An AI scraper crawls Canadian career sites, agency boards, and newsrooms every week. Our editors rank what's left. Here's a 16-second tour."
      />

      <ChartMeta meta={meta} accent={accent} />

      <div className="it-chart it-demo" ref={containerRef}>
        <div className="it-demo-progress" aria-hidden="true">
          <div className="it-demo-progress-bar" style={{ width: `${progress * 100}%`, background: accent }} />
        </div>

        <div className="it-demo-chrome">
          <div className="it-demo-badge">
            <span className="it-demo-badge-dot" style={{ background: accent }} />
            Demo · loops every 10s
          </div>
          <div className="it-demo-controls">
            <button
              className="it-demo-ctrl"
              onClick={() => setPaused((p) => !p)}
              aria-label={paused ? "Play" : "Pause"}
            >
              {paused ? "▶" : "❚❚"}
            </button>
            <button
              className="it-demo-ctrl"
              onClick={() => { setProgress(0); setTick((t) => t + 1); setPaused(false); }}
              aria-label="Replay"
            >
              ↻
            </button>
          </div>
        </div>

        <div className="it-demo-stage">
          <DemoCursor x={cursor.x} y={cursor.y} visible={cursor.visible} clicking={cursor.clicking} accent={accent} />

          <div className="it-chart-toolbar">
            <div className="it-search">
              <span className="it-search-icon" aria-hidden="true">⌕</span>
              <input value={q} readOnly placeholder="Search role, company, or tag…" tabIndex="-1" />
            </div>
            <DemoDropdown label="City" value={city} options={cities} open={openDD === "city"} accent={accent} demoTag="city" />
            <DemoDropdown label="Track" value={track} options={tracks} open={openDD === "track"} accent={accent} demoTag="track" />
            <DemoDropdown label="Mode" value={mode} options={modes} open={openDD === "mode"} accent={accent} demoTag="mode" />
            <div className="it-chart-spacer" />
            <div className="it-dd it-dd-sort">
              <div className="it-dd-btn" role="button">
                <span className="it-dd-label">Sort</span>
                <span className="it-dd-value">Rank</span>
                <span className="it-dd-caret" aria-hidden="true">▾</span>
              </div>
            </div>
            <div className={classNames("it-saved-btn", saved.size > 0 && "is-on")}>
              <span
                className="it-saved-icon"
                key={saveFlash}
                data-on={saved.size > 0 ? "true" : "false"}
              >
                <svg viewBox="0 0 12 16" width="11" height="13" aria-hidden="true">
                  <path
                    d="M2 1 H10 V15 L6 12 L2 15 Z"
                    fill={saved.size > 0 ? accent : "none"}
                    stroke="currentColor"
                    strokeWidth="1.4"
                    strokeLinejoin="round"
                  />
                </svg>
                <span className="it-saved-burst" style={{ borderColor: accent }} />
              </span>
              <span className="it-saved-btn-label">Saved</span>
              <span
                className={classNames("it-saved-count", saved.size > 0 && "is-on")}
                style={saved.size > 0 ? { background: accent, color: "#fff" } : undefined}
                key={`c-${saveFlash}`}
              >
                {saved.size}
              </span>
            </div>
          </div>

          <div className="it-chart-status">
            <span>
              Showing <strong>{filtered.length}</strong> of 100
              {activeFilterCount > 0 && (
                <span className="it-chart-status-filters">
                  {" "}· {activeFilterCount} filter{activeFilterCount === 1 ? "" : "s"} active
                </span>
              )}
            </span>
            {activeFilterCount > 0 && (
              <span className="it-link" data-demo="resetLink">Reset filters</span>
            )}
          </div>

          <ListHeader />

          <ol className={classNames("it-list", density === "compact" && "is-compact")}>
            {filtered.map((l, idx) => (
              <RankRow
                key={l.id}
                listing={l}
                open={openId === l.id}
                saved={saved.has(l.id)}
                onToggle={() => {}}
                onSave={() => {}}
                accent={accent}
                demoTag={idx === 0 ? "firstRow" : null}
                saveTag={openId === l.id ? "saveBtn" : null}
                demoCompact
              />
            ))}
          </ol>
        </div>
      </div>
    </section>
  );
}

function DemoDropdown({ label, value, options, open, accent, demoTag }) {
  const isOn = value !== options[0];
  return (
    <div className="it-dd">
      <div
        className={classNames("it-dd-btn", isOn && "is-on")}
        data-demo={demoTag === "city" ? "cityDD" : (demoTag + "DD")}
        style={isOn ? { borderColor: accent, color: accent } : undefined}
        role="button"
      >
        <span className="it-dd-label">{label}</span>
        <span className="it-dd-value">{value}</span>
        <span className="it-dd-caret" aria-hidden="true">▾</span>
      </div>
      {open && (
        <div className="it-dd-menu" role="listbox">
          {options.map((o) => (
            <div
              key={o}
              role="option"
              aria-selected={o === value}
              data-demo={`${demoTag}Opt-${o}`}
              className={classNames("it-dd-opt", o === value && "is-on")}
            >
              <span>{o}</span>
              {o === value && <span style={{ color: accent }}>✓</span>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function DemoCursor({ x, y, visible, clicking, accent, children }) {
  return (
    <div
      className={classNames("it-demo-cursor", visible && "is-on", clicking && "is-click")}
      style={{ transform: `translate(${x}px, ${y}px)` }}
      aria-hidden="true"
    >
      <svg viewBox="0 0 24 28" width="24" height="28">
        <path
          d="M3 2 L3 22 L9 17 L13 26 L17 24 L13 15 L21 14 Z"
          fill="#1A1410"
          stroke="#fff"
          strokeWidth="1.4"
          strokeLinejoin="round"
        />
      </svg>
      <span className="it-demo-ripple" style={{ borderColor: accent }} />
      {children}
    </div>
  );
}

// ─── Resume Match demo ────────────────────────────────────────────────────
const MATCH_LISTINGS = [
  { id: "M1", company: "Aritzia",     role: "Editorial Content Intern",    city: "Vancouver, BC", chartRank: 4,  finalScore: 96, matched: ["Editorial", "Brand voice", "Long-form"] },
  { id: "M2", company: "Hootsuite",   role: "Social Media Intern",         city: "Vancouver, BC", chartRank: 7,  finalScore: 91, matched: ["Social", "Content", "Community"] },
  { id: "M3", company: "Roots",       role: "Social & Content Intern",     city: "Toronto, ON",   chartRank: 11, finalScore: 84, matched: ["Social", "Toronto"] },
  { id: "M4", company: "Shopify",     role: "Corporate Communications",    city: "Toronto, ON",   chartRank: 1,  finalScore: 78, matched: ["Corporate", "Toronto"] },
  { id: "M5", company: "MLSE",        role: "Public Relations Intern",     city: "Toronto, ON",   chartRank: 5,  finalScore: 62, matched: ["PR"] },
  { id: "M6", company: "Air Canada",  role: "Corporate Affairs Intern",    city: "Montréal, QC",  chartRank: 2,  finalScore: 48, matched: [] },
];

function scoreColor(score, accent) {
  if (score >= 80) return "#2E7B45"; // green
  if (score >= 60) return "#C8923B"; // amber
  return "#B53A1B";                   // red
}

function MatchDemo({ accent }) {
  const containerRef = useRef(null);
  const [phase, setPhase] = useState("idle"); // idle | dropping | analyzing | scored
  const [scores, setScores] = useState([0, 0, 0, 0, 0, 0]);
  const [analyzingProgress, setAnalyzingProgress] = useState(0);
  const [cursor, setCursor] = useState({ x: 80, y: 80, visible: false, clicking: false, fileAttached: false });
  const [loopProgress, setLoopProgress] = useState(0);
  const [tick, setTick] = useState(0);
  const [paused, setPaused] = useState(false);

  useEffect(() => {
    let cancelled = false;
    let progressInterval = null;
    let scoreRaf = null;
    let analyzingRaf = null;
    const LOOP_MS = 14000;

    function wait(ms) {
      return new Promise((resolve) => {
        const start = performance.now();
        function tickWait() {
          if (cancelled) return resolve();
          if (paused) { requestAnimationFrame(tickWait); return; }
          if (performance.now() - start >= ms) return resolve();
          requestAnimationFrame(tickWait);
        }
        tickWait();
      });
    }

    function getCenter(selector) {
      const root = containerRef.current;
      if (!root) return null;
      const el = root.querySelector(selector);
      if (!el) return null;
      const c = root.getBoundingClientRect();
      const r = el.getBoundingClientRect();
      return { x: r.left - c.left + r.width / 2, y: r.top - c.top + r.height / 2 };
    }

    async function moveTo(selector, dwellMs = 800) {
      let pt = null;
      for (let i = 0; i < 8 && !cancelled; i++) {
        pt = getCenter(selector);
        if (pt) break;
        await wait(40);
      }
      if (cancelled || !pt) return;
      setCursor((c) => ({ ...c, x: pt.x, y: pt.y, visible: true, clicking: false }));
      await wait(dwellMs);
    }

    function animateAnalyzing(durMs) {
      const start = performance.now();
      function step(now) {
        if (cancelled) return;
        const t = Math.min(1, (now - start) / durMs);
        setAnalyzingProgress(t);
        if (t < 1) analyzingRaf = requestAnimationFrame(step);
      }
      analyzingRaf = requestAnimationFrame(step);
    }

    function animateScores(targets, durMs) {
      const start = performance.now();
      function step(now) {
        if (cancelled) return;
        const t = Math.min(1, (now - start) / durMs);
        const next = targets.map((s, i) => {
          const stagger = i * 0.10;
          const localT = Math.max(0, Math.min(1, (t - stagger) / (1 - stagger)));
          const eased = 1 - Math.pow(1 - localT, 3);
          return Math.round(s * eased);
        });
        setScores(next);
        if (t < 1) scoreRaf = requestAnimationFrame(step);
      }
      scoreRaf = requestAnimationFrame(step);
    }

    async function runOnce() {
      const start = performance.now();
      progressInterval = setInterval(() => {
        if (cancelled) { clearInterval(progressInterval); return; }
        if (paused) return;
        setLoopProgress(Math.min(1, (performance.now() - start) / LOOP_MS));
      }, 60);

      // ── 0.0s reset
      setPhase("idle");
      setScores([0,0,0,0,0,0]);
      setAnalyzingProgress(0);
      setCursor({ x: 80, y: 60, visible: false, clicking: false, fileAttached: false });
      await wait(1000);
      if (cancelled) return;

      // ── 1.0s cursor appears holding a resume PDF
      setCursor({ x: 60, y: 220, visible: true, clicking: false, fileAttached: true });
      await wait(450);

      // ── 1.45s cursor drags resume to drop zone
      await moveTo('[data-md="dropzone"]', 800);

      // ── 2.25s drop
      setCursor((c) => ({ ...c, clicking: true }));
      setPhase("dropping");
      await wait(280);
      setCursor((c) => ({ ...c, clicking: false, fileAttached: false }));
      await wait(420);

      // ── ~3s analyzing
      setPhase("analyzing");
      setCursor((c) => ({ ...c, visible: false }));
      animateAnalyzing(2400);
      await wait(2500);

      // ── ~5.5s scores reveal
      setPhase("scored");
      const targets = MATCH_LISTINGS.map((l) => l.finalScore);
      animateScores(targets, 2000);
      await wait(2100);

      // ── ~7.6s pause to read
      await wait(4500);
      if (cancelled) return;

      // ── ~12.1s fade out, loop
      setCursor((c) => ({ ...c, visible: false }));
      await wait(700);

      clearInterval(progressInterval);
    }

    (async () => {
      while (!cancelled) { await runOnce(); }
    })();

    return () => {
      cancelled = true;
      if (progressInterval) clearInterval(progressInterval);
      if (scoreRaf) cancelAnimationFrame(scoreRaf);
      if (analyzingRaf) cancelAnimationFrame(analyzingRaf);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tick]);

  return (
    <section className="it-section it-section-match" id="match">
      <SectionHeader
        kicker="Resume match"
        title={<>Drop your resume. <em>We do the math.</em></>}
        sub="Every internship gets a 0–100 fit score against your skills, courses, and past experience. Best matches float to the top of your personal chart."
      />

      <div className="it-chart it-demo it-match" ref={containerRef}>
        <div className="it-demo-progress" aria-hidden="true">
          <div className="it-demo-progress-bar" style={{ width: `${loopProgress * 100}%`, background: accent }} />
        </div>

        <div className="it-demo-chrome">
          <div className="it-demo-badge">
            <span className="it-demo-badge-dot" style={{ background: accent }} />
            Demo · loops every 14s
          </div>
          <div className="it-demo-controls">
            <button className="it-demo-ctrl" onClick={() => setPaused((p) => !p)} aria-label={paused ? "Play" : "Pause"}>
              {paused ? "▶" : "❚❚"}
            </button>
            <button className="it-demo-ctrl" onClick={() => { setLoopProgress(0); setTick((t) => t + 1); setPaused(false); }} aria-label="Replay">↻</button>
          </div>
        </div>

        <div className="it-demo-stage">
          <DemoCursor x={cursor.x} y={cursor.y} visible={cursor.visible} clicking={cursor.clicking} accent={accent}>
            {cursor.fileAttached && (
              <div className="it-md-cursor-file">
                <svg viewBox="0 0 16 20" width="14" height="18" aria-hidden="true">
                  <rect x="1" y="1" width="14" height="18" rx="2" fill="#fff" stroke="#1A1410" strokeWidth="1" />
                  <path d="M4 7 H12 M4 10 H12 M4 13 H9" stroke="#D85F2D" strokeWidth="1" strokeLinecap="round" />
                </svg>
                <span>resume.pdf</span>
              </div>
            )}
          </DemoCursor>

          <div
            className={classNames("it-md-dropzone", phase !== "idle" && "is-filled", phase === "dropping" && "is-dropping")}
            data-md="dropzone"
          >
            {phase === "idle" ? (
              <div className="it-md-dropzone-empty">
                <div className="it-md-dropzone-icon" style={{ borderColor: accent, color: accent }}>↑</div>
                <div className="it-md-dropzone-t">Drop your resume</div>
                <div className="it-md-dropzone-d">PDF or DOCX · or paste a LinkedIn URL</div>
              </div>
            ) : (
              <div className="it-md-file">
                <div className="it-md-file-icon" aria-hidden="true">
                  <svg viewBox="0 0 32 40" width="28" height="36">
                    <rect x="2" y="2" width="28" height="36" rx="3" fill="#fff" stroke="#1A1410" strokeWidth="1.4" />
                    <path d="M6 12H24M6 18H24M6 24H18M6 30H22" stroke={accent} strokeWidth="1.5" strokeLinecap="round" />
                  </svg>
                </div>
                <div className="it-md-file-meta">
                  <div className="it-md-file-name">Resume_Tolentino.pdf</div>
                  <div className="it-md-file-sub">247 KB · MacEwan · 2 years experience</div>
                </div>
                <div className="it-md-file-status">
                  {phase === "analyzing" && (
                    <>
                      <div className="it-md-analyzing-t">Analyzing your fit…</div>
                      <div className="it-md-prog">
                        <div className="it-md-prog-bar" style={{ width: `${analyzingProgress * 100}%`, background: accent }} />
                      </div>
                    </>
                  )}
                  {phase === "scored" && (
                    <div className="it-md-scored-badge" style={{ color: accent, borderColor: accent }}>
                      ✓ Matched against {MATCH_LISTINGS.length} roles
                    </div>
                  )}
                  {phase === "dropping" && (
                    <div className="it-md-analyzing-t" style={{ color: "var(--ink-3)" }}>Got it…</div>
                  )}
                </div>
              </div>
            )}
          </div>

          <div className="it-md-list-slot">
            {phase === "scored" && (
              <div className="it-md-list">
                {MATCH_LISTINGS.map((l, i) => (
                  <MatchRow
                    key={l.id}
                    listing={l}
                    score={scores[i]}
                    phase={phase}
                    accent={accent}
                    isTop={i === 0 && phase === "scored"}
                    index={i}
                  />
                ))}
              </div>
            )}
          </div>
        </div>
      </div>
    </section>
  );
}

function MatchRow({ listing, score, phase, accent, isTop, index = 0 }) {
  const showScore = phase === "analyzing" || phase === "scored";
  const color = scoreColor(score, accent);
  return (
    <div
      className={classNames("it-md-row", isTop && "is-top", phase === "scored" && "is-scored")}
      style={{ animationDelay: `${index * 90}ms` }}
    >
      <div className="it-md-score-cell">
        <div className="it-md-score-n" style={{ color: showScore && score > 0 ? color : "var(--ink-3)" }}>
          {showScore && score > 0 ? score : "—"}
        </div>
        <div className="it-md-score-bar">
          <div
            className="it-md-score-fill"
            style={{ width: `${showScore ? score : 0}%`, background: color }}
          />
        </div>
      </div>
      <div className="it-md-info">
        <div className="it-md-line1">
          <span className="it-md-co">{listing.company}</span>
          <span className="it-md-role">{listing.role}</span>
          {isTop && <span className="it-md-top-badge" style={{ background: accent }}>★ Top match</span>}
        </div>
        <div className="it-md-line2">
          <span className="it-md-city">{listing.city}</span>
          <span className="it-md-sep">·</span>
          <span className="it-md-chartrank">chart #{listing.chartRank}</span>
          {phase === "scored" && listing.matched.length > 0 && (
            <>
              <span className="it-md-sep">·</span>
              <span className="it-md-matched-k">matched on</span>
              {listing.matched.map((m) => (
                <span key={m} className="it-md-matched-tag" style={{ color: accent, borderColor: accent }}>
                  {m}
                </span>
              ))}
            </>
          )}
        </div>
      </div>
    </div>
  );
}


// ─── Features ───────────────────────────────────────────────────────────────
function Features({ accent }) {
  const items = [
    {
      n: "01",
      t: "Built for Canadian students.",
      d: "Filter by city, by co-op term, by track. We index roles from coast to coast — Halifax to Victoria — not just the Toronto-Vancouver axis.",
    },
    {
      n: "02",
      t: "One brief, one inbox.",
      d: "A weekly Tuesday brief with the top 10 new postings, deadlines that week, and a short editorial on one company we love.",
    },
    {
      n: "03",
      t: "Application-ready.",
      d: "Every posting links straight to the company application. No middlemen, no resume databases, no scraped contact emails.",
    },
  ];
  return (
    <section className="it-section" id="features">
      <SectionHeader
        kicker="Why In Turn"
        title={<>The job board <em>your</em> career deserves.</>}
        sub="A quiet platform doing one thing very well: connecting Canadian comms students to real, paid internships."
      />
      <div className="it-features">
        {items.map((it) => (
          <article className="it-feature" key={it.n}>
            <div className="it-feature-n" style={{ color: accent }}>{it.n}</div>
            <h3 className="it-feature-t">{it.t}</h3>
            <p className="it-feature-d">{it.d}</p>
          </article>
        ))}
      </div>
    </section>
  );
}

// ─── Team ───────────────────────────────────────────────────────────────────
function Team({ accent }) {
  return (
    <section className="it-section it-section-team" id="team">
      <SectionHeader
        kicker="Behind the chart"
        title={<>Built by someone who&rsquo;s been the <em>intern.</em></>}
      />
      <div className="it-team-single">
        <div className="it-team-photo-lg" style={{backgroundImage: "none", padding: 0, overflow: "hidden"}}>
          <img src="headshot.jpg.png" alt="Gabriel Jeremy Tolentino" style={{width: "100%", height: "100%", objectFit: "cover", display: "block", borderRadius: "14px"}} />
        </div>
        <div className="it-team-story">
          <div className="it-team-meta">
            <div className="it-team-name">Gabriel Jeremy Tolentino</div>
            <div className="it-team-role">
              Communications student · MacEwan University
            </div>
          </div>
          <div className="it-team-prose">
            <p>
              I started In Turn after spending months refreshing five different
              job boards every morning &mdash; and applying to roles that turned
              out to be unpaid, six months stale, or had nothing to do with
              communications.
            </p>
            <p>
              There&rsquo;s no good single place for Canadian comms students. <em>I&rsquo;m
              making one.</em>
            </p>
            <p>
              The first version is a weekly ranked chart of the top 100
              internships in the country &mdash; scraped, reviewed, and ranked.
              The next adds a resume match so you don&rsquo;t have to guess which
              roles are actually a fit.
            </p>
            <p>If that sounds useful, get on the waitlist.</p>
          </div>
          <div className="it-team-links">
            {SOCIAL.linkedin
              ? <a href={SOCIAL.linkedin} className="it-team-link" target="_blank" rel="noopener noreferrer">LinkedIn ↗</a>
              : null}
            <a href={`mailto:${CONTACT_EMAIL}`} className="it-team-link">{CONTACT_EMAIL}</a>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─── Brand showcase ────────────────────────────────────────────────────────
function BrandShowcase_REMOVED() { return null; }
const BRAND_NOTES = {};
function Swatch_REMOVED() { return null; }

// ─── Waitlist ───────────────────────────────────────────────────────────────
function Waitlist({ accent, onJoin }) {
  return (
    <section className="it-section it-section-waitlist" id="waitlist">
      <div className="it-waitlist">
        <div className="it-waitlist-copy">
          <div className="it-kicker">The waitlist</div>
          <h2 className="it-waitlist-h">
            Be on the board <em>first.</em>
          </h2>
          <p className="it-waitlist-sub">
            We launch in {LAUNCH.window} with the first 100 internships. Students on
            the waitlist get the board 48 hours early, plus a weekly Tuesday
            brief.
          </p>
          <ul className="it-waitlist-perks">
            <li><span style={{ color: accent }}>+</span> 48-hour early access</li>
            <li><span style={{ color: accent }}>+</span> The Tuesday brief, weekly</li>
            <li><span style={{ color: accent }}>+</span> Resume match when it launches</li>
            <li><span style={{ color: accent }}>+</span> No spam. Unsubscribe in one click.</li>
          </ul>
        </div>

        <div className="it-waitlist-cta">
          <button
            className="it-btn it-btn-primary it-btn-xl"
            onClick={onJoin}
            style={{ background: accent }}
          >
            Join the waitlist
            <span className="it-btn-arrow" aria-hidden="true">→</span>
          </button>
          <div className="it-waitlist-small">
            Takes 30 seconds. Three quick questions.
          </div>
          <div className="it-waitlist-privacy">
            We&rsquo;ll only email you the brief. No spam, no sharing your data &mdash; unsubscribe in one click.
          </div>
        </div>
      </div>
    </section>
  );
}

// ─── Waitlist Modal ─ multi-step, animated, ticket-stub success ──────────────────
const WAITLIST_QUESTIONS = [
  {
    key: "name",
    label: "Your name",
    q: "Who are we adding to the list?",
    sub: "First, then last. We'll use this on your Tuesday brief.",
    placeholder: "Jane Communications",
    type: "text",
  },
  {
    key: "email",
    label: "Email",
    q: "Where should we send the brief?",
    sub: "School email works great. We won't share it. Promise.",
    placeholder: "you@school.ca",
    type: "email",
  },
  {
    key: "school",
    label: "School",
    q: "Where are you studying?",
    sub: "So we can flag co-op terms and city-specific openings.",
    placeholder: "MacEwan, McGill, UBC…",
    type: "text",
    suggestions: ["MacEwan University", "McGill", "UBC", "University of Toronto", "Carleton", "Ryerson / TMU", "Concordia", "Dalhousie"],
  },
];

function WaitlistModal({ open, onClose, accent, Logo }) {
  const [stage, setStage] = useState(0);    // 0–2 form, 3 success
  const [form, setForm] = useState({ name: "", email: "", school: "" });
  const [touched, setTouched] = useState(false);
  const [position, setPosition] = useState(null);
  const [animPos, setAnimPos] = useState(0);  // count-up animation for the ticket number
  const inputRef = useRef(null);

  // Reset on open
  useEffect(() => {
    if (open) {
      setStage(0);
      setForm({ name: "", email: "", school: "" });
      setTouched(false);
      setPosition(null);
      setAnimPos(0);
    }
  }, [open]);

  // ESC + body scroll lock
  useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    function onKey(e) { if (e.key === "Escape") onClose(); }
    document.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = prev;
      document.removeEventListener("keydown", onKey);
    };
  }, [open, onClose]);

  // Auto-focus current input
  useEffect(() => {
    if (open && stage < 3 && inputRef.current) {
      const t = setTimeout(() => inputRef.current.focus(), 280);
      return () => clearTimeout(t);
    }
  }, [open, stage]);

  // Animate ticket number on success
  useEffect(() => {
    if (stage !== 3 || position == null) return;
    let raf;
    const start = performance.now();
    const dur = 1100;
    function step(now) {
      const t = Math.min(1, (now - start) / dur);
      const eased = 1 - Math.pow(1 - t, 3);
      setAnimPos(Math.floor(eased * position));
      if (t < 1) raf = requestAnimationFrame(step);
    }
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [stage, position]);

  if (!open) return null;

  const q = WAITLIST_QUESTIONS[stage] || WAITLIST_QUESTIONS[0];
  const currentValue = form[q.key] || "";
  const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email);
  const stageValid =
    (stage === 0 && currentValue.trim().length >= 2) ||
    (stage === 1 && emailValid) ||
    (stage === 2 && currentValue.trim().length >= 2);

  function next() {
    setTouched(true);
    if (!stageValid) return;
    setTouched(false);
    if (stage < 2) setStage(stage + 1);
    else submit();
  }
  function back() {
    setTouched(false);
    if (stage > 0) setStage(stage - 1);
  }
  function submit() {
    // Deterministic position so reload shows the same number
    const seed = (form.email + form.name).split("").reduce((a, c) => (a * 31 + c.charCodeAt(0)) | 0, 7);
    const pos = 247 + Math.abs(seed % 853);
    // Persist the signup to Supabase (fire-and-forget; never blocks the UI).
    saveWaitlistSignup(form, pos);
    setPosition(pos);
    setStage(3);
  }

  function onKeyDown(e) {
    if (e.key === "Enter") { e.preventDefault(); next(); }
  }

  return (
    <div className="it-modal-backdrop" onClick={onClose}>
      <div
        className="it-modal"
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-label="Join the waitlist"
      >
        <button className="it-modal-close" onClick={onClose} aria-label="Close">
          <svg viewBox="0 0 14 14" width="12" height="12" aria-hidden="true">
            <path d="M1 1 L13 13 M13 1 L1 13" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
          </svg>
        </button>

        {stage < 3 ? (
          <div className="it-modal-form">
            <div className="it-modal-hd">
              <Logo size={22} accent={accent} />
              <div className="it-modal-step-ind">
                <span style={{ color: accent }}>{String(stage + 1).padStart(2, "0")}</span>
                <span className="it-modal-step-sep">/</span>
                <span>03</span>
              </div>
            </div>

            <div className="it-modal-progress">
              {[0, 1, 2].map((i) => (
                <span
                  key={i}
                  className={classNames("it-modal-progress-pip", i <= stage && "is-on")}
                  style={i <= stage ? { background: accent } : undefined}
                />
              ))}
            </div>

            <div className="it-modal-body" key={stage}>
              <h3 className="it-modal-q">{q.q}</h3>
              <p className="it-modal-sub">{q.sub}</p>
              <label className="it-modal-input">
                <span>{q.label}</span>
                <input
                  ref={inputRef}
                  type={q.type}
                  value={currentValue}
                  onChange={(e) => setForm({ ...form, [q.key]: e.target.value })}
                  onKeyDown={onKeyDown}
                  placeholder={q.placeholder}
                  aria-invalid={touched && !stageValid}
                  autoComplete="off"
                />
              </label>
              {q.suggestions && (
                <div className="it-modal-suggest">
                  {q.suggestions.map((s) => (
                    <button
                      key={s}
                      type="button"
                      className="it-modal-suggest-chip"
                      onClick={() => setForm({ ...form, school: s })}
                    >
                      {s}
                    </button>
                  ))}
                </div>
              )}
              {touched && !stageValid && (
                <div className="it-modal-err">
                  {stage === 1 ? "That email doesn't look right." : "A few characters, at least."}
                </div>
              )}
            </div>

            <div className="it-modal-actions">
              <button
                className="it-modal-back"
                onClick={back}
                disabled={stage === 0}
              >
                ← Back
              </button>
              <button
                className="it-btn it-btn-primary it-btn-lg"
                onClick={next}
                style={{ background: accent }}
              >
                {stage === 2 ? "Send it" : "Next"}
                <span className="it-btn-arrow" aria-hidden="true">→</span>
              </button>
            </div>
          </div>
        ) : (
          <ModalSuccess form={form} position={position} animPos={animPos} accent={accent} Logo={Logo} />
        )}
      </div>
    </div>
  );
}

function ModalSuccess({ form, position, animPos, accent, Logo }) {
  // Brand-fit confetti: small accent dots radiating from center.
  const dots = Array.from({ length: 18 });
  return (
    <div className="it-modal-success">
      <div className="it-modal-fx" aria-hidden="true">
        {dots.map((_, i) => (
          <span
            key={i}
            className="it-modal-fx-dot"
            style={{
              background: i % 3 === 0 ? accent : (i % 3 === 1 ? "#C8923B" : "#1A1410"),
              "--a": `${(360 / dots.length) * i}deg`,
              "--d": `${(i * 23) % 220}ms`,
            }}
          />
        ))}
      </div>

      <div className="it-modal-check" style={{ background: accent }}>
        <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
          <path d="M5 12 L10 17 L19 8" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </div>

      <h3 className="it-modal-success-h">
        You&rsquo;re in <em>line.</em>
      </h3>

      <div className="it-ticket">
        <div className="it-ticket-row">
          <span className="it-ticket-k">In Turn</span>
          <span className="it-ticket-v">Waitlist</span>
        </div>
        <div className="it-ticket-divider" aria-hidden="true" />
        <div className="it-ticket-name">{form.name}</div>
        <div className="it-ticket-school">{form.school}</div>
        <div className="it-ticket-divider" aria-hidden="true" />
        <div className="it-ticket-num-row">
          <span className="it-ticket-num-k">No.</span>
          <span className="it-ticket-num" style={{ color: accent }}>
            {String(animPos).padStart(4, "0")}
          </span>
        </div>
        <div className="it-ticket-divider" aria-hidden="true" />
        <div className="it-ticket-row">
          <span className="it-ticket-k">First brief</span>
          <span className="it-ticket-v">{LAUNCH.firstBrief}</span>
        </div>
      </div>

      <p className="it-modal-success-d">
        Confirmation sent to <strong>{form.email}</strong>.
        <br />
        Your Tuesday brief lands next week.
      </p>
    </div>
  );
}

// ─── Generic section header ────────────────────────────────────────────────
function SectionHeader({ kicker, title, sub }) {
  return (
    <div className="it-sh">
      <div className="it-kicker">{kicker}</div>
      <h2 className="it-sh-title">{title}</h2>
      {sub && <p className="it-sh-sub">{sub}</p>}
    </div>
  );
}

// ─── Footer ────────────────────────────────────────────────────────────────
function Footer({ Logo, accent }) {
  return (
    <footer className="it-foot">
      <div className="it-foot-top">
        <Logo size={32} accent={accent} />
        <div className="it-foot-tag">Canada&rsquo;s communications internship board.</div>
      </div>
      <div className="it-foot-cols">
        <div>
          <div className="it-foot-h">Board</div>
          <a href="#board">Browse</a>
          <a href={`mailto:${CONTACT_EMAIL}?subject=Submit%20a%20listing`}>Submit a listing</a>
          <a href={`mailto:${CONTACT_EMAIL}?subject=Employer%20partnership`}>For employers</a>
        </div>
        <div>
          <div className="it-foot-h">Inside</div>
          <a href="#features">How it works</a>
          <a href="#team">Team</a>
          <a href="#waitlist">Waitlist</a>
        </div>
        <div>
          <div className="it-foot-h">Stay close</div>
          <a href="#waitlist">Waitlist</a>
          <a href="#waitlist">Tuesday brief</a>
          {SOCIAL.instagram
            ? <a href={SOCIAL.instagram} target="_blank" rel="noopener noreferrer">Instagram</a>
            : <a href={`mailto:${CONTACT_EMAIL}`}>Contact</a>}
        </div>
      </div>
      <div className="it-foot-bot">
        <span>© 2026 In Turn. Made in Edmonton, Canada.</span>
        <span>v 0.4 · pre-launch</span>
      </div>
    </footer>
  );
}

// ─── App ───────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const logoEntry = window.IN_TURN_LOGOS[t.activeLogo] || window.IN_TURN_LOGOS.editorial;
  const Logo = logoEntry.Component;
  const accent = t.accent;
  const [modalOpen, setModalOpen] = useState(false);
  const openModal = () => setModalOpen(true);
  const closeModal = () => setModalOpen(false);

  // Reflect accent into a CSS var for non-React consumers (selection, hover).
  useEffect(() => {
    document.documentElement.style.setProperty("--it-accent", accent);
  }, [accent]);

  return (
    <div className="it-root">
      <Nav Logo={Logo} accent={accent} onJoin={openModal} />
      <Hero accent={accent} variant={t.heroVariant} onJoin={openModal} />
      <ChartDemo accent={accent} density={t.chartDensity} />
      <MatchDemo accent={accent} />
      <Features accent={accent} />
      <Team accent={accent} />
      <Waitlist accent={accent} onJoin={openModal} />
      <Footer Logo={Logo} accent={accent} />

      <WaitlistModal open={modalOpen} onClose={closeModal} accent={accent} Logo={Logo} />

      <TweaksPanel>
        <TweakSection label="Theme" />
        <TweakColor
          label="Accent"
          value={t.accent}
          options={ACCENT_OPTIONS}
          onChange={(v) => setTweak("accent", v)}
        />
        <TweakSection label="Hero" />
        <TweakRadio
          label="Style"
          value={t.heroVariant}
          options={["marquee", "grid"]}
          onChange={(v) => setTweak("heroVariant", v)}
        />
        <TweakSection label="The Chart" />
        <TweakRadio
          label="Density"
          value={t.chartDensity}
          options={["cozy", "compact"]}
          onChange={(v) => setTweak("chartDensity", v)}
        />
      </TweaksPanel>
    </div>
  );
}

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