// QuizPlayer — restored single-card layout matching the original interface.
// One question per screen, instant feedback on click (no submit step),
// question grid sidebar, top toolbar with progress bar and live score badge.

const { useState: useQPState, useMemo: useQPMemo, useEffect: useQPEffect, useRef: useQPRef } = React;

function QuizPlayer({ chapter, quiz, onExit, onUpdateQuiz, onRenameQuiz }) {
  const T = window.LexTokens;
  const total = quiz.questions.length;
  const [idx, setIdx]           = useQPState(0);
  // Hydrate answers from persisted quiz state so Resume picks up where you left off.
  const [answers, setAnswers]   = useQPState(() => ({ ...(quiz.answers || {}) }));
  const [openSource, setOpenSource] = useQPState(null);
  const [done, setDone]         = useQPState(false);
  const [editingName, setEditingName] = useQPState(false);
  const [draftName, setDraftName]     = useQPState(quiz.name);

  const q          = quiz.questions[idx];
  const userPick   = q ? answers[q.id] : null;
  const revealed   = !!userPick;

  const answered  = Object.keys(answers).length;
  const correct   = useQPMemo(() =>
    quiz.questions.filter(qq => answers[qq.id] === qq.correctChoiceId).length,
    [answers, quiz.questions]
  );

  function persistAnswers(nextAnswers) {
    const correctNow = quiz.questions.filter(qq => nextAnswers[qq.id] === qq.correctChoiceId).length;
    onUpdateQuiz({
      answers: nextAnswers,
      progress: { answered: Object.keys(nextAnswers).length, correct: correctNow },
      lastTaken: Date.now(),
    });
  }

  function pick(choiceId) {
    const current = answers[q.id];
    let next;
    if (current === choiceId) {
      // Click the same selected choice → deselect (clear the answer for this question)
      next = { ...answers };
      delete next[q.id];
    } else {
      // Switch selection (works whether or not a choice was already selected)
      next = { ...answers, [q.id]: choiceId };
    }
    setAnswers(next);
    persistAnswers(next);
  }

  function commitRename() {
    const v = (draftName || '').trim();
    if (v && v !== quiz.name) onRenameQuiz(v);
    else setDraftName(quiz.name);
    setEditingName(false);
  }
  function cancelRename() {
    setDraftName(quiz.name);
    setEditingName(false);
  }
  function gotoIdx(i) {
    if (i >= 0 && i < total) { setIdx(i); setDone(false); }
  }
  function next() {
    if (idx < total - 1) setIdx(idx + 1);
    else if (Object.keys(answers).length === total) setDone(true);
  }
  function prev() { if (idx > 0) setIdx(idx - 1); }

  // Empty quiz (no generated questions yet)
  if (total === 0) {
    return (
      <div style={qpS.shell}>
        <Toolbar quiz={quiz} chapter={chapter} idx={0} total={0} answered={0} correct={0} onExit={onExit} />
        <div style={qpS.empty}>
          <div style={qpS.emptyIcon}>○</div>
          <div style={qpS.emptyTitle}>This quiz has no questions yet</div>
          <div style={qpS.emptyDesc}>The quiz generation algorithm hasn’t produced questions for this configuration. (Algorithm work is deferred.)</div>
          <button style={qpS.btnPrimary} onClick={onExit}>← Back to library</button>
        </div>
      </div>
    );
  }

  // Done summary
  if (done) {
    const pct = Math.round(correct / total * 100);
    return (
      <div style={qpS.shell}>
        <Toolbar quiz={quiz} chapter={chapter} idx={total} total={total} answered={answered} correct={correct} onExit={onExit} />
        <div style={qpS.summary}>
          <div style={qpS.summarySeal}>§</div>
          <div style={qpS.summaryEyebrow}>Quiz complete</div>
          <div style={qpS.summaryBig}>{correct}<span style={qpS.summaryOf}>/{total}</span></div>
          <div style={{...qpS.summaryGrade, color: pct >= 75 ? T.ok : pct >= 60 ? T.gold : T.crimson }}>
            {pct >= 90 ? 'Excellent' : pct >= 75 ? 'Strong showing' : pct >= 60 ? 'Passing' : 'Worth a re-read'} — {pct}%
          </div>
          <div style={qpS.summaryActions}>
            <button style={qpS.btnGhost} onClick={() => { setDone(false); setIdx(0); }}>Review answers</button>
            <button style={qpS.btnPrimary} onClick={() => { setDone(false); setIdx(0); setAnswers({}); persistAnswers({}); }}>Retake quiz</button>
            <button style={qpS.btnGhost} onClick={onExit}>← Done</button>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div style={qpS.shell}>
      <Toolbar
        quiz={quiz} chapter={chapter}
        idx={idx} total={total}
        answered={answered} correct={correct}
        onExit={onExit}
        editingName={editingName} draftName={draftName}
        onStartEdit={() => setEditingName(true)}
        onChangeDraft={setDraftName}
        onCommitEdit={commitRename}
        onCancelEdit={cancelRename}
      />

      <div style={qpS.layout}>
        {/* Main question card */}
        <article style={qpS.card}>
          <header style={qpS.cardHead}>
            <button style={{ ...qpS.navInline, opacity: idx === 0 ? .35 : 1 }} onClick={prev} disabled={idx === 0}>
              ← Prev
            </button>
            <div style={qpS.cardHeadMid}>
              <span style={qpS.qnLabel}>Q{idx + 1}</span>
              <span style={qpS.qnOf}>of {total}</span>
              {(() => {
                const meta = qtypeMeta(q.qtype);
                return <Pill kind={meta.kind}>{meta.label}</Pill>;
              })()}
            </div>
            <button style={qpS.navInline} onClick={next}>
              {idx < total - 1 ? 'Next →' : (answered === total ? 'Finish →' : 'Next →')}
            </button>
          </header>

          <h2 style={qpS.stem}>{q.stem}</h2>

          <div style={qpS.choices}>
            {q.choices.map((c, i) => {
              const letter   = String.fromCharCode(65 + i);
              const selected = userPick === c.id;
              const isAnswer = c.id === q.correctChoiceId;

              let stateStyle = qpS.choice;
              let badgeBg = T.ink;
              if (revealed) {
                if (isAnswer)        { stateStyle = { ...qpS.choice, ...qpS.choiceCorrect }; badgeBg = T.ok; }
                else if (selected)   { stateStyle = { ...qpS.choice, ...qpS.choiceWrong   }; badgeBg = T.err; }
                else                 { stateStyle = { ...qpS.choice, opacity: .5 };          badgeBg = T.muted; }
              }
              return (
                <button
                  key={c.id}
                  className={`lex-choice${revealed ? ' lex-choice--locked' : ''}`}
                  style={stateStyle}
                  onClick={() => pick(c.id)}
                >
                  <span style={{ ...qpS.choiceBadge, background: badgeBg }}>{letter}</span>
                  <span style={qpS.choiceText}>{c.text}</span>
                </button>
              );
            })}
          </div>

          {revealed && (
            <FeedbackInline
              question={q}
              chapter={chapter}
              wasCorrect={userPick === q.correctChoiceId}
              onOpenSource={(cit, quoteTexts) => setOpenSource({ citation: cit, quotes: quoteTexts })}
            />
          )}
        </article>

        {/* Question grid sidebar */}
        <aside style={qpS.sidebar}>
          <div style={qpS.sidebarLabel}>Questions</div>
          <div style={qpS.grid}>
            {quiz.questions.map((qq, i) => {
              const a       = answers[qq.id];
              const status  = !a ? 'open' : a === qq.correctChoiceId ? 'right' : 'wrong';
              const isHere  = i === idx;
              return (
                <button
                  key={qq.id}
                  style={{
                    ...qpS.gridCell,
                    ...(status === 'right' ? qpS.gridCellRight : {}),
                    ...(status === 'wrong' ? qpS.gridCellWrong : {}),
                    ...(isHere ? qpS.gridCellHere : {}),
                  }}
                  onClick={() => gotoIdx(i)}
                  title={`Q${i + 1}`}
                >{i + 1}</button>
              );
            })}
          </div>
          {answered === total && (
            <button style={qpS.finishBtn} onClick={() => setDone(true)}>
              Finish quiz →
            </button>
          )}
        </aside>
      </div>

      {openSource && (
        <SourceDrawer
          chapter={chapter}
          citation={openSource.citation}
          quotes={openSource.quotes}
          onClose={() => setOpenSource(null)}
        />
      )}
    </div>
  );
}

// ── Top toolbar ─────────────────────────────────────────────────────────────
function Toolbar({ quiz, chapter, idx, total, answered, correct, onExit, editingName, draftName, onStartEdit, onChangeDraft, onCommitEdit, onCancelEdit }) {
  const T = window.LexTokens;
  const pct = total > 0 ? Math.min(100, (answered / total) * 100) : 0;
  return (
    <header style={tbS.bar}>
      <button style={tbS.back} onClick={onExit}>← Back</button>

      <div style={tbS.titleBlock}>
        <div style={tbS.crumb}>{chapter.displayName}</div>
        {editingName ? (
          <input
            autoFocus
            value={draftName}
            onChange={e => onChangeDraft(e.target.value)}
            onBlur={onCommitEdit}
            onKeyDown={e => {
              if (e.key === 'Enter')  { e.preventDefault(); onCommitEdit(); }
              if (e.key === 'Escape') { e.preventDefault(); onCancelEdit(); }
            }}
            style={tbS.titleInput}
          />
        ) : (
          <div style={tbS.titleRow}>
            <button
              style={tbS.titleBtn}
              onClick={onStartEdit && (() => onStartEdit())}
              title={onStartEdit ? 'Click to rename quiz' : ''}
            >{quiz.name}</button>
            {onStartEdit && (
              <button style={tbS.titlePencil} onClick={onStartEdit} title="Rename quiz">
                <PencilIcon14 />
              </button>
            )}
          </div>
        )}
      </div>

      <div style={tbS.right}>
        <div style={tbS.progressWrap}>
          <div style={tbS.progressTrack}>
            <div style={{ ...tbS.progressFill, width: `${pct}%` }} />
          </div>
          <div style={tbS.progressLabel}>
            {answered}/{total} answered
          </div>
        </div>
        <span style={tbS.scoreBadge}>{correct}/{total}</span>
      </div>
    </header>
  );
}

function PencilIcon14() {
  return (
    <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
      <path d="M11.5 2.5 L13.5 4.5 L5 13 L2.5 13.5 L3 11 L11.5 2.5 Z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
    </svg>
  );
}

// Per-question pill metadata. `synthesis` and `compare-contrast` both signal
// cross-passage questions but read clearly enough on their own — keep them
// distinct in the label, share the green color so the pattern is recognizable.
function qtypeMeta(qtype) {
  switch (qtype) {
    case 'application':      return { label: 'Application', kind: 'blue' };
    case 'compare-contrast': return { label: 'Compare',     kind: 'green' };
    case 'synthesis':        return { label: 'Synthesis',   kind: 'green' };
    case 'fact':
    default:                 return { label: 'Fact',        kind: 'gold' };
  }
}

// ── Inline feedback (under choices, like the original) ──────────────────────
function FeedbackInline({ question, chapter, wasCorrect, onOpenSource }) {
  const T = window.LexTokens;
  const fb = question.feedback;
  const correctChoice = question.choices.find(c => c.id === question.correctChoiceId);
  const correctLetter = String.fromCharCode(65 + question.choices.indexOf(correctChoice));

  // Back-compat: pre-multi-citation quizzes have feedback.quote (string) and
  // feedback.citation (object). Newer quizzes have quotes[] and citations[].
  // Normalize both shapes here so the renderer only deals with the new shape.
  const quotes = normalizeQuotes(fb);
  const citations = normalizeCitations(fb);
  const primary = citations[0];
  const secondary = citations.slice(1);

  function openCitation(cit) {
    const quoteTextsForPage = quotes
      .filter(q => q.page == null || q.page === cit.page)
      .map(q => q.text);
    onOpenSource(cit, quoteTextsForPage);
  }
  // A citation has a "jump anchor" (a highlighted quote on its page) iff at
  // least one quote either matches its page or is page-less (back-compat).
  // When false, clicking the citation opens the full page text with no
  // highlight to scroll to — render it as a chip instead of a jump-link so
  // the user knows there's no specific target before clicking.
  const hasAnchor = (cit) => quotes.some(q => q.page == null || q.page === cit.page);

  return (
    <div style={{
      ...fbS.box,
      borderColor: wasCorrect ? T.ok : T.err,
      background: wasCorrect ? T.okSoft : T.errSoft,
    }}>
      <div style={{ ...fbS.verdict, color: wasCorrect ? T.ok : T.err }}>
        {wasCorrect ? '✓ Correct' : `✗ Incorrect — Answer: ${correctLetter}`}
      </div>
      <div style={fbS.summary}>{fb.summary}</div>
      <p style={fbS.exp}>{fb.explanation}</p>

      {quotes.map((q, i) => (
        <blockquote key={i} style={fbS.quote}>
          <span style={fbS.quoteMark}>“</span>
          <span style={fbS.quoteText}>{q.text}</span>
          <span style={fbS.quoteMark}>”</span>
          {q.page != null && <span style={fbS.quoteCite}> — p. {q.page}</span>}
        </blockquote>
      ))}

      {primary && (
        <div style={fbS.citRow}>
          <span style={fbS.citLabel}>Source:</span>
          <span style={fbS.citText}>{primary.label}</span>
          <span style={fbS.citDot}>·</span>
          <CitationButton citation={primary} hasAnchor={hasAnchor(primary)} onOpen={openCitation} />
          {secondary.length > 0 && (
            <>
              <span style={fbS.citDot}>·</span>
              <span style={fbS.citAlsoLabel}>also</span>
              {secondary.map((c, i) => (
                <CitationButton key={i} citation={c} hasAnchor={hasAnchor(c)} onOpen={openCitation} />
              ))}
            </>
          )}
        </div>
      )}
    </div>
  );
}

// Two visual variants. Both open the source drawer; the difference is what
// the drawer can show.
//   • hasAnchor = true  → underlined link with ↗ (jumps to a highlighted quote).
//   • hasAnchor = false → outlined chip "p. N" (opens the full page; no
//                         highlight to scroll to). The chip's border replaces
//                         the underline+arrow so it's recognizable as a
//                         different affordance, not just dimmed link text.
function CitationButton({ citation, hasAnchor, onOpen }) {
  const titleText = hasAnchor
    ? citation.label
    : `${citation.label} — opens page ${citation.page} (no pull-quote on this page)`;
  return (
    <button
      style={hasAnchor ? fbS.citBtn : fbS.citChip}
      title={titleText}
      onClick={() => onOpen(citation)}
    >
      {hasAnchor ? `p. ${citation.page} ↗` : `p. ${citation.page}`}
    </button>
  );
}

function normalizeQuotes(fb) {
  if (Array.isArray(fb?.quotes) && fb.quotes.length) {
    return fb.quotes
      .filter(q => q && q.text)
      .map(q => ({ page: typeof q.page === 'number' ? q.page : null, text: q.text }));
  }
  if (typeof fb?.quote === 'string' && fb.quote.trim()) {
    return [{ page: typeof fb?.citation?.page === 'number' ? fb.citation.page : null, text: fb.quote }];
  }
  return [];
}

function normalizeCitations(fb) {
  if (Array.isArray(fb?.citations) && fb.citations.length) {
    return fb.citations.filter(c => c);
  }
  if (fb?.citation) return [fb.citation];
  return [];
}

// ── Source drawer ───────────────────────────────────────────────────────────
// `quotes` is an array of plain quote strings to highlight on this page.
// Each is matched independently; overlapping matches are merged so adjacent
// highlights render as a single <mark>.
function SourceDrawer({ chapter, citation, quotes, onClose }) {
  const T = window.LexTokens;
  const page = chapter.pages.find(p => p.n === citation.page);
  const firstMarkRef = useQPRef(null);

  // Auto-scroll to the first highlighted quote when the drawer opens or the
  // citation/quotes change (user can click "also p. X" and we re-anchor).
  useQPEffect(() => {
    if (firstMarkRef.current) {
      firstMarkRef.current.scrollIntoView({ block: 'center', behavior: 'smooth' });
    }
  }, [citation.page, (quotes || []).join('|')]);

  const segments = page ? splitForHighlight(page.text || '', quotes || []) : null;
  const firstHiIdx = segments ? segments.findIndex(s => s.hi) : -1;

  return (
    <div style={drwS.overlay} onClick={onClose}>
      <aside style={drwS.drawer} onClick={e => e.stopPropagation()}>
        <header style={drwS.head}>
          <div>
            <div style={drwS.crumb}>{chapter.displayName} — page {citation.page}</div>
            <div style={drwS.title}>{citation.label}</div>
          </div>
          <button style={drwS.close} onClick={onClose}>✕</button>
        </header>
        <div style={drwS.body}>
          {page ? (
            <p style={drwS.pageText}>
              {segments.map((seg, i) =>
                seg.hi ? (
                  <mark
                    key={i}
                    ref={i === firstHiIdx ? firstMarkRef : null}
                    style={drwS.mark}
                  >{seg.text}</mark>
                ) : (
                  <React.Fragment key={i}>{seg.text}</React.Fragment>
                )
              )}
            </p>
          ) : (
            <div style={drwS.missing}>Page {citation.page} text not available in this preview.</div>
          )}
        </div>
        <footer style={drwS.foot}>
          <span style={drwS.footHint}>Page text is the indexed extraction stored when this chapter was uploaded.</span>
          <button style={drwS.btn} onClick={onClose}>Close</button>
        </footer>
      </aside>
    </div>
  );
}

// Locate one or more quote strings inside the page text and split into
// highlight segments. For each quote: tries an exact substring match first,
// then a whitespace-flexible regex match — PDF extraction frequently inserts
// stray spaces that break exact comparison. Overlapping matches are merged.
function splitForHighlight(pageText, quoteList) {
  if (!pageText) return [{ text: '', hi: false }];
  const list = (quoteList || []).map(q => (q || '').trim()).filter(Boolean);
  if (list.length === 0) return [{ text: pageText, hi: false }];

  const matches = [];
  for (const q of list) {
    const m = findQuoteMatch(pageText, q);
    if (m) matches.push(m);
  }
  if (matches.length === 0) return [{ text: pageText, hi: false }];

  // Sort by start, then merge overlapping/adjacent ranges.
  matches.sort((a, b) => a.start - b.start);
  const merged = [matches[0]];
  for (let i = 1; i < matches.length; i++) {
    const last = merged[merged.length - 1];
    if (matches[i].start <= last.end) {
      last.end = Math.max(last.end, matches[i].end);
    } else {
      merged.push({ ...matches[i] });
    }
  }

  const segments = [];
  let cursor = 0;
  for (const m of merged) {
    if (m.start > cursor) segments.push({ text: pageText.slice(cursor, m.start), hi: false });
    segments.push({ text: pageText.slice(m.start, m.end), hi: true });
    cursor = m.end;
  }
  if (cursor < pageText.length) segments.push({ text: pageText.slice(cursor), hi: false });
  return segments;
}

// findQuoteMatch — three matching tiers, each more forgiving than the last.
// PDFs are messy, so we don't get to assume the model's quote will appear
// verbatim in the extracted page text:
//   1. Exact substring match. Cheapest and ~always wins on clean PDFs.
//   2. Whitespace-flexible regex. Handles line breaks and run-of-the-mill
//      double-spaces, but breaks down when stray whitespace sneaks AROUND
//      punctuation — e.g. extraction produces "power , which" while the
//      model's quote has "power, which".
//   3. Canonicalized match. Strip ALL whitespace (and unify Unicode dash /
//      quote variants) on both sides; if the canonical quote is found in the
//      canonical page text, map the canonical indices back to the original
//      via a side-table. This handles "T h e", spaces-around-punctuation,
//      mid-quote line breaks, and mismatched dash glyphs in one pass.
function findQuoteMatch(pageText, quote) {
  // Tier 1: exact substring.
  const exact = pageText.indexOf(quote);
  if (exact !== -1) return { start: exact, end: exact + quote.length };

  // Tier 2: whitespace-flexible regex.
  const escaped = quote
    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    .replace(/\s+/g, '\\s+');
  try {
    const re = new RegExp(escaped, 'i');
    const m = pageText.match(re);
    if (m && m.index != null) return { start: m.index, end: m.index + m[0].length };
  } catch (e) { /* invalid regex — fall through to tier 3 */ }

  // Tier 3: whitespace-stripped canonical match.
  const pageCanon = canonicalizeWithMap(pageText);
  const quoteCanon = canonicalizeWithMap(quote);
  if (!quoteCanon.canon) return null;
  const cIdx = pageCanon.canon.indexOf(quoteCanon.canon);
  if (cIdx === -1) return null;
  // Map back to original-string indices. canon[cIdx] came from pageText
  // position pageCanon.map[cIdx]; the last canon char of the match came from
  // pageCanon.map[cIdx + quoteLen - 1], and we extend by 1 to make `end`
  // exclusive (matching the slice convention).
  const lastCanonIdx = cIdx + quoteCanon.canon.length - 1;
  const start = pageCanon.map[cIdx];
  const end = pageCanon.map[lastCanonIdx] + 1;
  return { start, end };
}

// canonicalizeWithMap — produces a whitespace-free, dash/quote-normalized,
// lowercase version of the input plus a parallel index map so callers can
// translate canonical positions back to original-string positions.
function canonicalizeWithMap(s) {
  const chars = [];
  const map = [];
  for (let i = 0; i < s.length; i++) {
    const orig = s[i];
    if (/\s/.test(orig)) continue;        // drop all whitespace
    chars.push(unifyChar(orig).toLowerCase());
    map.push(i);
  }
  return { canon: chars.join(''), map };
}

// Collapse Unicode dash / quote variants to their ASCII counterparts so the
// model's quote and the PDF extraction agree on punctuation glyphs.
function unifyChar(ch) {
  const code = ch.charCodeAt(0);
  // Various Unicode hyphens, en/em dashes, minus sign → ASCII '-'
  if ((code >= 0x2010 && code <= 0x2015) || code === 0x2212) return '-';
  // Curly single quotes / prime → "'"
  if (code === 0x2018 || code === 0x2019 || code === 0x201A || code === 0x201B || code === 0x2032) return "'";
  // Curly double quotes / prime → '"'
  if (code === 0x201C || code === 0x201D || code === 0x201E || code === 0x201F || code === 0x2033) return '"';
  // Non-breaking space already filtered by /\s/, but if present treat as space (won't reach here).
  // Ellipsis → three periods would change length; leave as-is to keep map 1:1.
  return ch;
}

// ── Styles ──────────────────────────────────────────────────────────────────
const T = window.LexTokens;

const qpS = {
  shell:  { maxWidth: 1180, margin: '0 auto', width: '100%', display: 'flex', flexDirection: 'column', gap: 18 },
  layout: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 220px', gap: 22, alignItems: 'flex-start' },

  card:    { background: T.paper, border: `1px solid ${T.rule}`, borderRadius: 14, padding: '22px 28px', boxShadow: T.shadow },
  cardHead:{ display:'flex', alignItems:'center', justifyContent:'space-between', gap: 14, paddingBottom: 16, borderBottom: `1px solid ${T.ruleSoft}`, marginBottom: 18 },
  cardHeadMid:{ display:'flex', alignItems:'center', gap: 10 },
  navInline:{ background:'none', border:`1px solid ${T.rule}`, borderRadius:7, padding:'7px 14px', fontSize:13, color: T.ink, fontWeight:500, fontFamily:'inherit', cursor:'pointer' },

  qnLabel: { fontFamily: T.serifDisp, fontSize: 14, fontWeight: 700, color: T.ink, letterSpacing:'.2px' },
  qnOf:    { fontSize: 12, color: T.muted, fontFamily: T.serifBody, marginRight: 4 },

  stem:    { fontFamily: T.serifDisp, fontSize: 19, lineHeight: 1.55, color: T.ink, fontWeight: 600, letterSpacing: '-0.1px', margin: '0 0 18px' },

  choices: { display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 },
  choice:  { display: 'flex', alignItems: 'flex-start', gap: 12, padding: '11px 14px', border: `1px solid ${T.rule}`, borderRadius: 9, background: T.paper, textAlign: 'left', cursor: 'pointer', transition: 'border-color .12s, background .12s, opacity .12s', fontFamily: 'inherit', width: '100%', outline: 'none', boxShadow: 'none', WebkitTapHighlightColor: 'transparent' },
  choiceCorrect:{ borderColor: T.ok, background: T.okSoft },
  choiceWrong:  { borderColor: T.err, background: T.errSoft },
  choiceBadge:  { width: 22, height: 22, borderRadius: '50%', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 700, fontFamily: T.serifDisp, flexShrink: 0, marginTop: 1 },
  choiceText:   { fontFamily: T.serifBody, fontSize: 14, color: T.ink, lineHeight: 1.55 },

  // Sidebar
  sidebar: { background: T.paper, border: `1px solid ${T.rule}`, borderRadius: 14, padding: '16px 16px', boxShadow: T.shadow, position: 'sticky', top: 18 },
  sidebarLabel:{ fontSize: 10.5, letterSpacing: '1.8px', textTransform: 'uppercase', color: T.forest, fontWeight: 700, marginBottom: 12 },
  grid:    { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6 },
  gridCell:{ width:'100%', aspectRatio:'1 / 1', border:`1.5px solid ${T.rule}`, background: T.paper, borderRadius: 6, fontFamily: T.serifDisp, fontSize: 13, fontWeight: 700, color: T.ink, cursor: 'pointer', transition: 'all .12s' },
  gridCellRight:{ background: T.ok, color: '#fff', borderColor: T.ok },
  gridCellWrong:{ background: T.err, color: '#fff', borderColor: T.err },
  gridCellHere: { boxShadow: `0 0 0 2px ${T.ink}, inset 0 0 0 1px #fff` },
  finishBtn:{ marginTop:14, width:'100%', padding:'10px 14px', background: T.forest, color:'#fff', border:'none', borderRadius:8, fontSize:13, fontWeight:600, cursor:'pointer', fontFamily:'inherit' },

  empty:  { background: T.paper, border:`1px solid ${T.rule}`, borderRadius:14, padding:80, textAlign:'center', fontFamily: T.serifBody, boxShadow: T.shadow },
  emptyIcon: { fontSize: 42, color: T.muted, marginBottom: 8 },
  emptyTitle:{ fontFamily: T.serifDisp, fontSize: 22, color: T.ink, fontWeight: 700, marginBottom: 8 },
  emptyDesc: { color: T.muted, fontSize: 14, lineHeight: 1.6, marginBottom: 20, maxWidth: 480, marginLeft: 'auto', marginRight: 'auto' },

  summary:{ background: T.paper, border:`1px solid ${T.rule}`, borderRadius:14, padding:'48px 36px', boxShadow: T.shadow, textAlign:'center' },
  summarySeal:{ fontSize: 28, color: T.gold, marginBottom: 6, fontFamily: T.serifDisp },
  summaryEyebrow:{ fontSize: 11.5, letterSpacing: '2.5px', textTransform: 'uppercase', color: T.forest, fontWeight: 700, marginBottom: 14 },
  summaryBig:{ fontFamily: T.serifDisp, fontSize: 72, fontWeight: 700, color: T.ink, lineHeight: 1, marginBottom: 6 },
  summaryOf:{ fontSize: 32, color: T.muted, fontWeight: 500 },
  summaryGrade:{ fontFamily: T.serifBody, fontSize: 16, marginTop: 8, marginBottom: 30 },
  summaryActions:{ display:'flex', justifyContent:'center', gap: 10, flexWrap:'wrap' },

  btnPrimary:{ padding:'10px 18px', background: T.forest, color: 'white', border: 'none', borderRadius: 8, fontSize: 13.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' },
  btnGhost:  { padding:'10px 18px', background: 'none', color: T.ink, border: `1px solid ${T.rule}`, borderRadius: 8, fontSize: 13.5, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' },
};

const tbS = {
  bar:  { display:'grid', gridTemplateColumns:'auto 1fr auto', alignItems:'center', gap: 16, padding: '12px 18px', background: T.paper, border: `1px solid ${T.rule}`, borderRadius: 12, boxShadow: T.shadow },
  back: { background:'none', border:`1px solid ${T.rule}`, borderRadius: 7, padding: '7px 12px', fontSize: 12.5, color: T.ink, fontFamily: 'inherit', cursor: 'pointer', fontWeight: 500 },
  titleBlock:{ minWidth: 0, paddingLeft: 6 },
  crumb:{ fontSize: 10.5, letterSpacing: '1.6px', textTransform: 'uppercase', color: T.forest, fontWeight: 700 },
  title:{ fontFamily: T.serifDisp, fontSize: 15, color: T.ink, fontWeight: 700, marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
  titleRow:{ display:'flex', alignItems:'center', gap: 6, marginTop: 1 },
  titleBtn:{ background:'none', border:'none', padding:0, fontFamily: T.serifDisp, fontSize: 15, color: T.ink, fontWeight: 700, cursor:'text', textAlign:'left' },
  titlePencil:{ width: 22, height: 22, padding: 0, background:'none', border:'none', color: T.muted, cursor:'pointer', display:'inline-flex', alignItems:'center', justifyContent:'center' },
  titleInput:{ fontFamily: T.serifDisp, fontSize: 15, color: T.ink, fontWeight: 700, background:'transparent', border:'none', borderBottom:`2px solid ${T.forest}`, padding:'1px 4px', margin:'-1px 0', outline:'none', minWidth: 220, fontStyle:'italic' },
  right:{ display:'flex', alignItems:'center', gap: 14 },
  progressWrap: { minWidth: 200, textAlign: 'right' },
  progressTrack:{ height: 5, background: T.ruleSoft, borderRadius: 99, overflow: 'hidden' },
  progressFill: { height: '100%', background: T.forest, transition: 'width .25s ease' },
  progressLabel:{ marginTop: 4, fontSize: 11.5, color: T.muted, fontFamily: T.serifBody },
  scoreBadge:   { padding:'7px 14px', background: T.ink, color:'#fff', borderRadius:8, fontFamily: T.serifDisp, fontSize: 13, fontWeight: 700 },
};

const fbS = {
  box:    { marginTop: 4, padding: '14px 18px', borderRadius: 10, border: '1.5px solid' },
  verdict:{ fontFamily: T.serifDisp, fontSize: 14, fontWeight: 700, marginBottom: 8, letterSpacing: '.2px' },
  summary:{ fontFamily: T.serifDisp, fontSize: 14.5, lineHeight: 1.5, color: T.ink, fontWeight: 600, marginBottom: 8 },
  exp:    { fontFamily: T.serifBody, fontSize: 13.5, lineHeight: 1.65, color: T.ink2, margin: 0 },
  quote:  { margin: '12px 0 0', padding: '10px 14px', borderLeft: `3px solid ${T.gold}`, background: T.paper, borderRadius: '0 6px 6px 0', fontFamily: T.serifBody, fontStyle: 'italic', fontSize: 13, lineHeight: 1.55, color: T.ink2 },
  quoteMark:{ fontFamily: T.serifDisp, color: T.gold, fontSize: 16, fontWeight: 700, fontStyle: 'normal' },
  quoteText:{ margin: '0 4px' },
  quoteCite:{ fontFamily: T.serifBody, fontStyle: 'normal', fontSize: 11.5, color: T.muted, fontWeight: 600, marginLeft: 4 },
  citRow: { marginTop: 12, paddingTop: 10, borderTop: `1px solid rgba(0,0,0,.07)`, display:'flex', alignItems:'center', flexWrap:'wrap', gap:6, fontSize: 12.5, fontFamily: T.serifBody },
  citLabel:{ fontSize: 10.5, letterSpacing: '1.4px', textTransform: 'uppercase', color: T.muted, fontWeight: 700 },
  citText: { color: T.ink, fontWeight: 600 },
  citDot:  { color: T.muted },
  citBtn:  { background:'none', border:'none', color: T.forest, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5, padding: 0, textDecoration: 'underline', textUnderlineOffset: 3 },
  // Pill/chip treatment for citations with no pull-quote on that page —
  // visually distinct from the underlined "jump" link so the user knows
  // there's no anchor to scroll to before they click.
  citChip: { background:'transparent', border:`1px solid ${T.forest}`, color: T.forest, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', fontSize: 11.5, padding: '1px 8px', borderRadius: 999, lineHeight: 1.5, letterSpacing: '.2px' },
  citAlsoLabel:{ fontSize: 10.5, letterSpacing: '1.4px', textTransform: 'uppercase', color: T.muted, fontWeight: 700 },
};

const drwS = {
  overlay:{ position: 'fixed', inset: 0, background: 'rgba(26,23,20,.45)', display: 'flex', justifyContent: 'flex-end', zIndex: 999 },
  drawer: { background: T.paper, width: 'min(520px, 92vw)', height: '100%', display: 'flex', flexDirection: 'column', borderLeft: `1px solid ${T.rule}`, boxShadow: T.shadowLg, animation: 'slideIn .2s ease-out' },
  head:   { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 14, padding: '22px 28px', borderBottom: `1px solid ${T.ruleSoft}`, background: T.cream },
  crumb:  { fontSize: 11.5, letterSpacing: '1.6px', textTransform: 'uppercase', color: T.forest, fontWeight: 700, marginBottom: 4 },
  title:  { fontFamily: T.serifDisp, fontSize: 17, color: T.ink, fontWeight: 700 },
  close:  { background: 'none', border: `1px solid ${T.rule}`, color: T.ink, width: 32, height: 32, borderRadius: 8, cursor: 'pointer', fontSize: 14 },
  body:   { flex: 1, overflowY: 'auto', padding: '26px 28px' },
  pageText:{ fontFamily: T.serifBody, fontSize: 14.5, lineHeight: 1.75, color: T.ink2 },
  mark:    { background: '#FBF3DC', color: T.ink, padding: '1px 2px', borderRadius: 2, boxShadow: '0 0 0 1px #F0E2B6' },
  missing:{ fontStyle: 'italic', color: T.muted, fontSize: 14 },
  foot:   { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, padding: '14px 24px', borderTop: `1px solid ${T.ruleSoft}`, background: T.cream },
  footHint:{ fontSize: 11.5, color: T.muted, fontFamily: T.serifBody, fontStyle: 'italic' },
  btn:    { padding: '8px 14px', background: T.forest, color: 'white', border: 'none', borderRadius: 6, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' },
};

Object.assign(window, { QuizPlayer });
