/* ─────────────────────────────
   PROJECT STYLES
   ─────────────────────────────

   Project-specific styles. Prefer the design system;
   add here only what it can't express.
   ───────────────────────────── */


/* -- Dark mode toggle (icon swap) -- */
/* Icons injected by assets/js/theme-toggle.js; the framework no
   longer ships these rules, so they live here alongside the script. */
.dark-mode-toggle .dark-mode-icon-dark {
  display: none;
}

[data-theme="dark"] .dark-mode-toggle .dark-mode-icon-light {
  display: none;
}

[data-theme="dark"] .dark-mode-toggle .dark-mode-icon-dark {
  display: flex;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .dark-mode-toggle .dark-mode-icon-light {
    display: none;
  }

  :root:not([data-theme]) .dark-mode-toggle .dark-mode-icon-dark {
    display: flex;
  }
}


/* ─────────────────────────────
   QUIZ
   ─────────────────────────────

   Layout the design system can't express: space-between rows,
   answer-option button geometry, and answer-input width cap.
   ───────────────────────────── */

/* -- [hidden] must always win: the JS toggles screens/buttons with the
   hidden attribute, and component display rules (e.g. .button's
   inline-flex) would otherwise override the browser default -- */
[hidden] {
  display: none !important;
}

/* -- Design-system bug workaround: .button[data-color] sets
   --button-text-color to inverted AFTER the outline variant sets it to
   the button colour (equal specificity, later rule wins), leaving
   outline+colour buttons with invisible text. Restate the outline text
   colour at higher specificity. Remove when fixed upstream in
   @bydefaultstudio/design-system. -- */
.button[data-variant="outline"][data-color] {
  --button-text-color: var(--button-color);
}

/* -- Toolbar / rows (space-between flex) -- */
.quiz-toolbar,
.quiz-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  flex-wrap: wrap;
}

/* -- Root scale: every font and spacing token is rem-based, so stepping
   the root font-size up on large screens scales type and spacing
   together (px container widths, borders, and icons stay fixed).
   Percentages instead of px restore the user's browser font-size
   preference, which the design system's hardcoded 16px ignores. -- */
html {
  font-size: 112.5%; /* 18px at browser default */
}

@media (min-width: 1440px) {
  html {
    font-size: 118.75%; /* 19px at browser default */
  }
}

@media (min-width: 1920px) {
  html {
    font-size: 125%; /* 20px at browser default */
  }
}

/* -- Fluid type: headings clamp larger on desktop. rem-based bounds with a
   modest vw term, so browser zoom and user font-size still scale. -- */
h1 {
  font-size: clamp(2.25rem, 1.5rem + 2.5vw, 4rem);
}

h2.text-size-xl {
  font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem);
}

/* -- Lifeline bar: a header-style stretched control on the question screen.
   Ask a friend is the only lifeline up here - the multiple-choice decision
   lives in the answer-mode fork inside the answer area. -- */
.quiz-lifelines {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-s);
  width: 100%;
}

/* In the app it sticks just under the fixed header while the question
   scrolls (scoped to .quiz-screen so the styleguide demo stays in flow).
   top matches the .page-content header offset; the background stops
   content showing through. */
.quiz-screen .quiz-lifelines {
  position: sticky;
  top: 3.5rem;
  z-index: 9;
  background-color: var(--background-primary);
  padding-block: var(--space-s);
}

/* -- Sticky action footer: the screen's primary action always in reach
   (player setup → Start quiz, summary → Leaderboard). position: sticky,
   not fixed - sticky is scroll-relative, so it survives transformed
   ancestors (GSAP screen tweens break fixed) and iOS URL-bar collapse.
   As the section's last child it pins to the viewport bottom while
   scrolling and parks at the end of the content. -- */
.quiz-sticky-footer {
  position: sticky;
  bottom: 0;
  z-index: 10;
  display: flex;
  justify-content: center;
  padding: var(--space-s) var(--space-m) calc(var(--space-s) + env(safe-area-inset-bottom));
  background-color: var(--background-primary);
  border-top: var(--border-s) solid var(--border-faded);
}

.quiz-sticky-footer .button {
  flex: 1;
  max-width: 24rem;
}

/* -- Status callouts read like toasts: ALL text takes the status colour
   on the pastel background, in both themes - the design system only
   colours the title, leaving grey body text that fails in dark mode.
   Works with theme.css keeping --status-* saturated in dark mode. -- */
.callout[data-type="success"] { color: var(--status-success); }
.callout[data-type="warning"] { color: var(--status-warning); }
.callout[data-type="danger"] { color: var(--status-danger); }
.callout[data-type="info"] { color: var(--status-info); }

/* -- Design-system bug workaround: the generic dark-mode toast rule
   ([data-theme="dark"] .toast → neutral grey) comes after the typed
   colour rules at equal specificity and wipes them out. Re-assert the
   status colours; this file loads last so these win the tie in both
   themes. Remove when fixed upstream in @bydefaultstudio/design-system. -- */
.toast[data-type="success"], .toast[data-type="green"] { background: var(--status-success-bg); color: var(--status-success); }
.toast[data-type="warning"], .toast[data-type="yellow"] { background: var(--status-warning-bg); color: var(--status-warning); }
.toast[data-type="danger"], .toast[data-type="red"] { background: var(--status-danger-bg); color: var(--status-danger); }
.toast[data-type="info"], .toast[data-type="blue"] { background: var(--status-info-bg); color: var(--status-info); }

/* System-preference fallback: the design system's no-JS grey toast rule
   (:root:not([data-theme]) .toast) has higher specificity, so the
   re-assertion must be scoped to beat it there too. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .toast[data-type="success"], :root:not([data-theme]) .toast[data-type="green"] { background: var(--status-success-bg); color: var(--status-success); }
  :root:not([data-theme]) .toast[data-type="warning"], :root:not([data-theme]) .toast[data-type="yellow"] { background: var(--status-warning-bg); color: var(--status-warning); }
  :root:not([data-theme]) .toast[data-type="danger"], :root:not([data-theme]) .toast[data-type="red"] { background: var(--status-danger-bg); color: var(--status-danger); }
  :root:not([data-theme]) .toast[data-type="info"], :root:not([data-theme]) .toast[data-type="blue"] { background: var(--status-info-bg); color: var(--status-info); }
}

/* -- Header eyebrow: reset paragraph margin inside the toolbar row -- */
.quiz-eyebrow {
  margin: 0;
}

/* -- Fixed header: burger + wordmark left, dark-mode toggle right -- */
.quiz-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-xs) var(--space-s);
  background-color: var(--background-top-nav);
  border-bottom: var(--border-s) solid var(--border-faded);
}

/* Structural offset for the fixed header height - not block spacing.
   Scoped so pages without the header (styleguide) are unaffected. */
body:has(> .quiz-header) .page-content {
  padding-top: 3.5rem;
}

/* Structural offset while the on-screen keyboard is up - scroll room so
   the answer input can sit above an overlaid keyboard (iOS). Driven by
   keyboard-inset.js; 0 whenever the keyboard is closed or the browser
   resizes the page itself (Android). Not block spacing. */
:root {
  --keyboard-inset: 0px;
}

.page-content {
  padding-bottom: var(--keyboard-inset);
}

/* -- Menu drawer: the native dialog pinned to the left edge, full height -- */
.quiz-menu {
  margin: 0;
  margin-inline-end: auto;
  width: min(20rem, 85vw);
  height: 100dvh;
  max-height: 100dvh;
}

/* -- Menu drawer body: nav group at the top, game actions pinned to the
   bottom of the drawer -- */
.quiz-menu .dialog-body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-l);
}

/* -- Dialog backdrop: no blur (overrides the design-system default) -- */
.dialog::backdrop {
  backdrop-filter: none;
}

/* -- Dialog backdrop fades (GSAP can't reach ::backdrop). Entry via
   @starting-style (progressive enhancement - instant where unsupported);
   exit rides .is-closing, set by closeDialog() while the panel tweens. -- */
@media (prefers-reduced-motion: no-preference) {
  .dialog::backdrop {
    opacity: 1;
    transition: opacity var(--duration-m) var(--ease-out);
  }

  @starting-style {
    .dialog[open]::backdrop {
      opacity: 0;
    }
  }

  .dialog.is-closing::backdrop {
    opacity: 0;
  }
}

/* -- Player edit row: input shares the row with its buttons -- */
.quiz-row input[type="text"] {
  flex: 1;
  min-width: 12rem;
  width: auto;
}

/* -- Question text keeps a headline measure (centered via data-align);
   its own clamp sits under the h1 scale so long questions wrap sanely -- */
.quiz-question-text {
  max-width: var(--line-length-headline);
  font-size: clamp(1.75rem, 1.3rem + 2vw, 3.25rem);
}

/* -- Optional question image: 5:3 crop (no .img-5x3 exists in the design
   system's ratio set) plus rounding -- */
.quiz-question-image {
  aspect-ratio: 5 / 3;
  border-radius: var(--radius-l);
}

/* -- Answer area spans the container so options get the full width -- */
.quiz-answer-area {
  width: 100%;
}

/* -- Answer input width cap (global inputs are 100% of a wide container),
   centered within the full-width answer area -- */
.quiz-answer-block {
  width: 100%;
  max-width: var(--max-width-xs);
  margin-inline: auto;
}

/* -- Letter-box answer cells: the typed answer renders as one cell per
   letter (built by quiz-app.js from the canonical answer's shape). The real
   #answer-input stays focusable but invisible, laid over the block so the
   caret's geometry stays near the cells (iOS scroll/zoom behaviour); the
   cells row is a <label> for it, so tapping the row focuses the input.
   Numeric answers keep the plain visible input - no digit count leaks. -- */
#text-answer-block {
  position: relative;
}

#text-answer-block[data-answer-mode="letters"] #answer-input {
  position: absolute;
  inset: 0;
  opacity: 0;
  border: 0;
  pointer-events: none;
}

#text-answer-block[data-answer-mode="numeric"] .quiz-letter-cells {
  display: none;
}

.quiz-letter-cells {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-s);
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  cursor: text;
}

/* Words wrap as units; a word longer than the row wraps within itself */
.quiz-letter-word {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
}

.quiz-letter-cell {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2.5rem;
  background: var(--background-faded);
  border: var(--border-s) solid var(--border-faded);
  border-radius: var(--radius-s);
  color: var(--text-primary);
  font-weight: var(--font-weight-semi-bold);
  text-transform: uppercase;
}

@media (prefers-reduced-motion: no-preference) {
  .quiz-letter-cell {
    transition:
      border-color var(--duration-xs) var(--ease-out),
      background-color var(--duration-xs) var(--ease-out);
  }
}

/* The next empty cell reads as the caret - only while the input has
   focus. The invisible input has no ring of its own, so this caret IS
   the visible focus indicator (it stays on the last cell when the row
   is full - see paintCells in quiz-app.js). */
#text-answer-block:focus-within .quiz-letter-cell.is-active {
  border-color: var(--input-focus);
}

/* Pre-revealed punctuation (hyphens, apostrophes, dots) - shown, not typed */
.quiz-letter-cell[data-cell="separator"] {
  width: 1.25rem;
  background: none;
  border-color: transparent;
  color: var(--text-faded);
}

/* Long answers get smaller cells (set by the renderer from the pattern) */
.quiz-letter-cells[data-size="small"] .quiz-letter-cell {
  width: 1.5rem;
  height: 2rem;
  font-size: var(--text-size-s);
}

/* Verdict states colour the letter cells; separators stay quiet */
.quiz-letter-cells[data-color="success"] .quiz-letter-cell[data-cell="letter"] {
  background: var(--status-success-bg);
  border-color: var(--status-success);
  color: var(--status-success);
}

.quiz-letter-cells[data-color="danger"] .quiz-letter-cell[data-cell="letter"] {
  background: var(--status-danger-bg);
  border-color: var(--status-danger);
  color: var(--status-danger);
}

/* -- Question browser (Settings → Question browser dialog): a full-page
   surface, not a floating panel - it fills the viewport and the motion
   layer slides it up over the app like opening a book page (the app
   recedes behind it; see dialogOpen/dialogClose in quiz-motion.js).
   Content keeps a readable measure inside the full-bleed surface. -- */
.dialog.quiz-browser-dialog {
  inset: 0;
  width: 100%;
  max-width: none;
  height: 100dvh;
  max-height: none;
  margin: 0;
  box-shadow: none;
  /* Full-bleed + viewport-fit=cover: keep content clear of the notch
     and home indicator (same recipe as .quiz-sticky-footer) */
  padding-right: calc(var(--space-xl) + env(safe-area-inset-right));
  padding-bottom: calc(var(--space-xl) + env(safe-area-inset-bottom));
  padding-left: calc(var(--space-xl) + env(safe-area-inset-left));
}

.quiz-browser-dialog .dialog-header,
.quiz-browser-dialog .dialog-body > .block {
  width: 100%;
  max-width: var(--max-width-l);
  margin-inline: auto;
}

.quiz-browser-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
}

.quiz-browser-filters input[type="search"] {
  flex: 1;
  min-width: 12rem;
  width: auto;
}

.quiz-browser-filters select {
  width: auto;
}

.quiz-browser-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.quiz-browser-image {
  max-width: 12rem;
  border-radius: var(--radius-m);
}

.quiz-browser-options {
  margin: 0;
  padding-inline-start: var(--space-xl);
  color: var(--text-secondary);
}

.quiz-browser-options .is-active {
  color: var(--text-primary);
  font-weight: var(--font-weight-semi-bold);
}

/* -- Entry curtain: theatre for the load moment. quiz-intro.js arms it
   (.is-active) on every real document load, drops the wordmark in, and
   lifts it (.is-leaving) once fonts and the question bank are ready.
   Without JS (or with reduced motion / no GSAP) it never shows -
   display: none is the resting state and the app is untouched. -- */
.quiz-intro {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100; /* above the fixed header (10); toasts (900) deliberately
                   stay visible over it - a bank-load error must be seen */
  align-items: center;
  justify-content: center;
  background-color: var(--background-primary);
}

.quiz-intro.is-active {
  display: flex;
}

.quiz-intro.is-leaving {
  opacity: 0;
  transition: opacity var(--duration-m) var(--ease-in-out);
}

/* Screen-reader announcement for the curtain (the design system ships
   no visually-hidden utility; standard clip recipe, scoped here) */
.quiz-intro-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The wordmark ("Quiz" until the SVG logo lands): letters are built and
   spring-dropped in by quiz-intro.js */
.quiz-intro-stage {
  display: flex;
  color: var(--text-primary);
  font-size: clamp(3.5rem, 14vw, 8rem);
  font-weight: var(--font-weight-bold);
  line-height: 1.1;
}

.quiz-intro-letter {
  display: inline-block;
  will-change: transform;
}

/* -- Multiple-choice option buttons -- */
.quiz-option-grid {
  width: 100%;
}

/* Full-width cells (1 col on mobile, the 2-col grid on desktop) with
   centered text; the state icon is pinned to the right edge, and equal
   inline padding keeps the text optically centered clear of it */
.quiz-option.button {
  width: 100%;
  min-height: 4.5rem;
  position: relative;
  justify-content: center;
  text-align: center;
  padding-inline: var(--space-2xl);
}

.quiz-option.button .svg-icn {
  position: absolute;
  left: var(--space-m);
  top: 50%;
  translate: 0 -50%;
}

/* -- Styleguide only: show an open dialog in normal flow (the browser
   positions non-modal open dialogs absolutely, overlaying content) -- */
.quiz-dialog-demo[open] {
  position: static;
}

/* -- Styleguide only: fixed header, sticky footer, and full-height drawer
   shown in normal flow -- */
.quiz-header-demo,
.quiz-footer-demo {
  position: static;
}

.quiz-menu.quiz-dialog-demo[open] {
  height: auto;
  max-height: none;
}

/* -- Leaderboard stat lines align right of the row on wide screens -- */
.quiz-stats {
  text-align: right;
}

@media (max-width: 768px) {
  .quiz-stats {
    text-align: left;
  }
}

/* ─────────────────────────────
   DISPLAY MODE (HOST MODE AUDIENCE VIEW)
   ─────────────────────────────

   index.html?mode=display is the host-mode audience mirror: the same
   markup rendered from synced state with no listeners wired
   (quiz-app.js). Everything interactive hides by the app's own
   convention - every control carries data-action - so future controls
   hide themselves with zero extra work. The two :not()s are the
   audience-visible exceptions: MC options are content, and the
   leaderboard sort is an inert label of the active sort.
   ───────────────────────────── */

[data-mode="display"] [data-action]:not(.quiz-option):not(.segmented-control-btn) {
  display: none !important;
}

/* -- Non-button chrome: header (incl. the class-driven dark-mode
   toggle), the lifeline bar, the answer-mode fork's container (its
   buttons hide via the rule above, but the empty block would still
   claim a flex-gap slot in the answer area), the typed-answer surface,
   setup-only sections, and the sticky footers. -- */
[data-mode="display"] .quiz-header,
[data-mode="display"] .quiz-lifelines,
[data-mode="display"] .quiz-sticky-footer,
[data-mode="display"] #host-answer-block,
[data-mode="display"] #judge-bar,
[data-mode="display"] #commit-answer-block,
[data-mode="display"] #text-answer-block,
[data-mode="display"] #ask-friend-hint,
[data-mode="display"] #saved-players-section,
[data-mode="display"] #theme-section,
[data-mode="display"] #categories-section {
  display: none !important;
}

/* -- The fixed header is gone, so drop its content offset (same
   specificity as the body:has(> .quiz-header) rule, later wins). -- */
html[data-mode="display"] .page-content {
  padding-top: 0;
}

/* -- Belt and braces: nothing on the audience view is interactive. -- */
[data-mode="display"] body {
  pointer-events: none;
  user-select: none;
}

/* -- TV type pass: the audience reads from across a room. Every font and
   spacing token is rem-based, so one root bump scales the whole view
   coherently (outranks the breakpoint steps above: 0,1,1 vs 0,0,1).
   Per-screen nudges wait for a real TV at viewing distance. -- */
html[data-mode="display"] {
  font-size: 150%; /* 24px at browser default */
}
