/* ═══════════════════════════════════════════════════════════════════
   QExtract App — Premium Dark Theme (Full Tab)
   ═══════════════════════════════════════════════════════════════════ */

/* ── Reset & Variables ─────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg:       #0c0c12;
  --surface:  #14141f;
  --surface2: #1c1c2e;
  --surface3: #252540;
  --border:   #2a2a45;
  --text:     #f8f8ff;
  --text2:    #b0b0d0;
  --text3:    #9090b0;
  --accent:   #7c5cfc;
  --accent-readable: #8b75ff;
  /* v2.3.2 AX2 (1.4.3): small accent-coloured TEXT (the active tab, the sample links, the
     badges) was hard-coded to the dark theme's tints and fell to 2.1-2.4:1 on light surfaces.
     Two tokens, one per use, so the dark theme keeps its exact colours and the light theme
     gets readable ones. Dark: #a58fff on #e3e6ef... n/a; on the dark surfaces 7.6:1 and
     #b59fff 8.9:1. Light (below): #5235d8 on #e3e6ef 6.5:1, on #eef0f5 6.9:1, on #f5f6f9 7.3:1. */
  --accent-text: #a58fff;
  --badge-text:  #b59fff;
  --accent2:  #6040e0;
  --green:    #34d399;
  --green-dark: #059669;
  --yellow:   #fbbf24;
  --red:      #f87171;
  --red-dark: #dc2626;
  --radius:   12px;
  --font:     'Segoe UI', system-ui, -apple-system, sans-serif;
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  overflow-y: scroll;
}

.app {
  max-width: 900px;
  margin: 0 auto;
  padding: 40px 20px;
  display: flex;
  flex-direction: column;
  gap: 30px;
  min-height: 100vh;
}

.version { 
  font-size: 0.7rem; 
  font-weight: 600; 
  background: rgba(124, 92, 252, 0.1); 
  color: #7c5cfc; 
  padding: 2px 6px; 
  border-radius: 4px; 
  vertical-align: middle; 
  margin-left: 8px; 
}

/* ── Header ────────────────────────────────────────────────────── */
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* v2.3.2 CMDPOS1: space-between alone left 1 px between the brand, the tools and the tabs
     whenever the row was full (every desktop width: the column caps at 860 px). */
  column-gap: 16px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border);
  user-select: none;
  -webkit-user-select: none;
}

.brand { display: flex; align-items: center; gap: 14px; }
.logo svg { width: 44px; height: 44px; }
.brand-name { font-size: 1.6rem; font-weight: 700; background: linear-gradient(135deg, var(--accent), var(--green)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -0.5px; }
.brand-tag { font-size: 0.9rem; color: var(--text2); letter-spacing: 0.3px; margin-top: -2px; }

.tabs { display: flex; background: var(--surface2); padding: 4px; border-radius: 8px; border: 1px solid var(--border); }
.tab { background: transparent; border: none; color: var(--text2); padding: 8px 16px; border-radius: 6px; font-size: 0.9rem; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.tab:hover { color: var(--text); }
.tab.active { background: var(--surface3); color: var(--accent-text); box-shadow: 0 2px 8px rgba(0,0,0,0.2); }

.version { font-size: 0.8rem; color: var(--text3); background: var(--surface2); padding: 4px 12px; border-radius: 20px; border: 1px solid var(--border); font-weight: 600; }

/* ── Main Layout ───────────────────────────────────────────────── */
.main-content { flex: 1; display: flex; flex-direction: column; }
.view-section { animation: fadeIn 0.4s ease; display: flex; flex-direction: column; gap: 24px; }
@keyframes fadeIn { from { opacity: 1; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/* ── Setup View (Upload Zone) ──────────────────────────────────── */
.upload-container { max-width: 600px; margin: 40px auto 0; width: 100%; display: flex; flex-direction: column; gap: 16px; }

.upload-zone {
  border: 2px dashed var(--border); border-radius: var(--radius); padding: 48px 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; background: var(--surface); position: relative; overflow: hidden;
  user-select: none; -webkit-user-select: none;
}
.upload-zone::before { content: ''; position: absolute; inset: 0; background: radial-gradient(circle at 50% 120%, rgba(124,92,252,0.06), transparent 70%); pointer-events: none; }
.upload-zone:hover, .upload-zone.drag-over { border-color: var(--accent); background: var(--surface2); transform: translateY(-2px); box-shadow: 0 8px 32px rgba(124,92,252,0.15); }
.upload-zone:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.upload-icon { width: 64px; height: 64px; margin: 0 auto 16px; color: var(--text3); transition: color 0.3s; }
.upload-zone:hover .upload-icon { color: var(--accent); }
.upload-text { font-size: 1.1rem; color: var(--text2); margin-bottom: 8px; font-weight: 500; }
.upload-link { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.upload-hint { font-size: 0.85rem; color: var(--text3); margin-bottom: 8px; }
.sample-downloads { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 0.8rem; color: var(--text3); margin-top: 4px; }
/* WCAG 2.2 AA (2.5.8 Target Size Minimum): these are standalone controls in a
   row, not links inside a sentence, so the inline exception does not apply.
   At padding 2px they measured 19px tall against the 24px floor. 5px vertical
   padding clears it without changing the type or colour. */
.link-btn { background: none; border: none; color: var(--accent-text); text-decoration: underline; cursor: pointer; padding: 5px 6px; min-height: 24px; font-size: inherit; font-weight: 600; transition: color 0.2s; }
.link-btn:hover { color: var(--green); }

/* File Info */
.file-info { display: flex; align-items: center; gap: 14px; padding: 16px 20px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); animation: slideIn 0.3s ease; }
@keyframes slideIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
.file-icon { font-size: 1.8rem; }
.file-meta { flex: 1; min-width: 0; }
.file-name { font-size: 1rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); }
.file-size { font-size: 0.8rem; color: var(--text3); margin-top: 2px; }
/* v2.3.1 AX1: the batch list inside the card (two or more files; one file shows no list),
   its help line, and the status line under the card. */
.file-info.is-batch { align-items: flex-start; }
.file-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.file-list li { display: flex; align-items: center; gap: 10px; padding: 6px 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; }
.file-list-name { flex: 1; min-width: 0; font-size: 0.9rem; color: var(--text); overflow-wrap: anywhere; }
.file-list-size { font-size: 0.8rem; color: var(--text3); white-space: nowrap; }
.file-list-help { font-size: 0.8rem; color: var(--text3); margin: 8px 0 0; }
.file-list-status { font-size: 0.85rem; color: var(--text2); margin: 0; text-align: center; overflow-wrap: anywhere; }
/* Empty, the status leaves the container's flex flow (the column has a 16px gap) but stays in the
   accessibility tree, so the live region exists before its first announcement. */
.file-list-status:empty { position: absolute; width: 1px; height: 1px; overflow: hidden; margin: 0; }
/* While a parse runs the batch is its live queue: the Remove controls are off (the handlers refuse too). */
.upload-container:has(#btnProcess.processing) .file-list-remove, .upload-container:has(#btnProcess.processing) #btnClear { opacity: 0.5; pointer-events: none; }

/* Buttons */
.btn { padding: 14px 24px; border: none; border-radius: var(--radius); font-family: var(--font); font-size: 1rem; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.25s ease; position: relative; overflow: hidden; }
.btn-primary { background: linear-gradient(135deg, var(--accent), var(--accent2)); color: #fff; box-shadow: 0 4px 16px rgba(124,92,252,0.25); }
.btn-primary:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(124,92,252,0.4); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }
.btn-primary.processing { pointer-events: none; }
.btn-secondary { background: var(--surface2); color: var(--text); border: 1px solid var(--border); }
.btn-secondary:hover { background: var(--surface3); border-color: var(--text3); }
.btn-success { background: linear-gradient(135deg, var(--green), var(--green-dark)); color: #fff; box-shadow: 0 4px 16px rgba(52,211,153,0.25); }
.btn-success:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(52,211,153,0.4); }

.btn-icon { width: 32px; height: 32px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface2); color: var(--text3); cursor: pointer; font-size: 1rem; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.btn-icon:hover { border-color: var(--red); color: var(--red); background: rgba(248,113,113,0.1); }

.btn-spinner { width: 18px; height: 18px; border: 2px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* Stats Bar */
.stats { display: flex; gap: 16px; margin-top: 10px; }
.stat { flex: 1; text-align: center; padding: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.stat-value { display: block; font-size: 2rem; font-weight: 700; background: linear-gradient(135deg, var(--accent), var(--green)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 4px; }
.stat-label { font-size: 0.75rem; color: var(--text3); text-transform: uppercase; letter-spacing: 0.8px; font-weight: 600; }

/* Log Panel */
.log-panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; margin-top: 10px; }
.log-toggle { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px; font-size: 0.9rem; font-weight: 600; color: var(--text2); cursor: pointer; user-select: none; transition: background 0.2s, color 0.2s; }
.log-toggle:hover { color: var(--text); background: var(--surface2); }
.log-count { background: var(--surface3); padding: 2px 10px; border-radius: 12px; font-size: 0.75rem; color: var(--text3); }
.log-container { max-height: 240px; overflow-y: auto; padding: 0 20px 16px; border-top: 1px solid var(--border); }
.log-entry { font-size: 0.85rem; padding: 6px 0; border-bottom: 1px solid rgba(42,42,69,0.5); line-height: 1.5; color: var(--text2); font-family: 'Cascadia Code', 'Consolas', monospace; }
.log-time { color: var(--text3); margin-right: 8px; font-size: 0.75rem; }
.log-info { color: var(--text2); } .log-warn { color: var(--yellow); } .log-error { color: var(--red); } .log-success { color: var(--green); }

/* ── Review View ───────────────────────────────────────────────── */
.review-header { display: flex; justify-content: space-between; align-items: flex-start; padding-bottom: 16px; border-bottom: 1px dashed var(--border); margin-bottom: 24px; gap: 20px; flex-wrap: wrap; }
.review-header h2 { font-size: 1.5rem; font-weight: 600; color: var(--text); margin-bottom: 6px; }
.review-subtitle { font-size: 0.95rem; color: var(--text2); max-width: 500px; line-height: 1.5; }
.review-actions { display: flex; gap: 10px; flex-shrink: 0; align-items: center; flex-wrap: wrap; width: 100%; justify-content: flex-end; }
.review-actions .btn { padding: 0 16px; height: 42px; font-size: 0.9rem; white-space: nowrap; }

/* Review-action grouping: collapse many export formats behind one dropdown */
.export-menu, .more-menu { position: relative; display: inline-block; }
.export-menu > summary, .more-menu > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
}
.export-menu > summary::-webkit-details-marker, .more-menu > summary::-webkit-details-marker { display: none; }
.export-menu[open] > summary, .more-menu[open] > summary { background: var(--surface3); }
.export-menu-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  display: flex; flex-direction: column; gap: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius, 8px); padding: 8px;
  min-width: 230px; max-height: 340px; overflow-y: auto;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
}
.export-menu-panel .btn { width: 100%; justify-content: flex-start; height: 38px; min-height: 38px; flex: 0 0 auto; }
/* Review panels sit on their own row below the action bar */
.report-card-panel, .explain-panel-host { width: 100%; margin-top: 12px; }

/* v2.2 U2 — the rating ask. Sits inline under the primary actions rather than
   as a modal: a dialog that steals focus the moment a download finishes is a
   nag, and this gets exactly one appearance per device. */
.rating-prompt { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; margin-top: 12px; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--green); border-radius: var(--radius); padding: 14px 16px; flex-wrap: wrap; }
/* `display: flex` above BEATS the user-agent's `[hidden]{display:none}`, exactly
   as it did for .parse-failure-report — see the note at the bottom of this file.
   Without this the ask renders on every review screen from the first run, which
   is what the visual-regression gate caught. */
.rating-prompt[hidden] { display: none; }
.rating-prompt-text { margin: 0; color: var(--text2); font-size: 0.9rem; flex: 1 1 260px; }
.rating-prompt-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* The ask is an <a> so it is an ordinary navigation the user starts rather than
   something the extension does — but it is presented as a button, so it should
   not also wear a link underline. */
.rating-prompt-actions .btn { text-decoration: none; }

.parse-diagnostics { background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--yellow); border-radius: var(--radius); padding: 16px; margin-bottom: 20px; }
.diag-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 12px; }
.diag-head h3 { margin: 0 0 4px; color: var(--text); font-size: 1rem; font-weight: 700; }
.diag-head p { margin: 0; color: var(--text2); font-size: 0.85rem; }
.diag-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-bottom: 12px; }
.diag-stat { background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; min-width: 0; }
.diag-stat span { display: block; color: var(--text); font-size: 1.25rem; font-weight: 800; line-height: 1; }
.diag-stat label { display: block; color: var(--text3); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; margin-top: 5px; }
.diag-reasons { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; }
.diag-reason-group { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; color: var(--text2); font-size: 0.82rem; }
.diag-reason-group strong { color: var(--text); margin-right: 2px; }
.diag-reason-group span { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.18); color: var(--text2); border-radius: 999px; padding: 4px 8px; }
.diag-samples { margin-top: 12px; color: var(--text2); font-size: 0.84rem; }
.diag-samples summary { cursor: pointer; color: var(--text); font-weight: 700; }
.diag-samples ul { margin: 10px 0 0; padding-left: 18px; }
.diag-samples li { margin-bottom: 8px; }
.diag-sample-text { display: block; color: var(--text2); line-height: 1.4; }
.diag-sample-reason { display: inline-block; color: var(--red); font-size: 0.78rem; margin-top: 3px; }
/* BF1: the files of a batch that added no questions; a long file name wraps at 320px. */
.diag-skipped { margin: 0 0 16px; padding-left: 18px; color: var(--text2); font-size: 0.85rem; line-height: 1.45; }
.diag-skipped li { margin-bottom: 6px; overflow-wrap: anywhere; }

.web-attribution {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--green);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 20px;
  color: var(--text2);
  font-size: 0.86rem;
}
.web-attribution div {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.web-attribution strong {
  color: var(--text);
  white-space: nowrap;
}
.web-attribution span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.web-attribution a {
  color: #a58fff;
  font-weight: 700;
  text-decoration: none;
  overflow-wrap: anywhere;
}
.web-attribution small {
  color: var(--text3);
  font-size: 0.78rem;
}

.parser-debug-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 20px;
  color: var(--text2);
  font-size: 0.85rem;
}

.parser-debug-panel summary {
  cursor: pointer;
  color: var(--text);
  font-weight: 800;
}

.parser-debug-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin-top: 12px;
}

.parser-debug-grid div {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px;
}

.parser-debug-grid strong,
.parser-debug-grid span {
  display: block;
}

.parser-debug-grid strong {
  color: var(--text3);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  margin-bottom: 4px;
}

.parser-debug-grid span {
  color: var(--text);
  font-weight: 700;
}

.parser-debug-actions {
  margin-top: 12px;
}

.processing-options { display: flex; justify-content: center; margin-bottom: 16px; }

.toggle-option { display: flex; align-items: center; gap: 8px; color: var(--text2); font-size: 0.9rem; cursor: pointer; user-select: none; background: var(--surface2); padding: 0 16px; height: 42px; border-radius: 8px; border: 1px solid var(--border); transition: all 0.2s; }
.toggle-option input { accent-color: var(--accent); transform: scale(1.1); cursor: pointer; }
.toggle-option:hover { color: var(--text); border-color: var(--accent); }

.questions-list { display: flex; flex-direction: column; gap: 24px; }

/* Question Edit Card */
.q-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; position: relative; transition: border-color 0.2s; }
.q-card:focus-within { border-color: var(--accent); box-shadow: 0 4px 20px rgba(124,92,252,0.1); }

.q-card-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px 12px; min-width: 0; margin-bottom: 16px; }
.q-meta-badges { display: flex; gap: 8px; align-items: center; flex: 1 1 420px; min-width: 0; max-width: 100%; flex-wrap: wrap; }
.q-badge { background: var(--surface2); color: var(--badge-text); border: 1px solid var(--border); padding: 4px 12px; border-radius: 16px; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.5px; display: inline-flex; align-items: center; max-width: 100%; min-width: 0; }
.q-badge,
.q-type-badge { overflow-wrap: anywhere; }
.q-type-badge { padding: 3px 10px; border-radius: 4px; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; border: 1px solid transparent; }
/* v2.3.2 AX2 (1.4.3): at 11px the accent itself measured 3.79:1 (dark) on this tint; the text token reads 6:1 or more in both themes. */
.q-type-badge.mcq { background: rgba(124,92,252,0.1); color: var(--accent-text); border-color: rgba(124,92,252,0.2); }
.q-type-badge.tf { background: rgba(52,211,153,0.1); color: var(--green); border-color: rgba(52,211,153,0.2); }
.q-type-badge.blank { background: rgba(251,191,36,0.1); color: var(--yellow); border-color: rgba(251,191,36,0.2); }
.q-type-badge.math { background: #dbeafe; color: #1e40af; border-color: #bfdbfe; }

/* v1.2.0 Deep Parse Badges */
.q-badge.section-badge { background: var(--surface2); color: var(--text2); border: 1px solid var(--border); font-weight: 500; }
.q-badge.marks-badge { background: rgba(52,211,153,0.1); color: var(--green); border: 1px solid rgba(52,211,153,0.2); font-weight: 600; }
.q-badge.topic-badge { background: rgba(124,92,252,0.05); color: var(--accent-text); border: 1px solid rgba(124,92,252,0.2); font-weight: 500; font-size: 0.7rem; text-transform: capitalize; }
.q-badge.warning-badge { background: rgba(248,113,113,0.1); color: var(--red); border: 1px solid rgba(248,113,113,0.3); font-weight: 700; cursor: help; }
.needs-review { border-color: rgba(248,113,113,0.4); border-left: 4px solid var(--red); }
.q-issues-hint { font-size: 0.8rem; color: var(--red); margin-top: -10px; margin-bottom: 12px; font-weight: 500; padding: 6px 12px; background: rgba(248,113,113,0.05); border-radius: 6px; }


.math-tex { font-family: "Cambria Math", "Latin Modern Math", "Times New Roman", serif; font-style: italic; }
.math-tex sub, .math-tex sup { font-style: normal; font-size: 0.8em; }

.q-type-badge.descriptive { background: var(--surface2); color: var(--text3); border-color: var(--border); }
.type-hint { font-size: 0.85rem; color: var(--text3); font-style: italic; margin-top: 10px; }
.rubric-preview { margin-top: 12px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface2); color: var(--text2); font-size: 0.85rem; }
.rubric-preview summary { cursor: pointer; color: var(--text); font-weight: 700; }
.rubric-preview ul { margin: 10px 0 0; padding-left: 20px; }
.rubric-preview li { margin: 4px 0; }
.passage-preview { max-width: 100%; min-width: 0; box-sizing: border-box; overflow-wrap: anywhere; word-break: break-word; white-space: pre-wrap; }
.passage-preview .katex { display: inline-block; max-width: 100%; box-sizing: border-box; overflow-x: auto; overflow-y: hidden; vertical-align: middle; }
.passage-preview .katex-display { max-width: 100%; overflow-x: auto; overflow-y: hidden; }
.passage-preview .katex-display > .katex { display: block; }
.q-delete-btn { background: transparent; color: var(--text3); border: 1px solid transparent; border-radius: 6px; padding: 6px 12px; font-size: 0.85rem; cursor: pointer; transition: all 0.2s; display: flex; gap: 6px; align-items: center; font-weight: 500; flex: 0 0 auto; }
.q-delete-btn:hover { background: rgba(248,113,113,0.1); color: var(--red); border-color: rgba(248,113,113,0.3); }

/* Textareas & Inputs */
.input-field { width: 100%; background: var(--bg); color: var(--text); border: 1px solid var(--surface3); border-radius: 8px; padding: 12px 16px; font-family: var(--font); font-size: 0.95rem; line-height: 1.5; transition: border-color 0.2s; outline: none; }
.input-field:focus { border-color: var(--accent); }
.q-text-input { resize: vertical; min-height: 80px; margin-bottom: 16px; font-weight: 500; }

.opts-container { display: flex; flex-direction: column; gap: 10px; margin-left: 12px; padding-left: 16px; border-left: 2px solid var(--surface2); }
.opt-row { display: flex; align-items: center; gap: 12px; padding: 4px 0; }
.opt-correct-radio { cursor: pointer; transform: scale(1.2); accent-color: var(--green); }
.opt-label { font-weight: 700; color: var(--text3); width: 24px; text-align: right; }
.opt-input { flex: 1; padding: 10px 14px; background: var(--surface2); border: 1px solid var(--surface3); }
.opt-input:focus { background: var(--bg); border-color: var(--accent); }
.opt-delete { background: transparent; color: var(--text3); border: none; cursor: pointer; padding: 8px; border-radius: 6px; transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
.opt-delete:hover { color: var(--red); background: rgba(248,113,113,0.1); }
.opt-add {
  align-self: flex-start; margin-top: 2px;
  background: transparent; color: var(--accent-readable); border: 1px dashed var(--border);
  cursor: pointer; padding: 6px 12px; border-radius: var(--radius, 8px);
  font-size: 0.85rem; font-weight: 600; transition: all 0.15s;
}
.opt-add:hover { background: var(--surface2); border-color: var(--accent); }
.opt-add:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Empty State */
.empty-state { text-align: center; padding: 60px 20px; background: var(--surface); border: 1px dashed var(--border); border-radius: var(--radius); }
.empty-state p { font-size: 1.1rem; color: var(--text2); margin-bottom: 16px; }

/* ── Footer ────────────────────────────────────────────────────── */
.footer { text-align: center; padding-top: 20px; margin-top: auto; border-top: 1px solid var(--border); user-select: none; -webkit-user-select: none; }
.footer p { font-size: 0.8rem; color: var(--text3); display: flex; align-items: center; justify-content: center; gap: 6px; }
.footer p::before { content: '🔒'; font-size: 0.75rem; }

/* Extracted Images */
.extracted-img {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
  border: 1px solid var(--border);
  margin-top: 10px;
  background: #fff; /* White background for diagram readability */
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  display: block;
}

.q-image-preview {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.q-image-attachment {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: start;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface2);
}

.q-image-frame {
  min-width: 0;
}

.q-image-frame .extracted-img,
.q-image-frame .reconstructed-svg-container {
  margin: 0;
}

.q-image-controls {
  display: flex;
  justify-content: flex-start;
}

.q-image-add-btn,
.q-image-pick-btn,
.q-image-remove {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface2);
  color: var(--text2);
  cursor: pointer;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  min-height: 32px;
  padding: 0 12px;
  transition: all 0.2s;
}

.q-image-add-btn:hover,
.q-image-pick-btn:hover {
  border-color: var(--accent);
  color: var(--text);
  background: var(--surface3);
}

.q-image-remove:hover {
  border-color: rgba(248,113,113,0.35);
  color: var(--red);
  background: rgba(248,113,113,0.1);
}

.source-image-picker {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0,0,0,0.72);
}

.source-picker-panel {
  width: min(860px, 100%);
  max-height: 82vh;
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.45);
}

.source-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.source-picker-close {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface2);
  color: var(--text2);
  cursor: pointer;
  min-height: 32px;
  padding: 0 12px;
}

.source-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
}

.source-image-pick {
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface2);
  color: var(--text);
  cursor: pointer;
  padding: 10px;
  text-align: left;
}

.source-image-thumb {
  display: block;
  min-height: 80px;
  max-height: 160px;
  overflow: hidden;
  background: #fff;
  border-radius: 6px;
  margin-bottom: 8px;
}

.source-image-thumb .extracted-img,
.source-image-thumb .reconstructed-svg-container {
  margin: 0;
  max-height: 160px;
  object-fit: contain;
}

.source-image-meta {
  color: var(--text3);
  font-size: 0.78rem;
  font-weight: 700;
}

/* Utilities */
.hidden { display: none !important; }
/* The hidden attribute must win over display rules on classes (.btn is inline-flex, .btn-icon is flex):
   without this a hidden button stays visible, which is how the dead Re-OCR row showed for a year (v2.3.1 UX1). */
[hidden] { display: none !important; }
#btnProcess, #btnEvaluate { width: 100%; }

/* Responsive Layout */
@media (max-width: 850px) {
  .review-header { flex-direction: column; align-items: flex-start; gap: 16px; }
  .review-actions { width: 100%; justify-content: flex-start; }
}

/* v2.3 LIC2: the header on narrow screens. The brand and the display toolbar
   share the first row, the three tabs fill the second, each on one line, so
   that with the trial banner below it the page's own content still starts
   near the top of a phone's screen. CMDPOS1 moved it from 780 to 800 px: from 781 to 800 px
   the single row is full, and gaps between its groups wrapped a tab label (master kept the row
   by letting the gaps fall to 3 px, the touching the user saw). */
@media (max-width: 800px) {
  .header { flex-wrap: wrap; gap: 14px 12px; padding-bottom: 16px; }
  .brand { flex: 1 1 auto; min-width: 0; }
  .a11y-toolbar, .header-tools { flex: 0 0 auto; }
  /* v2.3.2 B15: the Commands button is its icon alone on narrow layouts */
  .command-palette-btn { flex: 0 0 auto; }
  .command-label { display: none; }
  .command-icon { display: inline; }
  .tabs { order: 3; flex: 1 1 100%; }
  /* No nowrap: the labels fit on one line at every width (the shortest
     screens shorten them), and a wider font may wrap one rather than push
     the tabs off the side. */
  .tab { flex: 1 1 0; padding: 10px 6px; text-align: center; }
}
@media (max-width: 600px) {
  .app { padding: 20px 16px; gap: 20px; }
  .logo svg { width: 36px; height: 36px; }
  .brand { gap: 10px; }
  .brand-name { font-size: 1.3rem; }
  .brand-tag { font-size: 0.78rem; }
  .tab { min-height: 44px; }
}
/* The shortest screens: the tabs read Create / Take / Evaluate (their
   accessible names are unchanged), and the key link "Get free key". */
/* v2.3.2 B15: the Commands button's word and key hint need the full header (the column caps at about
   860 px): below about 940 px of window they squeezed the tabs until a label wrapped (LIC2 test at
   820 px), so the button is its icon alone there, as up to 800 px. */
@media (max-width: 940px) {
  .command-label { display: none; }
  .command-icon { display: inline; }
  /* CMDPOS1: between 800 and 940 px the row is nearly full (at 801 px: 245 + 126 + 348 of 746);
     wider gaps squeezed a tab label onto two lines, so here the groups are 8 px apart, as the tools are */
  .header { column-gap: 8px; }
}
@media (max-width: 420px) {
  .tab-word, .trial-word { display: none; }
  /* v2.3.2 B15: no room beside the brand and the display buttons (measured at 320 px: 165 + 96 of
     273); on a phone every action is on the screen as its own button, and Ctrl/Cmd+K still opens
     the palette from a keyboard. CMDPOS1: written with the group's class, so it outranks the
     base rule further down (as a lone class, the later base rule won and the button stayed,
     wrapping to a third header row at 375 px). */
  .header-tools .command-palette-btn { display: none; }
}
@media (max-width: 360px) {
  .brand-tag { display: none; }
}

@media (max-width: 600px) {
  .version { display: none; }
  
  /* v2.3.2 B15.3: two columns, Close | Discard, the answer-key toggle, Export | More,
     Generate. A single column stacked every control on a row of its own (ten rows,
     510 px before the first question). */
  .review-actions { display: grid; grid-template-columns: 1fr 1fr; align-items: stretch; }
  .review-actions .btn, .review-actions .toggle-option { width: 100%; justify-content: center; }
  .review-actions > .toggle-option, .review-actions > #btnGenerate { grid-column: 1 / -1; }
  .review-actions > .export-menu, .review-actions > .more-menu { display: block; }
  .review-actions .export-menu-panel .btn { justify-content: flex-start; }
  /* Export sits in the left column: its panel opens from its left edge, inside the screen */
  .review-actions > .export-menu > .export-menu-panel { left: 0; right: auto; }
  .diag-grid { grid-template-columns: 1fr; }
  
  .q-card-header { flex-direction: column; align-items: flex-start; gap: 12px; }
  .q-meta-badges { flex-wrap: wrap; }
  .q-image-attachment { grid-template-columns: 1fr; }
  .q-image-remove { width: 100%; }
}

/* ── 13.12: Error fingerprint chip in diagnostics ──────────────────────────── */
.diag-fingerprint {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px dashed var(--border);
  border-radius: 8px;
  font-size: 0.85rem;
  color: var(--text2);
}
.diag-fingerprint-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--surface2, rgba(127, 127, 127, 0.12));
  padding: 2px 8px;
  border-radius: 6px;
  cursor: pointer;
  user-select: all;
}
.diag-fingerprint-code:hover,
.diag-fingerprint-code:focus-visible {
  outline: 1px solid var(--border);
}

/* ── Enrichment badges: Bloom's / difficulty / readability ─────────────────── */
.bloom-badge select,
.difficulty-badge select {
  background: transparent;
  border: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  padding: 0;
  max-width: 100%;
  min-width: 0;
}
/* v2.3.2 AX2 (1.4.3): the three levels keep their dark-theme colours through tokens; the light
   theme (below) maps them to its AA green, yellow and red, since #2ea043 on the light tint read 2.86:1. */
.difficulty-badge.difficulty-easy { background: rgba(46, 160, 67, 0.15); color: var(--difficulty-easy, #2ea043); }
.difficulty-badge.difficulty-medium { background: rgba(210, 153, 34, 0.15); color: var(--difficulty-medium, #d29922); }
.difficulty-badge.difficulty-hard { background: rgba(248, 81, 73, 0.15); color: var(--difficulty-hard, #f85149); }
.readability-badge { background: rgba(56, 139, 253, 0.12); }

/* ── 11.1: Question bank standards filter ──────────────────────────────────── */
.bank-filter-bar {
  display: flex;
  gap: 10px;
  align-items: center;
  margin: 14px 0;
  flex-wrap: wrap;
}
/* 160, not 220: the bar carries four controls since v2.2 P31 and a 220px floor
   on the search box pushed the last toggle onto its own line at the width the
   review pane actually has. It still grows to fill whatever is left over. */
.bank-filter-bar input[type="search"] { flex: 1; min-width: 160px; }
.bank-filter-bar select {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface2);
  color: var(--text1);
  max-width: 340px;
}
.bank-filter-count { font-size: 0.85rem; color: var(--text2); }
.standards-badge { background: rgba(163, 113, 247, 0.14); }

/* The review queue (v2.2 P27). Styled as a control, not a badge: it does
   something when clicked, and a warning-coloured pill that also happens to be
   clickable reads as decoration.
   `--red` on purpose, not a new token: it is the exact colour of the
   "⚠️ Needs Review" badge and the card's left border, and a filter that finds
   those questions in a different colour reads as a different concept. */
.bank-review-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface2);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
}
.bank-review-toggle input { cursor: pointer; margin: 0; accent-color: var(--red); }
.bank-review-toggle:has(input:checked) {
  border-color: rgba(248, 113, 113, 0.5);
  background: rgba(248, 113, 113, 0.12);
}
/* Zero flagged questions is a RESULT, not an absence: the control stays put and
   says so. Removing it would leave the user unsure whether the parser checked. */
.bank-review-toggle:has(input:disabled) { opacity: 0.55; cursor: default; }
.bank-review-toggle:has(input:disabled) input { cursor: default; }
/* Focus lives on the checkbox, so lift the ring to the whole control or
   keyboard users cannot see where they are.
   `:focus-visible`, not `:focus-within`: with the latter a mouse click leaves a
   purple ring sitting on top of the red checked state, and the two read as two
   different things having happened. Keyboard focus still shows it. */
.bank-review-toggle:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The duplicates queue (v2.2 P31). Same control, different finding, so it
   reuses .bank-review-toggle and only re-tints: a near-duplicate is not a
   parse failure, and colouring it the same red as "Needs review" would say it
   is. Accent, which the card badge below matches. */
.bank-dupe-toggle input { accent-color: var(--accent); }
.bank-dupe-toggle:has(input:checked) {
  border-color: rgba(124, 92, 252, 0.55);
  background: rgba(124, 92, 252, 0.14);
}
/* Names its partner by POSITION, so it stays true as the bank is reordered. */
.q-badge.dupe-badge {
  background: rgba(124, 92, 252, 0.14);
  color: var(--accent-readable);
  border: 1px solid rgba(124, 92, 252, 0.35);
  cursor: help;
}

/* The bulk bar (v2.2 P32). Appears only once something is selected, so an
   untouched bank carries no destructive control at all. */
.bulk-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 14px;
  padding: 10px 14px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.bulk-count { font-size: 0.85rem; color: var(--text); font-weight: 700; margin-right: 4px; }
.bulk-btn {
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.82rem;
  cursor: pointer;
}
.bulk-btn:hover { background: var(--surface3); }
.bulk-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The destructive one is the only one that looks destructive. Colouring the
   whole bar red would make "Clear selection" read as dangerous too. */
.bulk-danger { border-color: rgba(248, 113, 113, 0.45); color: var(--red); }
.bulk-danger:hover { background: rgba(248, 113, 113, 0.12); }
.bulk-undo { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.bulk-undo span { font-size: 0.82rem; color: var(--text2); }
/* The per-card checkbox. Sits before the badges so it reads as belonging to the
   whole card rather than to "Question N". */
.q-select { margin: 0 2px 0 0; cursor: pointer; accent-color: var(--accent); flex: none; }

/* Shown when a filter matched nothing. A list that has gone blank has to say
   why — silence there reads as a crash rather than as a filter doing its job. */
.bank-filter-empty {
  margin: 4px 0 14px;
  padding: 14px 16px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text2);
  font-size: 0.9rem;
}


/* ── A11y: light theme (WCAG AA contrast), dyslexic font, toolbar ─────────── */
html[data-theme="light"] {
  --bg:       #f5f6f9;
  --surface:  #ffffff;
  --surface2: #eef0f5;
  --surface3: #e3e6ef;
  --border:   #c9cede;
  --text:     #16181f;
  --text2:    #3c4254;
  --text3:    #555d73;
  --accent:   #5235d8;
  --accent-readable: #5235d8;
  --accent-text: #5235d8;
  --badge-text:  #5235d8;
  --difficulty-easy: #0e7a55;
  --difficulty-medium: #8a5b00;
  --difficulty-hard: #b3261e;
  --accent2:  #4326c4;
  --green:    #0e7a55;
  --yellow:   #8a5b00;
  --red:      #b3261e;
}
html[data-font="dyslexic"] body,
html[data-font="dyslexic"] input,
html[data-font="dyslexic"] button,
html[data-font="dyslexic"] textarea,
html[data-font="dyslexic"] select {
  font-family: 'OpenDyslexic', 'Comic Sans MS', 'Comic Sans', Verdana, sans-serif !important;
  letter-spacing: 0.04em;
  word-spacing: 0.12em;
}
.a11y-toolbar { display: flex; gap: 8px; align-items: center; }
/* v2.3.2 CMDPOS1: the display buttons and Commands as one group, 8 px apart as the display buttons are. */
.header-tools { display: flex; gap: 8px; align-items: center; }
/* v2.3.2 B15: the command palette, its header button and the review screen's health line. */
.command-palette-btn { display: inline-flex; align-items: center; gap: 6px; font-size: 0.85rem; font-weight: 600; white-space: nowrap; }
/* The icon is for narrow layouts only: on wide screens the word fits beside the brand without
   pushing its tagline to a second line (with the icon too, the header grew 72 -> 91 px). */
.command-icon { display: none; font-size: 1rem; line-height: 1; }
.command-card { width: min(560px, calc(100vw - 32px)); max-height: min(70vh, 560px); display: flex; flex-direction: column; gap: 10px; }
.command-card h3 { margin: 0; }
.command-help { margin: 0; font-size: 0.8rem; color: var(--text2); }
.command-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; }
.command-option { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 9px 12px; cursor: pointer; color: var(--text); min-height: 24px; }
.command-option[aria-selected="true"] { background: var(--surface3); box-shadow: inset 3px 0 0 var(--accent-readable); }
.command-option[aria-disabled="true"] .command-name { color: var(--text2); }
.command-note { font-size: 0.78rem; color: var(--text2); white-space: nowrap; }
.command-status { margin: 0; font-size: 0.8rem; color: var(--text2); min-height: 1.2em; }
.bank-health { margin: 6px 0 0; font-size: 0.9rem; color: var(--text2); }
.bank-health:empty { display: none; }
.a11y-tool-btn {
  background: var(--surface2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 6px 10px;
  cursor: pointer;
  font-size: 1rem;
}
.a11y-tool-btn[aria-pressed="true"] { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(124,92,252,0.3); }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* ── Sortable review cards ─────────────────────────────────────────────────── */
.q-card-order-controls { display: inline-flex; gap: 4px; margin-right: 8px; flex: 0 0 auto; }
/* v2.3.2 B14: the list's history, a card's structure actions, adding a question. */
.history-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 8px 0; }
.history-bar .bulk-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.history-status { color: var(--text2); font-size: 0.85rem; }
.q-structure-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.q-structure-btn { background: var(--surface2); border: 1px solid var(--border); color: var(--text2); border-radius: 6px; padding: 4px 10px; font-size: 0.8rem; cursor: pointer; }
.q-structure-btn:hover { color: var(--text); border-color: var(--accent); }
.add-question-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0; }
.add-question-bar select { width: auto; }
.history-list { list-style: none; margin: 8px 0; padding: 0; max-height: 50vh; overflow: auto; }
.history-list li { padding-inline-start: calc(var(--depth, 0) * 16px); }
.history-go { background: none; border: 1px solid transparent; color: var(--text); text-align: start; padding: 4px 8px; border-radius: 6px; cursor: pointer; width: 100%; }
.history-go:hover { border-color: var(--border); }
.history-go[aria-current] { font-weight: 600; border-color: var(--accent); }
/* v2.3.2 B14.2: a question's Preview. */
.preview-dialog { width: min(900px, 94vw); }
.preview-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 8px 0; }
.preview-tab { background: none; border: 1px solid transparent; border-bottom: none; color: var(--text2); padding: 6px 12px; border-radius: 6px 6px 0 0; cursor: pointer; min-height: 32px; }
.preview-tab[aria-selected="true"] { color: var(--text); border-color: var(--border); background: var(--surface2); font-weight: 600; }
.preview-panel { min-height: 200px; }
.preview-frame { width: 100%; height: 420px; border: 1px solid var(--border); border-radius: var(--radius); background: #fff; }
.preview-flashcard { border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; display: grid; gap: 12px; }
.preview-data { max-height: 50vh; overflow: auto; white-space: pre-wrap; word-break: break-word; font-size: 0.8rem; background: var(--surface2); padding: 12px; border-radius: var(--radius); }
.preview-status { margin-inline-start: 8px; color: var(--text2); font-size: 0.85rem; }
.q-move-btn {
  background: var(--surface2);
  border: 1px solid var(--border);
  color: var(--text2);
  border-radius: 6px;
  width: 26px; height: 26px;
  cursor: pointer;
  line-height: 1;
}
.q-move-btn:hover { color: var(--text); border-color: var(--accent); }
.q-card.drag-source { opacity: 0.45; }
.q-card.drag-target { outline: 2px dashed var(--accent); outline-offset: 4px; }
.q-tts-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text2);
  border-radius: 6px;
  padding: 3px 8px;
  cursor: pointer;
  flex: 0 0 auto;
}
.q-tts-btn[aria-pressed="true"] { color: var(--green); border-color: var(--green); }


/* ── Modal dialogs ─────────────────────────────────────────────────────────
   ONE rule for every dialog, keyed off .overlay rather than off each id.
   It used to be written per-id — #cameraModal.overlay and #assembleModal.overlay
   — so #studyModal, added later with the same markup and the same class, got no
   positioning at all and rendered as a plain block in document flow at the
   bottom of the page. It was never a modal. Nobody noticed because the same
   release also made it unopenable, so it was never seen on screen.
   A new dialog now only has to carry class="overlay hidden" to behave. */
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.65); display: flex; align-items: center; justify-content: center; z-index: 200; }
.camera-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; width: min(560px, 92vw); }
/* max-height + scroll keeps a dialog usable at 320px and at 200% zoom, both of
   which the a11y behaviour suite checks. */
.assemble-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; width: min(520px, 92vw); max-height: 90vh; overflow-y: auto; }
/* The diagnostic preview shows the WHOLE file before it is written. It is
   monospaced because the report's evidence is alignment and punctuation, and
   proportional text would hide the very thing the reader is checking. */
/* pre-wrap, not pre: the notice a user has to READ before deciding is prose and
   runs past the card, and nobody scrolls a preview sideways to finish a
   sentence. Wrapping keeps the leading indentation that carries the sample
   alignment, which is the only part where columns matter. */
.diagnose-preview { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 0; max-height: 300px; overflow-y: auto; overflow-x: hidden; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .75rem; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text1); }
/* Wider than the other dialogs on purpose — the preview is the feature, and a
   file the user is asked to approve should not be read through a keyhole. */
.diagnose-card { width: min(680px, 94vw); }
/* The destination link (A11Y-RP): the browser's default link blue fails WCAG
   contrast on the dark card, so it takes the theme's readable accent, which
   passes in both themes. */
#diagnoseDestination a { color: var(--accent-readable); font-weight: 600; }
/* Per-section counts in the practice-paper dialog. A grid so the section names
   and their number boxes line up however long the names are. */
.assemble-sections { display: grid; grid-template-columns: 1fr auto; gap: 8px 12px; align-items: center; }
.assemble-sections label { font-size: .85rem; color: var(--text1); }
.assemble-sections .assemble-section-meta { color: var(--text2); font-size: .78rem; }
.assemble-sections input { width: 72px; padding: 6px 8px; text-align: right; background: var(--bg2); color: var(--text1); border: 1px solid var(--border); border-radius: 6px; }
/* The setup-view failure prompt. Deliberately calm: a user whose file just
   failed is already annoyed, and an alarm-coloured panel reads as blame. */
.parse-failure-report { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 20px; padding: 14px 16px; background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius); }
/* `display: flex` on the line above BEATS the user-agent's `[hidden]{display:none}`,
   so without this the panel is visible from page load and the parse-failed
   prompt sits on a screen where nothing has failed. Same class of defect as the
   v1.9.2 dialogs: CSS quietly defeating a visibility mechanism the JS believes
   in. Any element styled with display:* must restate its own hidden rule. */
.parse-failure-report[hidden] { display: none; }
.parse-failure-report p { margin: 0; flex: 1 1 260px; font-size: .9rem; color: var(--text2); }
/* v2.3.2 LIB1: the sets saved on this device, and the review screen's save status. */
.saved-sets { margin-top: 24px; padding: 16px 20px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.saved-sets[hidden] { display: none; }
.saved-sets-title { margin: 0 0 4px; font-size: 1.1rem; color: var(--text); }
.saved-sets-help { margin: 0 0 12px; font-size: .85rem; color: var(--text2); }
.saved-sets-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.saved-set { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 10px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; }
.saved-set-text { flex: 1 1 220px; min-width: 0; }
.saved-set-title { margin: 0; font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.saved-set-meta { margin: 2px 0 0; font-size: .82rem; color: var(--text2); }
.saved-set-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.saved-set-actions .btn, .saved-set-rename-form .btn { min-height: 32px; padding: 6px 14px; }
.saved-set-rename-form { flex: 1 1 220px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.saved-set-rename-form .input-field { flex: 1 1 180px; min-width: 0; }
/* v2.3.2 LIB2: choose sets to open together. */
.saved-set-check { width: 24px; height: 24px; margin: 0; flex: 0 0 auto; accent-color: var(--accent); cursor: pointer; }
.saved-sets-combine { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 10px 0 0; }
.saved-sets-combine[hidden] { display: none; }
.saved-sets-combine-help { margin: 0; flex: 1 1 220px; font-size: .85rem; color: var(--text2); }
.saved-sets-status { margin: 10px 0 0; font-size: .85rem; color: var(--text2); overflow-wrap: anywhere; }
.saved-sets-status:empty, .set-save-status:empty { position: absolute; width: 1px; height: 1px; overflow: hidden; margin: 0; }
.set-save-status { margin: 4px 0 0; font-size: .85rem; color: var(--text2); }
.set-save-status.is-failed { color: var(--red); font-weight: 600; }
.attach-papers { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 8px; padding: 10px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; }
.attach-papers[hidden] { display: none; }
.attach-papers-text { margin: 0; flex: 1 1 240px; font-size: .88rem; color: var(--text2); }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* v2.3.2 DATA1: "Your data on this device" (back up, restore, delete). */
.data-panel-link { margin: 14px 0 0; text-align: center; }
.qx-dialog.data-panel { width: min(560px, calc(100vw - 32px)); max-height: calc(100vh - 32px); overflow: auto; }
.data-panel-summary { margin: 0; color: var(--text2); font-size: .9rem; }
.data-panel-part { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 12px 0 0; border-top: 1px solid var(--border); }
.data-panel-part-title { margin: 0; font-size: 1rem; color: var(--text); }
.data-panel-help { margin: 0; font-size: .85rem; color: var(--text2); }
.data-panel-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.data-restore-choice { display: flex; flex-direction: column; gap: 8px; width: 100%; padding: 10px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; }
.data-restore-choice[hidden] { display: none; }
.data-restore-choice p { margin: 0; color: var(--text); font-size: .9rem; overflow-wrap: anywhere; }
.data-restore-problems { margin: 0; padding-left: 18px; color: var(--text2); font-size: .85rem; }
.data-restore-problems[hidden] { display: none; }
.data-delete-btn { color: var(--text); border-color: var(--red); }
.data-panel .toggle-option span { color: var(--text); }
.data-panel-status { margin: 0; font-size: .88rem; color: var(--text2); overflow-wrap: anywhere; }
.data-panel-status:empty { position: absolute; width: 1px; height: 1px; overflow: hidden; margin: 0; }
/* v2.3.2 HELP1: "How QExtract works", in the footer of every screen. */
.footer .footer-help { margin-top: 2px; }
.footer .footer-help::before { content: none; }
.footer .footer-help .link-btn { font-size: .85rem; user-select: auto; -webkit-user-select: auto; }
.qx-dialog.help-panel { width: min(600px, calc(100vw - 32px)); max-height: calc(100vh - 32px); overflow: auto; }
.help-panel p, .help-panel li { color: var(--text); font-size: .92rem; line-height: 1.5; }
.help-panel p { margin: 0 0 8px; }
.help-steps { margin: 0; padding-left: 22px; display: flex; flex-direction: column; gap: 10px; }
.help-steps ul, .help-notes { margin: 0 0 8px; padding-left: 20px; }
.help-part-title { margin: 6px 0 4px; font-size: 1rem; color: var(--text); }
.help-panel code { font-size: .88em; overflow-wrap: anywhere; }

/* ── Source view (v2.3 P2.1): a question's page, its region highlighted ──── */
/* Wide on purpose: the page is the content, and a region read through a
   keyhole is no check at all. */
.source-card { width: min(920px, 96vw); display: flex; flex-direction: column; }
.source-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.source-head h3 { margin: 0; font-size: 1rem; overflow-wrap: anywhere; }
.source-status { font-size: .85rem; color: var(--text2); margin: 8px 0 10px; }
/* The gutter is reserved up front: the page is sized to the width left once
   the vertical scrollbar exists, or the scrollbar would push it into a
   horizontal scroll of its own. */
.source-scroll { overflow: auto; scrollbar-gutter: stable; max-height: 72vh; border: 1px solid var(--border); border-radius: 8px; background: var(--surface2); }
.source-page { position: relative; width: max-content; margin: 0 auto; }
.source-page canvas { display: block; background: #fff; }
/* An outline, not a fill: the words under it must stay readable. */
.source-highlight { position: absolute; border: 3px solid var(--accent); border-radius: 4px; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.18); pointer-events: none; }
.q-source-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text2);
  border-radius: 6px;
  padding: 3px 8px;
  cursor: pointer;
  flex: 0 0 auto;
  margin-right: 6px;
}
.q-source-btn:hover { color: var(--text); border-color: var(--accent); }
/* v2.3 LIC2: the trial banner (days left, the end date, the way to the free
   key) and the license dialog. The tone colours the edge, the bar and, near
   the end, the headline; the words carry the meaning on their own. */
.trial-banner { --tone: var(--accent); display: flex; align-items: center; justify-content: space-between; gap: 14px 24px; flex-wrap: wrap; margin-top: -12px; padding: 14px 18px; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--tone); border-radius: var(--radius); }
.trial-banner[data-tone="soon"] { --tone: var(--yellow); }
.trial-banner[data-tone="last"] { --tone: var(--red); }
.trial-banner-main { flex: 1 1 320px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.trial-banner-title { margin: 0; font-weight: 700; font-size: 1rem; color: var(--text); }
.trial-banner[data-tone="soon"] .trial-banner-title, .trial-banner[data-tone="last"] .trial-banner-title { color: var(--tone); }
.trial-banner-detail { margin: 0; color: var(--text2); font-size: 0.875rem; line-height: 1.5; }
.trial-progress { height: 6px; max-width: 360px; border-radius: 3px; background: var(--surface3); overflow: hidden; }
.trial-progress-fill { height: 100%; width: 0; border-radius: 3px; background: var(--tone); }
.trial-banner-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.trial-banner-actions .btn { flex: 1 1 auto; padding: 10px 16px; font-size: 0.9rem; white-space: nowrap; text-decoration: none; }
/* Narrow screens: a tighter banner whose two buttons share a row while they
   fit, and stack once they do not (the shortest screens shorten the link's
   words; see the header rules). */
@media (max-width: 600px) {
  .trial-banner { margin-top: -6px; padding: 12px 14px; gap: 12px; }
  .trial-banner-title { font-size: 0.95rem; }
  .trial-banner-detail { font-size: 0.82rem; }
  .trial-banner-actions { width: 100%; }
  .trial-banner-actions .btn { padding: 10px 12px; font-size: 0.85rem; min-height: 44px; }
}
.license-sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.license-overlay { z-index: 400; }
.license-card { max-width: 500px; width: calc(100% - 32px); max-height: calc(100vh - 32px); overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
.license-intro, .license-privacy { margin: 0; color: var(--text2); font-size: 0.9rem; line-height: 1.5; }
.license-steps { margin: 4px 0 0; padding: 0; list-style: none; counter-reset: license-step; display: flex; flex-direction: column; gap: 16px; }
.license-steps > li { counter-increment: license-step; position: relative; padding-left: 38px; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.license-steps > li::before { content: counter(license-step); position: absolute; left: 0; top: -3px; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; font-size: 0.85rem; font-weight: 700; color: var(--text); background: var(--surface3); border: 1px solid var(--border); }
.license-step-text { font-weight: 600; font-size: 0.9rem; line-height: 1.4; color: var(--text); }
.license-get { text-decoration: none; }
.license-input { width: 100%; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 0.8rem; word-break: break-all; resize: vertical; }
.license-hint { font-size: 0.8rem; color: var(--text3); }
.license-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.license-status { margin: 0; min-height: 1.2em; font-size: 0.85rem; color: var(--text); }
.license-status[data-ok="true"] { color: var(--green); font-weight: 600; }
body.license-locked { overflow: hidden; }
/* v2.3 B13.2: a printed question missing after this one. A note, not a card. */
.q-gap { margin-top: 12px; padding: 8px 12px; border-left: 3px solid var(--yellow); background: var(--surface2); border-radius: 6px; }
.q-gap-text { margin: 0 0 6px; font-size: 0.85rem; color: var(--text); line-height: 1.4; }
.q-gap-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.q-gap-show, .q-gap-add, .q-gap-dismiss { display: inline-flex; align-items: center; min-height: 28px; background: transparent; border: 1px solid var(--border); color: var(--text2); border-radius: 6px; padding: 3px 10px; cursor: pointer; font-size: 0.8rem; }
.q-gap-show:hover, .q-gap-add:hover, .q-gap-dismiss:hover, .q-gap-show:focus-visible, .q-gap-add:focus-visible, .q-gap-dismiss:focus-visible { color: var(--text); border-color: var(--accent); }

/* ── v2.3.1 UX1: file slots (Evaluate), in-page status lines, and the app's own dialog ── */
.upload-link-btn { background: none; border: none; padding: 0; font: inherit; cursor: pointer; color: var(--accent-readable); text-decoration: underline; text-underline-offset: 2px; }
.upload-link-btn:hover { color: var(--accent-readable); }
.upload-link-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.file-status { font-size: 0.9rem; color: var(--text2); margin: 0; min-height: 1.3em; text-align: center; overflow-wrap: anywhere; }
.file-status.is-error { color: var(--red); }
.file-slots { display: flex; flex-direction: column; gap: 12px; }
.file-slot { display: flex; align-items: center; gap: 14px; padding: 16px 18px; border: 2px dashed var(--border); border-radius: var(--radius); background: var(--surface); cursor: pointer; transition: border-color 0.2s, background 0.2s; }
.file-slot:hover, .file-slot.drag-over { border-color: var(--accent); background: var(--surface2); }
.file-slot[data-empty="false"] { border-style: solid; cursor: default; }
.file-slot-icon { font-size: 1.8rem; flex: none; }
.file-slot-body { flex: 1; min-width: 0; }
.file-slot-title { font-weight: 600; color: var(--text); margin: 0 0 2px; }
.file-slot-text { font-size: 0.9rem; color: var(--text2); margin: 0; }
.file-slot[data-empty="false"] .file-slot-empty, .file-slot[data-empty="true"] .file-slot-chosen { display: none; }
.file-slot-name { font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.file-slot-size { color: var(--text3); font-size: 0.8rem; margin-left: 6px; }
.file-slot-clear { flex: none; }
.eval-intro { color: var(--text2); margin: 0; font-size: 0.9rem; }
.eval-result { margin-top: 8px; padding: 20px; background: var(--surface2); border-radius: var(--radius); border: 1px solid var(--border); }
.eval-score-line { font-size: 1.4rem; margin: 0 0 16px; text-align: center; }
.eval-score { color: var(--accent-readable); }
.eval-details { display: flex; flex-direction: column; gap: 8px; font-size: 0.9rem; max-height: 300px; overflow-y: auto; }
/* v2.3.2 B15.4: what QExtract repaired on a card, and the way back. Text, not colour. */
.q-repairs { margin: 6px 0 10px; font-size: 0.85rem; color: var(--text2); }
.q-repairs summary { cursor: pointer; }
.q-repairs summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.q-repair-list { margin: 6px 0 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.q-repair-original { color: var(--text); overflow-wrap: anywhere; }
.q-repair-revert { margin-left: 6px; }
/* v2.3.2 EVAL2: the class table. Marks are words for screen readers and a
   symbol on screen; colour only repeats what the symbol says. */
.eval-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.eval-class-table-wrap { overflow-x: auto; max-width: 100%; }
.eval-class-table-wrap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.eval-class-table { border-collapse: collapse; font-size: 0.85rem; min-width: 100%; }
.eval-class-table caption { text-align: left; font-weight: 600; margin-bottom: 8px; }
.eval-class-table th, .eval-class-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: center; white-space: nowrap; }
.eval-class-table th[scope="row"] { text-align: left; }
.eval-class-table .mark-right { color: var(--green); }
.eval-class-table .mark-wrong { color: var(--red); }
.eval-class-table tfoot th, .eval-class-table tfoot td { font-weight: 600; }
.eval-most-missed { margin: 12px 0; }
.eval-file-errors { margin: 8px 0; padding-left: 20px; color: var(--red); font-size: 0.9rem; }
/* The dialog: a native <dialog> (modal, focus kept and returned by the browser), styled as a card. */
.qx-dialog { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); padding: 0; width: min(440px, calc(100vw - 32px)); box-shadow: 0 24px 64px rgba(0,0,0,0.45); }
.qx-dialog::backdrop { background: rgba(0,0,0,0.65); }
.qx-dialog-form { display: flex; flex-direction: column; gap: 12px; padding: 22px 24px; }
.qx-dialog-title { font-size: 1.1rem; margin: 0; }
.qx-dialog[data-tone="error"] .qx-dialog-title::before { content: '⚠️ '; }
.qx-dialog[data-tone="success"] .qx-dialog-title::before { content: '✓ '; color: var(--green); }
.qx-dialog-text { margin: 0; color: var(--text2); font-size: 0.95rem; line-height: 1.5; white-space: pre-line; overflow-wrap: anywhere; }
.qx-dialog-label { font-size: 0.85rem; font-weight: 600; margin-top: 4px; }
.qx-dialog-error { margin: 0; color: var(--red); font-size: 0.85rem; }
.qx-dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 6px; flex-wrap: wrap; }
.qx-dialog-actions .btn { padding: 10px 18px; font-size: 0.95rem; }

/* v2.3.x PG1: the Source playground. The outline becomes a box the teacher
   moves and resizes; part boxes mark the question text or one option; the
   panel reads them and applies the result. Colours are the theme's tokens,
   so both themes keep their contrast. */
.source-head-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.source-body { display: flex; gap: 14px; align-items: flex-start; }
.source-body .source-scroll { flex: 1 1 auto; min-width: 0; }
.source-card:has(.pg-panel:not([hidden])) { width: min(1240px, 98vw); }
.pg-panel { flex: 0 0 290px; display: flex; flex-direction: column; gap: 10px; font-size: .9rem; max-height: 72vh; overflow: auto; }
.pg-help { margin: 0; color: var(--text2); line-height: 1.45; }
.pg-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.pg-label { color: var(--text2); }
.pg-select { flex: 1 1 120px; min-width: 0; width: auto; padding: 8px 10px; }
.pg-parts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pg-parts li { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px; color: var(--text); }
.pg-remove { padding: 4px 12px; min-height: 32px; font-size: .85rem; }
.pg-heading { margin: 4px 0 0; font-size: .9rem; color: var(--text); }
.pg-preview { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; background: var(--surface2); }
.pg-preview-empty { margin: 0; color: var(--text2); }
.pg-preview-stem { margin: 0 0 6px; color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }
.pg-preview-options { margin: 0; padding-left: 1.6em; color: var(--text); overflow-wrap: anywhere; }
.pg-preview-note { margin: 6px 0 0; color: var(--text2); font-style: italic; }
.pg-box { position: absolute; border: 2px dashed var(--accent); border-radius: 3px; background: rgba(124, 92, 252, 0.06); cursor: move; touch-action: none; }
.pg-box:focus-visible { outline: 3px solid var(--accent-readable); outline-offset: 2px; }
.pg-region { border-style: solid; border-width: 3px; }
/* PG1A, WCAG 1.4.11: the page under the boxes is always white, whatever the
   theme, so part borders use fixed colours with 3:1 or more against white
   (green 5.3:1, amber 5.8:1); the dark theme's light green and yellow tokens
   were 1.8:1 and 1.7:1 there. */
.pg-part { border-color: #0e7a55; background: rgba(14, 122, 85, 0.08); }
.pg-part[data-role="stem"] { border-color: #8a5b00; background: rgba(138, 91, 0, 0.08); }
/* In the left margin, beside its box: above it, the tag hid the line above
   (the first snapshot showed it over the question text). */
.pg-part-tag { position: absolute; right: 100%; top: -2px; margin-right: 6px; padding: 1px 6px; font-size: .75rem; line-height: 1.4; border-radius: 4px; background: #0e7a55; color: #ffffff; white-space: nowrap; pointer-events: none; }
/* PG1.1: a box the playground drew where its option differs from the page or was not read:
   solid and thicker than the dashed part boxes, same colours (3:1 kept); its tag says why. */
.pg-box.pg-part-check { border-style: solid; border-width: 3px; }
.pg-part[data-role="stem"] .pg-part-tag { background: #8a5b00; }
/* A 14px handle with a 26px hit area (WCAG 2.5.8); the keyboard and the
   two-click redraw do the same without a pointer. */
.pg-handle { position: absolute; width: 14px; height: 14px; background: var(--surface); border: 2px solid var(--accent); border-radius: 3px; touch-action: none; }
.pg-handle::after { content: ''; position: absolute; inset: -7px; }
.pg-part .pg-handle { border-color: #0e7a55; }
.pg-part[data-role="stem"] .pg-handle { border-color: #8a5b00; }
.pg-nw { left: -8px; top: -8px; cursor: nwse-resize; }
.pg-ne { right: -8px; top: -8px; cursor: nesw-resize; }
.pg-sw { left: -8px; bottom: -8px; cursor: nesw-resize; }
.pg-se { right: -8px; bottom: -8px; cursor: nwse-resize; }
.source-page.pg-drawing { cursor: crosshair; touch-action: none; }
.source-page.pg-drawing .pg-box { cursor: crosshair; }
.pg-draft { position: absolute; border: 2px dashed #1f2937; border-radius: 3px; pointer-events: none; }
/* What a box holds, for a screen reader only (PG1A). */
.pg-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@media (max-width: 900px) {
  .source-body { flex-direction: column; align-items: stretch; }
  .pg-panel { flex-basis: auto; width: 100%; max-height: none; }
}
