/*
 * Daily Quiz — front-end stylesheet.
 *
 * Everything is scoped under `.dqz-app` so a hostile WordPress theme can
 * neither leak into the quiz nor be damaged by it. The only intentionally
 * unscoped selector is `.dqz-noscript`, which lives inside <noscript> and so by
 * definition cannot sit inside the app root; it is namespaced by the `dqz-`
 * prefix like everything else.
 *
 * Four conventions carry the whole file, and every one of them is load-bearing:
 *
 *   1. Every colour and size goes through a design token WITH A LITERAL
 *      FALLBACK: var(--dqz-primary, #3d63ff). The tokens are emitted twice —
 *      server-side by DQZ_Shortcode::inline_css() so there is no flash of
 *      unstyled quiz, and again client-side by applyTheme() in case a cache or
 *      a "combine CSS" plugin stripped the inline block. The fallbacks mean the
 *      quiz still renders correctly when BOTH emissions are missing.
 *   2. Every transition and animation duration is scaled by var(--dqz-motion, 1)
 *      via calc(), so the admin motion multiplier and prefers-reduced-motion
 *      both genuinely work rather than being advertised and ignored.
 *   3. --dqz-shadow-on (0|1) is folded into shadow ALPHA with calc(), so one
 *      switch turns every shadow off without a second set of rules.
 *   4. --dqz-tone is a re-pointable colour channel: a component reads
 *      var(--dqz-tone) and a modifier class re-points it, so one rule set
 *      serves every variant.
 *
 * Mobile-portrait first (~360-430px), with enhancements at 640px, 760px and
 * 900px. No at-rule imports, no external URLs, no fonts, no images — nothing in
 * this file is ever fetched.
 */

/* ===================================================================== */
/* ===== 1. RESET & TOKENS ============================================= */
/* ===================================================================== */

.dqz-app {
  /* Neutral derivations. These are black/white alpha values, not palette
     colours — every palette colour below is referenced through its token. */
  --dqz-space: var(--dqz-spacing, 14px);
  --dqz-space-xs: calc(var(--dqz-spacing, 14px) * 0.35);
  --dqz-space-s: calc(var(--dqz-spacing, 14px) * 0.6);
  --dqz-space-l: calc(var(--dqz-spacing, 14px) * 1.5);
  --dqz-space-xl: calc(var(--dqz-spacing, 14px) * 2.2);
  --dqz-pill-radius: 999px;
  --dqz-ink: rgba(0, 0, 0, 0.55);
  --dqz-ink-soft: rgba(0, 0, 0, 0.28);
  --dqz-ink-strong: rgba(0, 0, 0, 0.72);
  --dqz-hairline: rgba(255, 255, 255, 0.08);
  --dqz-hairline-strong: rgba(255, 255, 255, 0.18);
  --dqz-glass: rgba(255, 255, 255, 0.04);
  --dqz-glass-strong: rgba(255, 255, 255, 0.09);
  --dqz-tap: rgba(255, 255, 255, 0.05);
  /* Shadows fold in the --dqz-shadow-on 0|1 switch through the alpha channel. */
  --dqz-shadow-1: 0 1px 1px rgba(0, 0, 0, calc(0.3 * var(--dqz-shadow-on, 1))),
    0 6px 16px rgba(0, 0, 0, calc(0.26 * var(--dqz-shadow-on, 1)));
  --dqz-shadow-2: 0 10px 28px rgba(0, 0, 0, calc(0.42 * var(--dqz-shadow-on, 1)));
  --dqz-shadow-3: 0 18px 52px rgba(0, 0, 0, calc(0.55 * var(--dqz-shadow-on, 1)));
  --dqz-shadow-inset: inset 0 1px 0 rgba(255, 255, 255, calc(0.08 * var(--dqz-shadow-on, 1)));

  /* Paper values for the print section, kept here so they are findable. */
  --dqz-print-paper: #ffffff;
  --dqz-print-ink: #000000;
  --dqz-print-ink-soft: #444444;
  --dqz-print-rule: #999999;

  /* Zone/tone channel, re-pointed by modifier classes further down. */
  --dqz-tone: var(--dqz-primary, #3d63ff);

  position: relative;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  background: var(--dqz-grad-background, linear-gradient(160deg, #0a0e1c 0%, #141c3d 100%));
  background-color: var(--dqz-bg, #0a0e1c);
  color: var(--dqz-text, #f2f5ff);
  font-family: var(--dqz-font, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
  font-size: var(--dqz-body-size, 16px);
  line-height: 1.45;
  text-align: left;
  letter-spacing: 0;
  /*
   * `clip`, NOT `hidden`, AND THE DIFFERENCE IS THE STICKY HEADER.
   *
   * Both stop sideways scrolling. Only `hidden` makes this element a SCROLL
   * CONTAINER — per CSS Overflow 3, one axis `hidden` with the other `visible`
   * computes the visible axis to `auto` — and once it is one, `.dqz-topbar`'s
   * `position: sticky` sticks to THIS scrollport rather than the viewport.
   * `.dqz-app` is sized by its content and never scrolls internally, so the
   * header had nothing to stick to and scrolled away with the page.
   *
   * That was survivable while the header carried a brand and an account
   * button. It stopped being survivable when it became the way back: a Home
   * control reachable only above the fold is no use at the bottom of a
   * fifty-row leaderboard, which is the dead end it was added to close.
   *
   * `clip` creates no scroll container, so the header sticks and the
   * no-sideways-scroll guarantee is unchanged.
   */
  overflow-x: clip;
  overscroll-behavior-x: none;
  -webkit-tap-highlight-color: var(--dqz-tap);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

.dqz-app *,
.dqz-app *::before,
.dqz-app *::after {
  box-sizing: border-box;
}

.dqz-app h1,
.dqz-app h2,
.dqz-app h3,
.dqz-app h4,
.dqz-app h5,
.dqz-app h6,
.dqz-app p,
.dqz-app figure,
.dqz-app blockquote,
.dqz-app dl,
.dqz-app dd {
  margin: 0;
  padding: 0;
  font-family: inherit;
  line-height: 1.3;
  letter-spacing: inherit;
  text-transform: none;
}

/*
 * COLOUR, WHICH THIS RESET FORGOT AND WHICH IS THE ONE THAT SHOWS.
 *
 * An element with no colour declaration of its own takes its parent's by
 * inheritance, and an inherited value loses to ANY matched declaration whatever
 * its specificity — so a theme carrying nothing more than `p { color: #1a1a1a }`
 * beat `.dqz-modal__panel { color: var(--dqz-text) }` and painted black text on a
 * dark panel. It was reported as one line in the profile ("You are #1 of 1") and
 * was thirty-two of them: every bare p, span, li, th and strong the modules emit,
 * the confirm dialog's message and the leaderboard's explainer among them.
 *
 * `inherit`, NOT `var(--dqz-text)`. A span inside a luminous button takes
 * `var(--dqz-bg)` as its ink from the button; hard-coding the body colour here
 * would paint every one of those labels white on white. This defeats the theme and
 * leaves the cascade otherwise untouched.
 *
 * Specificity stays at (0,1,1) on purpose. `.dqz-app .dqz-muted` is (0,2,0) and
 * must keep winning, as must every pill, badge and button rule. Doubling the root
 * class would beat this stylesheet's own classes — a worse bug than the one being
 * fixed.
 *
 * Form controls are deliberately absent: `input`, `select`, `textarea` and
 * `button` have their own colour rules against their own backgrounds, and
 * `inherit` on a field would put body text on a field surface.
 */
.dqz-app h1,
.dqz-app h2,
.dqz-app h3,
.dqz-app h4,
.dqz-app h5,
.dqz-app h6,
.dqz-app p,
.dqz-app span,
.dqz-app strong,
.dqz-app em,
.dqz-app small,
.dqz-app li,
.dqz-app dt,
.dqz-app dd,
.dqz-app td,
.dqz-app th,
.dqz-app label,
.dqz-app figcaption,
.dqz-app blockquote {
  color: inherit;
}

.dqz-app p {
  line-height: 1.5;
}

.dqz-app ul,
.dqz-app ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

.dqz-app hr {
  height: 1px;
  margin: 0;
  border: 0;
  background: var(--dqz-border, #2d3767);
}

.dqz-app img,
.dqz-app canvas,
.dqz-app svg {
  max-width: 100%;
}

.dqz-app img {
  height: auto;
  border: 0;
  display: block;
}

.dqz-app button,
.dqz-app input,
.dqz-app select,
.dqz-app textarea {
  margin: 0;
  font: inherit;
  font-family: inherit;
  line-height: 1.3;
  color: inherit;
  letter-spacing: inherit;
  text-transform: none;
}

.dqz-app button {
  display: inline-block;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  text-align: inherit;
  text-decoration: none;
  text-shadow: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  touch-action: manipulation;
}

.dqz-app a {
  color: var(--dqz-accent, #00e0b8);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.dqz-app a:hover,
.dqz-app a:focus {
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app table {
  border-collapse: collapse;
}

.dqz-app details {
  display: block;
}

.dqz-app summary {
  display: block;
  cursor: pointer;
  list-style: none;
}

.dqz-app summary::-webkit-details-marker {
  display: none;
}

/* --- focus -------------------------------------------------------------- */

.dqz-app :focus {
  outline: none;
}

.dqz-app :focus-visible,
.dqz-app button:focus-visible,
.dqz-app a:focus-visible,
.dqz-app input:focus-visible,
.dqz-app select:focus-visible,
.dqz-app textarea:focus-visible,
.dqz-app [tabindex]:focus-visible {
  outline: 3px solid var(--dqz-accent, #00e0b8);
  outline-offset: 2px;
  border-radius: var(--dqz-radius-small, 8px);
}

/* Fallback for engines without :focus-visible (keyboard + programmatic focus). */
.dqz-app button:focus:not(:focus-visible) {
  outline: none;
}

/* --- utilities ---------------------------------------------------------- */

.dqz-sr,
.dqz-app .dqz-sr {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.dqz-app .dqz-icon {
  display: block;
  flex: none;
  width: auto;
  height: auto;
  color: inherit;
  fill: none;
  vertical-align: middle;
  pointer-events: none;
}

.dqz-app .dqz-center {
  text-align: center;
}

/* Thin, token-coloured scrollbars on every internal scroller. */
.dqz-app .dqz-modal__body {
  scrollbar-width: thin;
  scrollbar-color: var(--dqz-border, #2d3767) transparent;
}

.dqz-app .dqz-modal__body::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.dqz-app .dqz-modal__body::-webkit-scrollbar-track {
  background: var(--dqz-glass);
  border-radius: var(--dqz-pill-radius);
}

.dqz-app .dqz-modal__body::-webkit-scrollbar-thumb {
  background: var(--dqz-border, #2d3767);
  border-radius: var(--dqz-pill-radius);
}

/* ===================================================================== */
/* ===== 2. TYPOGRAPHY ================================================= */
/* ===================================================================== */

.dqz-app .dqz-h1 {
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-h1-size, 30px);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--dqz-text, #f2f5ff);
  text-wrap: balance;
}

.dqz-app .dqz-h2 {
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-h2-size, 21px);
  font-weight: 750;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-eyebrow {
  margin: 0 0 2px;
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dqz-accent, #00e0b8);
}

.dqz-app .dqz-muted {
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-small {
  font-size: var(--dqz-small-size, 13px);
  line-height: 1.4;
}

.dqz-app .dqz-card__title {
  margin: 0 0 var(--dqz-space-s);
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-body-size, 16px) * 1.05);
  font-weight: 750;
  letter-spacing: 0.01em;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app h4 {
  font-size: var(--dqz-body-size, 16px);
  font-weight: 700;
}

/* ===================================================================== */
/* ===== 3. LAYOUT / SHELL ============================================= */
/* ===================================================================== */

.dqz-app .dqz-shell {
  display: block;
  width: 100%;
  max-width: var(--dqz-max-width, 560px);
  margin: 0 auto;
  padding: 0 var(--dqz-space) var(--dqz-space-xl);
}

.dqz-app .dqz-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: block;
  margin: 0 calc(-1 * var(--dqz-space));
  padding: var(--dqz-space-xs) var(--dqz-space);
  background: var(--dqz-grad-header, linear-gradient(100deg, #1b2a6b 0%, #3d63ff 100%));
  border-bottom: 1px solid var(--dqz-hairline);
  box-shadow: var(--dqz-shadow-1);
}

.dqz-app .dqz-topbar__main {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dqz-space-s);
  min-height: 48px;
}

.dqz-app .dqz-topbar__brand {
  min-width: 0;
  overflow: hidden;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-small-size, 13px) * 1.08);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--dqz-primary-text, #ffffff);
  text-shadow: 0 1px 2px var(--dqz-ink-soft);
}

/*
 * WRAPS, SINCE THERE ARE THREE OF THEM. `flex: none` sizes this row at its
 * max-content width and never lets it shrink, and the brand beside it is
 * already ellipsising to zero — so a third control simply ran past the right
 * edge, where `.dqz-app`'s `overflow-x: hidden` clipped the account button.
 * Keeping the page from scrolling sideways by cutting a control off is not
 * keeping the rule. `.dqz-footer` below has carried `flex-wrap` from the
 * start; this row is the one that did not, and the profile button is what
 * made it matter.
 */
.dqz-app .dqz-topbar__actions {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: var(--dqz-space-xs);
}

/* An empty action row must not reserve a gap of its own. */
.dqz-app .dqz-topbar__actions:empty {
  display: none;
}

/* The "Presented by" lockup: its own band across the top of the header, because
   sharing a line with the brand and the controls never left it enough room. */
.dqz-app .dqz-presented {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px 0 2px;
  border-bottom: 1px solid var(--dqz-hairline);
  opacity: 0.95;
  /* Sponsor creatives are static and NEVER clickable — the About/privacy body
     says so in as many words, so the stylesheet has to keep the promise even if
     a theme filter ever wraps the image in a link. */
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

.dqz-app .dqz-presented__img {
  width: auto;
  max-width: min(70%, 260px);
  max-height: 30px;
  height: auto;
  margin: 0 auto;
  object-fit: contain;
}

.dqz-app .dqz-topbar.has-presented .dqz-topbar__main {
  min-height: 44px;
}

.dqz-app .dqz-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--dqz-space-s);
  margin-top: var(--dqz-space-l);
  padding-top: var(--dqz-space);
  border-top: 1px solid var(--dqz-border, #2d3767);
}

/* No footer controls yet in this build: an empty bar with a rule above it reads
   as a broken layout rather than as an absence. */
.dqz-app .dqz-footer:empty {
  display: none;
}

.dqz-app .dqz-screen {
  display: block;
  padding-top: var(--dqz-space);
}

.dqz-app .dqz-screen:not(.is-active) {
  display: none;
}

.dqz-app .dqz-screen__inner {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space);
}

/* scrollIntoView targets must clear the sticky top bar. */
.dqz-app .dqz-anchor {
  scroll-margin-top: 72px;
  scroll-margin-bottom: var(--dqz-space-l);
}

/* The server-rendered boot state, replaced the moment the module runs. */
.dqz-app .dqz-boot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--dqz-space-s);
  min-height: 180px;
  padding: var(--dqz-space-xl) var(--dqz-space);
  color: var(--dqz-text-muted, #9aa6d0);
  font-size: var(--dqz-small-size, 13px);
  letter-spacing: 0.1em;
  text-align: center;
  text-transform: uppercase;
}

.dqz-app .dqz-boot::before {
  content: "";
  width: 18px;
  height: 18px;
  border: 2px solid var(--dqz-border, #2d3767);
  border-top-color: var(--dqz-accent, #00e0b8);
  border-radius: 50%;
  animation: dqz-spin calc(900ms * var(--dqz-motion, 1)) linear infinite;
}

.dqz-app .dqz-spinner {
  display: inline-block;
  flex: none;
  width: 20px;
  height: 20px;
  border: 2px solid var(--dqz-border, #2d3767);
  border-top-color: var(--dqz-accent, #00e0b8);
  border-radius: 50%;
  animation: dqz-spin calc(800ms * var(--dqz-motion, 1)) linear infinite;
}

/* Lives outside .dqz-app (inside <noscript>), so it cannot be scoped. */
.dqz-noscript {
  margin: 0;
  padding: 16px;
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: var(--dqz-radius, 14px);
  background: var(--dqz-surface, #131a33);
  color: var(--dqz-text, #f2f5ff);
  font-size: var(--dqz-small-size, 13px);
  text-align: center;
}

/* --- primitives --------------------------------------------------------- */

.dqz-app .dqz-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dqz-space-s);
}

.dqz-app .dqz-row--between {
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: flex-start;
}

.dqz-app .dqz-row--between > *:first-child {
  min-width: 0;
}

.dqz-app .dqz-col {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-xs);
  min-width: 0;
}

.dqz-app .dqz-stack {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
}

.dqz-app .dqz-actions {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
}

.dqz-app .dqz-divider {
  height: 1px;
  margin: var(--dqz-space-s) 0;
  border: 0;
  background: linear-gradient(90deg, transparent, var(--dqz-border, #2d3767), transparent);
}

/* ===================================================================== */
/* ===== 4. CARDS ====================================================== */
/* ===================================================================== */

.dqz-app .dqz-card {
  --dqz-card-pad: var(--dqz-space);
  position: relative;
  display: block;
  padding: var(--dqz-card-pad);
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: var(--dqz-radius, 14px);
  background-color: var(--dqz-surface, #131a33);
  background-image: linear-gradient(180deg, var(--dqz-glass), transparent 60%);
  box-shadow: var(--dqz-shadow-1), var(--dqz-shadow-inset);
}

/* Cards set no colour of their own, so their text would be INHERITED from
   .dqz-app — and an inherited value loses to any theme rule that targets the
   element directly, no matter how specific the ancestor selector is. A theme
   with `.entry-content p { color: #333 }` therefore prints near-black body text
   on the dark card. So every text element a card can contain gets its colour
   stated outright rather than inherited. (Inherited from the parent plugin,
   where this was diagnosed the hard way.) */
.dqz-app .dqz-card p,
.dqz-app .dqz-card li,
.dqz-app .dqz-card dd,
.dqz-app .dqz-card dt,
.dqz-app .dqz-card span,
.dqz-app .dqz-card strong,
.dqz-app .dqz-card em,
.dqz-app .dqz-card b,
.dqz-app .dqz-card h2,
.dqz-app .dqz-card h3,
.dqz-app .dqz-card h4 {
  color: var(--dqz-text, #f2f5ff);
}

/* Muted and accent text inside a card must still win over the blanket rule. */
.dqz-app .dqz-card .dqz-muted,
.dqz-app .dqz-card .dqz-small.dqz-muted {
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-card--tight {
  --dqz-card-pad: var(--dqz-space-s);
}

.dqz-app .dqz-card--warn {
  border-color: var(--dqz-warn, #f5a623);
  background-image: linear-gradient(180deg, var(--dqz-glass-strong), transparent 70%);
}

.dqz-app .dqz-card--warn::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  border-radius: var(--dqz-radius, 14px) 0 0 var(--dqz-radius, 14px);
  background: var(--dqz-warn, #f5a623);
}

.dqz-app .dqz-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
}

.dqz-app .dqz-card > * + * {
  margin-top: var(--dqz-space-s);
}

.dqz-app .dqz-card > .dqz-card__title + * {
  margin-top: 0;
}

/* <details> cards: "How it works", and the per-question detail later on. */
.dqz-app .dqz-card > summary,
.dqz-app .dqz-howto > summary {
  position: relative;
  padding-right: 28px;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-body-size, 16px) * 1.02);
  font-weight: 750;
  color: var(--dqz-text, #f2f5ff);
  transition: color calc(140ms * var(--dqz-motion, 1)) ease;
}

.dqz-app .dqz-card > summary:hover {
  color: var(--dqz-accent, #00e0b8);
}

.dqz-app .dqz-card > summary::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 4px;
  width: 8px;
  height: 8px;
  margin-top: -6px;
  border-right: 2px solid var(--dqz-text-muted, #9aa6d0);
  border-bottom: 2px solid var(--dqz-text-muted, #9aa6d0);
  transform: rotate(45deg);
  transform-origin: 50% 50%;
  transition: transform calc(180ms * var(--dqz-motion, 1)) ease;
}

.dqz-app .dqz-card[open] > summary::after {
  transform: rotate(-135deg);
}

.dqz-app .dqz-card[open] > summary {
  margin-bottom: var(--dqz-space-s);
}

/* ===================================================================== */
/* ===== 5. BUTTONS & FORMS ============================================ */
/* ===================================================================== */

.dqz-app .dqz-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--dqz-space-s);
  min-width: 44px;
  min-height: 44px;
  height: var(--dqz-button-height, 52px);
  padding: 0 var(--dqz-button-padding-x, 22px);
  border: 1px solid transparent;
  border-radius: var(--dqz-button-radius, 12px);
  background-color: var(--dqz-surface-alt, #1c2447);
  color: var(--dqz-text, #f2f5ff);
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-button-font-size, 17px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  box-shadow: var(--dqz-shadow-1);
  transition: transform calc(140ms * var(--dqz-motion, 1)) cubic-bezier(0.2, 0.8, 0.2, 1),
    background-color calc(160ms * var(--dqz-motion, 1)) ease,
    border-color calc(160ms * var(--dqz-motion, 1)) ease,
    box-shadow calc(160ms * var(--dqz-motion, 1)) ease,
    opacity calc(160ms * var(--dqz-motion, 1)) ease;
}

.dqz-app .dqz-btn > span {
  min-width: 0;
}

.dqz-app .dqz-btn:hover {
  background-color: var(--dqz-surface, #131a33);
  border-color: var(--dqz-hairline-strong);
}

.dqz-app .dqz-btn:active {
  transform: scale(0.965);
  box-shadow: none;
}

.dqz-app .dqz-btn[disabled],
.dqz-app .dqz-btn:disabled {
  /* Dimmed but still readable — never invisible. */
  opacity: 0.62;
  filter: saturate(0.5);
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.dqz-app .dqz-btn--primary {
  border-color: transparent;
  background-image: var(--dqz-grad-button, linear-gradient(120deg, #3d63ff 0%, #00b7ff 100%));
  background-color: var(--dqz-primary, #3d63ff);
  color: var(--dqz-primary-text, #ffffff);
  box-shadow: var(--dqz-shadow-2), var(--dqz-shadow-inset);
}

.dqz-app .dqz-btn--primary:hover {
  background-color: var(--dqz-primary, #3d63ff);
  filter: brightness(1.07);
}

.dqz-app .dqz-btn--ghost {
  background-color: transparent;
  background-image: none;
  border-color: var(--dqz-border, #2d3767);
  color: var(--dqz-text, #f2f5ff);
  font-weight: 650;
  box-shadow: none;
}

.dqz-app .dqz-btn--ghost:hover {
  border-color: var(--dqz-accent, #00e0b8);
  background-color: var(--dqz-glass);
  color: var(--dqz-text, #f2f5ff);
}

/* Between primary and ghost: the real way forward, but not the headline one. */
.dqz-app .dqz-btn--secondary {
  background-color: transparent;
  background-image: none;
  border-color: var(--dqz-accent, #00e0b8);
  border-width: 2px;
  color: var(--dqz-accent, #00e0b8);
  font-weight: 800;
  box-shadow: none;
}

.dqz-app .dqz-btn--secondary:hover:not([disabled]),
.dqz-app .dqz-btn--secondary:focus-visible {
  background-color: var(--dqz-glass-strong);
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-btn--danger {
  background-image: none;
  background-color: var(--dqz-bad, #c93a4d);
  border-color: var(--dqz-bad, #c93a4d);
  color: var(--dqz-primary-text, #ffffff);
}

.dqz-app .dqz-btn--danger:hover {
  background-color: var(--dqz-bad, #c93a4d);
  filter: brightness(1.1);
}

.dqz-app .dqz-btn--block {
  display: flex;
  width: 100%;
}

.dqz-app .dqz-btn--small {
  height: 40px;
  min-height: 40px;
  padding: 0 calc(var(--dqz-button-padding-x, 22px) * 0.6);
  border-radius: calc(var(--dqz-button-radius, 12px) * 0.8);
  font-size: var(--dqz-small-size, 13px);
  gap: var(--dqz-space-xs);
}

.dqz-app .dqz-btn--icon {
  width: var(--dqz-button-height, 52px);
  height: var(--dqz-button-height, 52px);
  padding: 0;
  gap: 0;
  border-radius: 50%;
  background-color: var(--dqz-glass-strong);
  border-color: var(--dqz-hairline);
}

.dqz-app .dqz-topbar .dqz-btn--icon {
  width: 44px;
  height: 44px;
  min-height: 44px;
}

/* Icon-only buttons keep an accessible label; it is just never seen. */
.dqz-app .dqz-btn--icon .dqz-btn__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.dqz-app .dqz-btn--link {
  height: auto;
  min-height: 44px;
  padding: var(--dqz-space-xs) 0;
  border: 0;
  border-radius: var(--dqz-radius-small, 8px);
  background: none;
  box-shadow: none;
  color: var(--dqz-accent, #00e0b8);
  font-size: var(--dqz-body-size, 16px);
  font-weight: 650;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.dqz-app .dqz-btn--link:hover {
  background: none;
  border-color: transparent;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-btn--link:active {
  transform: none;
}

.dqz-app .dqz-btn__label {
  font-weight: inherit;
}

.dqz-app .dqz-btn__count {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--dqz-pill-radius);
  background: var(--dqz-glass-strong);
  color: var(--dqz-text-muted, #9aa6d0);
  font-size: calc(var(--dqz-small-size, 13px) * 0.92);
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* --- fields ------------------------------------------------------------- */

.dqz-app .dqz-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.dqz-app .dqz-field--row {
  flex-direction: column;
  gap: var(--dqz-space-xs);
}

.dqz-app .dqz-field__label {
  display: block;
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-field__input,
.dqz-app .dqz-select {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: var(--dqz-radius-small, 8px);
  background-color: var(--dqz-bg, #0a0e1c);
  color: var(--dqz-text, #f2f5ff);
  font-size: var(--dqz-body-size, 16px);
  line-height: 1.3;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: inset 0 1px 2px var(--dqz-ink-soft);
  transition: border-color calc(160ms * var(--dqz-motion, 1)) ease,
    box-shadow calc(160ms * var(--dqz-motion, 1)) ease;
}

.dqz-app .dqz-field__input::placeholder {
  color: var(--dqz-text-muted, #9aa6d0);
  opacity: 0.75;
}

.dqz-app .dqz-field__input:hover,
.dqz-app .dqz-select:hover {
  border-color: var(--dqz-hairline-strong);
}

.dqz-app .dqz-field__input:focus,
.dqz-app .dqz-select:focus {
  border-color: var(--dqz-primary, #3d63ff);
  box-shadow: inset 0 1px 2px var(--dqz-ink-soft), 0 0 0 3px var(--dqz-glass-strong);
}

.dqz-app .dqz-field__input[disabled],
.dqz-app .dqz-select[disabled] {
  opacity: 0.65;
  cursor: not-allowed;
}

.dqz-app .dqz-select {
  padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, var(--dqz-text-muted, #9aa6d0) 50%),
    linear-gradient(135deg, var(--dqz-text-muted, #9aa6d0) 50%, transparent 50%);
  background-position: right 18px center, right 12px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

.dqz-app .dqz-field__help {
  margin: 0;
  color: var(--dqz-text-muted, #9aa6d0);
  font-size: var(--dqz-small-size, 13px);
  line-height: 1.4;
}

.dqz-app .dqz-field__help:empty,
.dqz-app .dqz-field__error:empty {
  display: none;
}

.dqz-app .dqz-field__error {
  margin: 0;
  color: var(--dqz-bad, #c93a4d);
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  line-height: 1.4;
}

/* --- checkboxes --------------------------------------------------------- */

.dqz-app .dqz-check {
  display: flex;
  align-items: center;
  gap: var(--dqz-space-s);
  min-height: 44px;
  padding: 6px 8px;
  border-radius: var(--dqz-radius-small, 8px);
  cursor: pointer;
  transition: background-color calc(140ms * var(--dqz-motion, 1)) ease;
}

.dqz-app .dqz-check:hover {
  background-color: var(--dqz-glass);
}

/* A real checkbox, kept native and simply sized + tinted. */
.dqz-app .dqz-check__box {
  flex: none;
  width: 22px;
  height: 22px;
  margin: 0;
  accent-color: var(--dqz-primary, #3d63ff);
  cursor: pointer;
}

.dqz-app .dqz-check__box:focus-visible {
  outline: 3px solid var(--dqz-accent, #00e0b8);
  outline-offset: 3px;
}

.dqz-app .dqz-check__label {
  font-size: var(--dqz-small-size, 13px);
  line-height: 1.4;
  color: var(--dqz-text, #f2f5ff);
}

/* --- badges & pills ----------------------------------------------------- */

/*
 * These two are the worked example of the --dqz-tone channel: the component
 * reads var(--dqz-tone) for its border, its text and the alpha wash behind it,
 * and a modifier class re-points the channel. One rule set, every variant — and
 * a new tone is three lines rather than a new copy of the component.
 */

.dqz-app .dqz-badge {
  --dqz-tone: var(--dqz-text-muted, #9aa6d0);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--dqz-tone);
  border-radius: var(--dqz-pill-radius);
  color: var(--dqz-tone);
  font-size: calc(var(--dqz-small-size, 13px) * 0.9);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
}

.dqz-app .dqz-badge::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: var(--dqz-tone);
  opacity: 0.12;
  pointer-events: none;
}

.dqz-app .dqz-badge > * {
  position: relative;
}

.dqz-app .dqz-pill {
  --dqz-tone: var(--dqz-text-muted, #9aa6d0);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 30px;
  padding: 3px 8px;
  border: 1px solid var(--dqz-tone);
  border-radius: var(--dqz-radius-small, 8px);
  color: var(--dqz-tone);
  font-size: calc(var(--dqz-small-size, 13px) * 0.95);
  font-weight: 900;
  line-height: 1.2;
  letter-spacing: 0.06em;
  overflow: hidden;
}

.dqz-app .dqz-pill::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: var(--dqz-tone);
  opacity: 0.14;
  pointer-events: none;
}

.dqz-app .dqz-pill > * {
  position: relative;
}

/* Rank tones. */
.dqz-app .dqz-badge--gold,
.dqz-app .dqz-pill--gold {
  --dqz-tone: var(--dqz-gold, #ffd34d);
}

.dqz-app .dqz-badge--silver,
.dqz-app .dqz-pill--silver {
  --dqz-tone: var(--dqz-silver, #cfd6e6);
}

.dqz-app .dqz-badge--bronze,
.dqz-app .dqz-pill--bronze {
  --dqz-tone: var(--dqz-bronze, #d08b4a);
}

/* Chrome semantics — the quieter pair, for board tints and framing. */
.dqz-app .dqz-badge--good,
.dqz-app .dqz-pill--good {
  --dqz-tone: var(--dqz-good, #2e9e5b);
}

.dqz-app .dqz-badge--bad,
.dqz-app .dqz-pill--bad {
  --dqz-tone: var(--dqz-bad, #c93a4d);
}

/* A streak is orange, never gold: gold means board rank 1, and a player at rank
   40 with a hot streak must not be shown the rank-1 colour. */
.dqz-app .dqz-badge--streak,
.dqz-app .dqz-pill--streak {
  --dqz-tone: var(--dqz-streak, #ff8c42);
}

/* ===================================================================== */
/* ===== 6. RESERVED AD / SPONSOR SLOT ================================= */
/* ===================================================================== */

/*
 * THE ANTI-REFLOW GUARANTEE, and the three rules it rests on:
 *
 * 1. The wrapper always renders, but it only KEEPS ITS HEIGHT when something
 *    could still arrive to fill it — which means `ad` mode and nothing else.
 *    Third-party ad code is injected after first paint, so the space has to be
 *    held or the page jumps when it lands. A sponsor creative cannot arrive
 *    late: it is static and self-hosted, so it is either in the config at
 *    render time or it does not exist. Holding 250 pixels open for one that is
 *    never coming buys no guarantee and costs a visible gap, so every non-ad
 *    mode collapses — see the `is-empty` rule below.
 *
 *    The wrapper itself still renders in all cases, because `app.slot()` is
 *    appended unconditionally by the screens and returning nothing would mean
 *    every caller needed a null check.
 * 2. Height comes from a TOKEN, never from content: an explicit `height` plus
 *    `contain: layout size`, so the box's own size cannot be influenced by what
 *    lands inside it. The creative is absolutely positioned inside
 *    `overflow: hidden`, so a tag that renders 250px into a 50px slot CLIPS —
 *    it cannot push. A clipped ad is an acceptable price; a page that jumps
 *    under a tapping finger is not.
 * 3. The persistent banner is mounted in the shell, not in the stage (see
 *    app.js), because the router clears the stage on every navigation and a
 *    banner inside it would be re-created and re-requested every time.
 *
 * `contain: size` means the element has no intrinsic size of its own, so the
 * explicit height below is not optional — remove it and the box collapses.
 */

.dqz-app .dqz-slot {
  position: relative;
  display: block;
  width: 100%;
  height: var(--dqz-slot-banner-height, 50px);
  margin: var(--dqz-space-s) 0 0;
  overflow: hidden;
  contain: layout size;
  border: 1px solid var(--dqz-hairline);
  border-radius: var(--dqz-radius-small, 8px);
  background-color: var(--dqz-glass);
}

/* Empty: always undecorated. */
.dqz-app .dqz-slot.is-empty {
  border-color: transparent;
  background-color: transparent;
}

/*
 * Empty, and nothing can arrive later: collapse it completely.
 *
 * The selector is the whole argument. `ad` mode is excluded because that is the
 * one mode where a creative appears after first paint, and reserving its height
 * is what stops the page moving under a finger when it does. Every other mode —
 * `sponsor` with no image uploaded, or `off` — is fully resolved at render time,
 * so an empty box there is just a gap.
 *
 * `height: 0` alone is not enough: the base rule carries a top margin, and the
 * banner in the shell carries another, so both have to go or the collapsed slot
 * still occupies space. `contain: layout size` means there is no intrinsic
 * height to fall back to, so zero really is zero.
 *
 * Specificity, since the per-placement heights below look like they should win:
 * this is four classes deep and they are two, so it takes precedence wherever
 * it sits in the file.
 */
.dqz-app .dqz-slot.is-empty:not([data-mode="ad"]) {
  height: 0;
  margin: 0;
  border-width: 0;
}

.dqz-app .dqz-shell > .dqz-slot--banner.is-empty:not([data-mode="ad"]) {
  margin-top: 0;
}

/* Sponsor creatives are static, self-hosted and never clickable. The privacy
   notice says exactly that, so it is enforced here rather than assumed. */
.dqz-app .dqz-slot[data-mode="sponsor"] {
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

.dqz-app .dqz-slot__inner {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Defensive: third-party markup is injected here by ads.js and must not be
   able to widen its container. It can still overflow vertically — and be
   clipped, which is the intended outcome. */
.dqz-app .dqz-slot__inner > * {
  max-width: 100%;
}

.dqz-app .dqz-slot__img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 100%;
  margin: 0 auto;
  object-fit: contain;
  object-position: center;
}

.dqz-app .dqz-slot__label {
  position: absolute;
  top: 1px;
  left: 6px;
  z-index: 2;
  color: var(--dqz-text-muted, #9aa6d0);
  font-size: calc(var(--dqz-small-size, 13px) * 0.72);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.75;
  pointer-events: none;
}

/* Per-placement reserved heights. All four are design tokens, and `home` has had its own since
 * `slotHomeHeight` landed. This said three, with `home` described as twice
 * the banner — which the rule three lines below already contradicted, and
 * which would have led a reader to delete a token that is wired end to end:
 * a default, a sanitiser range, a Design-tab control and a place in the
 * inline CSS.
 */
.dqz-app .dqz-slot--interquestion,
.dqz-app .dqz-slot--board {
  height: var(--dqz-slot-banner-height, 50px);
}

.dqz-app .dqz-slot--home {
  height: var(--dqz-slot-home-height, 100px);
}

.dqz-app .dqz-slot--result {
  height: var(--dqz-slot-result-height, 250px);
}

/* In the shell the banner sits directly under the sticky topbar and spans the
   full shell width. */
.dqz-app .dqz-shell > .dqz-slot--banner {
  margin-top: var(--dqz-space-s);
}

/* ===================================================================== */
/* ===== 7. EMPTY STATE ================================================ */
/* ===================================================================== */

/*
 * The no-quiz-today screen. A real shipped state: the streak is safe, the copy
 * is admin-editable, and there is somewhere to go next. It gets a little visual
 * weight for the same reason — a visitor who lands here has to have a reason to
 * come back tomorrow.
 */

.dqz-app .dqz-empty__hero {
  padding: var(--dqz-space-l) 0 0;
  text-align: center;
}

.dqz-app .dqz-empty__card {
  text-align: center;
  animation: dqz-slide-in calc(280ms * var(--dqz-motion, 1)) cubic-bezier(0.2, 0.8, 0.2, 1) 1;
}

.dqz-app .dqz-empty__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin: 0 auto var(--dqz-space-s);
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: 50%;
  background-color: var(--dqz-surface-alt, #1c2447);
  color: var(--dqz-text-muted, #9aa6d0);
  box-shadow: var(--dqz-shadow-1);
}

.dqz-app .dqz-empty__body {
  margin: 0;
  color: var(--dqz-text, #f2f5ff);
  font-size: var(--dqz-body-size, 16px);
  line-height: 1.55;
  text-wrap: pretty;
}

.dqz-app .dqz-empty__next {
  margin-top: var(--dqz-space-s);
}

.dqz-app .dqz-empty__actions {
  margin-top: var(--dqz-space-l);
}

/* An action list with nothing in it must not add a gap. */
.dqz-app .dqz-actions:empty {
  display: none;
}

/* ===================================================================== */
/* ===== 8. THE QUIZ: TIMER, QUESTION, CHOICES, REVEAL, SUMMARY ======== */
/* ===================================================================== */

/*
 * Eight palette entries and five design tokens have been reaching the page
 * since 0.1.0 with nothing consuming them. This section is what they were
 * emitted for, and their names are the contract:
 *
 * - `choice` / `choiceText` exist so the answer buttons get their own surface
 *   instead of borrowing `surfaceAlt` from the card chrome. The most important
 *   element in the game must not be re-tinted by a change to the furniture
 *   around it.
 * - `correct` / `wrong` are separate from the `good` / `bad` chrome pair, so an
 *   operator tinting a board cannot dim the most important feedback event in
 *   the product.
 * - `answerReveal` is a third state, not a shade of the first two. It marks
 *   "this was right and you did not pick it", and it is deliberately violet —
 *   off the green/red axis entirely — because a player who got the question
 *   wrong must never see the correct colour and misremember being right.
 *
 * Heights come from tokens and never from content. `questionMinHeight` holds
 * the question block open so a 40-character question and a 180-character one
 * both put the first answer button at the same y coordinate, which is the real
 * requirement behind "buttons do not move under a tapping finger". Questions
 * with different option counts still differ below that point; the first buttons
 * — the ones a fast player is already reaching for — do not move.
 *
 * `.dqz-play` and `.dqz-summary` are cards, so `.dqz-card > * + *` already
 * spaces their direct children. Nothing here re-states that margin.
 */

/* --- the countdown ------------------------------------------------------ */

/*
 * The track. The bar sits ABOVE the question and never below the answers: the
 * answers are the bottom-most interactive thing on the screen and nothing may
 * come between them and the thumb.
 */
.dqz-app .dqz-timer {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: var(--dqz-timer-height, 8px);
  border-radius: var(--dqz-pill-radius, 999px);
  background: var(--dqz-hairline, rgba(255, 255, 255, 0.08));
}

/*
 * The fill. `transform` and `transition-duration` are both set inline by
 * timer.js — the duration is the remaining milliseconds, which only it knows —
 * so this rule supplies everything except those two and must not declare a
 * `transition` shorthand, which would reset the duration it sets.
 *
 * The calm-to-urgent ramp is NOT implemented here, and that is deliberate
 * rather than forgotten: `--dqz-grad-timer` painted on a `scaleX()`-ed element
 * squashes with it, so the ramp is spatially fixed relative to the bar and
 * communicates nothing. Doing it properly needs an interpolatable registered
 * custom property (`@property --dqz-timer-ratio`) transitioning alongside the
 * transform. Until then the bar is the flat `timer` colour and `timerWarn` and
 * the `timer` gradient stay unconsumed. A squashed gradient would look like the
 * feature and behave like nothing.
 */
.dqz-app .dqz-timer__fill {
  width: 100%;
  height: 100%;
  transform: scaleX(1);
  transform-origin: left center;
  background: var(--dqz-timer, #7fe0ff);
  transition-property: transform;
  transition-timing-function: linear;
}

/*
 * The reduced-motion variant. A continuously sliding bar IS motion, so it is
 * replaced by pips that go out once a second — the timer is information and a
 * player who asked for less movement still has to know the clock is running.
 * timer.js chooses between the two; both are styled here.
 */
.dqz-app .dqz-timer--pips {
  display: flex;
  gap: 3px;
  background: none;
  border-radius: 0;
}

.dqz-app .dqz-timer__pip {
  flex: 1 1 auto;
  height: var(--dqz-timer-height, 8px);
  border-radius: 2px;
  background: var(--dqz-timer, #7fe0ff);
}

.dqz-app .dqz-timer__pip.is-out {
  background: var(--dqz-hairline, rgba(255, 255, 255, 0.08));
}

/* --- the question ------------------------------------------------------- */

.dqz-app .dqz-play__progress {
  margin: 0 0 var(--dqz-space-s);
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dqz-text-muted, #9aa6d0);
}

/*
 * The question. It is an `h2` carrying `tabindex="-1"` so the screen can move
 * focus here between questions; the outline on that programmatic focus is the
 * global `:focus-visible` rule's job, not this one's.
 */
.dqz-app .dqz-question {
  display: flex;
  align-items: center;
  min-height: var(--dqz-question-min-height, 120px);
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-h2-size, 21px);
  font-weight: 750;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--dqz-text, #f2f5ff);
  text-wrap: balance;
}

/*
 * An image question. `min-height` above is the floor for the text; a picture
 * pushes past it, and the answer buttons move down for the whole question
 * rather than mid-question, which is the case that matters.
 */
.dqz-app .dqz-question__media {
  display: block;
  width: 100%;
  max-height: 240px;
  margin: 0 0 var(--dqz-space-s);
  border-radius: var(--dqz-radius-small, 8px);
  object-fit: cover;
}

/* --- the answers ------------------------------------------------------- */

.dqz-app .dqz-choices {
  display: grid;
  gap: var(--dqz-choice-gap, 10px);
}

/*
 * Real buttons in a `role="group"`, not a roving-tabindex radiogroup: every
 * answer is reachable with one Tab and pressable with Space or Enter, which is
 * the behaviour a player on a clock needs.
 *
 * `min-height` floors at 44 in the sanitiser rather than the generic button
 * floor of 34. That is not a preference — it is a tap target in a game that is
 * timed, and a mis-tap costs the question outright.
 */
.dqz-app .dqz-choice {
  display: flex;
  align-items: center;
  gap: var(--dqz-space-s);
  width: 100%;
  min-height: var(--dqz-choice-height, 56px);
  padding: var(--dqz-space-s) var(--dqz-space);
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: var(--dqz-radius-small, 8px);
  background-color: var(--dqz-choice, #232f5e);
  background-image: linear-gradient(180deg, var(--dqz-glass), transparent 70%);
  font-size: var(--dqz-choice-font-size, 17px);
  font-weight: 600;
  line-height: 1.25;
  text-align: left;
  color: var(--dqz-choice-text, #eef2ff);
  box-shadow: var(--dqz-shadow-inset);
  transition: border-color calc(120ms * var(--dqz-motion, 1)) ease-out,
    transform calc(90ms * var(--dqz-motion, 1)) ease-out,
    background-color calc(120ms * var(--dqz-motion, 1)) ease-out;
}

.dqz-app .dqz-choice:hover:not(:disabled) {
  border-color: var(--dqz-tone, #3d63ff);
}

.dqz-app .dqz-choice:active:not(:disabled) {
  transform: scale(calc(1 - (0.015 * var(--dqz-motion, 1))));
}

/*
 * Disabled is the post-answer state of every button on the screen, so it must
 * stay perfectly legible — this is the moment the player is reading the result.
 * No opacity drop, and the cursor simply stops inviting a click.
 */
.dqz-app .dqz-choice:disabled {
  cursor: default;
}

.dqz-app .dqz-choice__letter {
  flex: 0 0 auto;
  min-width: 1.7em;
  padding: 3px 0;
  border-radius: 5px;
  background: var(--dqz-glass-strong, rgba(255, 255, 255, 0.09));
  font-size: var(--dqz-small-size, 13px);
  font-weight: 800;
  line-height: 1.2;
  text-align: center;
  color: inherit;
}

.dqz-app .dqz-choice__label {
  flex: 1 1 auto;
  color: inherit;
}

/* --- the three reveal states ------------------------------------------- */

/* The one the player touched, before the server has answered. */
.dqz-app .dqz-choice.is-chosen {
  border-color: var(--dqz-tone, #3d63ff);
  background-color: var(--dqz-choice, #232f5e);
}

/*
 * `correct`, `wrong` and `answerReveal` are all luminous enough to need dark
 * text on them, so the page background colour is reused as ink rather than a
 * hardcoded near-black — an operator who lightens the theme gets a readable
 * button, and one who picks a pale `correct` gets told by the contrast checker
 * in the Design tab rather than by a player.
 */
.dqz-app .dqz-choice.is-correct {
  border-color: var(--dqz-correct, #3ddc84);
  background-color: var(--dqz-correct, #3ddc84);
  background-image: none;
  color: var(--dqz-bg, #0a0e1c);
}

.dqz-app .dqz-choice.is-wrong {
  border-color: var(--dqz-wrong, #ff5470);
  background-color: var(--dqz-wrong, #ff5470);
  background-image: none;
  color: var(--dqz-bg, #0a0e1c);
}

/*
 * "This was the answer, and you did not choose it." OUTLINED, never filled, and
 * never the `correct` colour: the player who sees this got it wrong, and a
 * green fill here is how somebody walks away remembering a wrong answer as
 * right. The inset ring gives it a second, non-colour difference from the two
 * filled states above, so it survives a palette an operator has flattened.
 */
.dqz-app .dqz-choice.is-answer {
  border-color: var(--dqz-answer-reveal, #a97bff);
  background-color: transparent;
  background-image: none;
  color: var(--dqz-text, #f2f5ff);
  box-shadow: inset 0 0 0 2px var(--dqz-answer-reveal, #a97bff);
}

.dqz-app .dqz-choice.is-correct .dqz-choice__letter,
.dqz-app .dqz-choice.is-wrong .dqz-choice__letter {
  background: var(--dqz-ink-soft, rgba(0, 0, 0, 0.28));
}

/* --- the verdict ------------------------------------------------------- */

.dqz-app .dqz-reveal__verdict {
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-body-size, 16px) * 1.15);
  font-weight: 800;
}

/* Colour is the second signal here, never the only one: the element carries the
   word "Correct" or "Not this time" as text, which is what a forced-colours
   mode and a colour-blind player both fall back to. */
.dqz-app .dqz-reveal__verdict.is-correct {
  color: var(--dqz-correct, #3ddc84);
}

.dqz-app .dqz-reveal__verdict.is-wrong {
  color: var(--dqz-wrong, #ff5470);
}

.dqz-app .dqz-reveal__why {
  margin: var(--dqz-space-xs) 0 0;
  font-size: var(--dqz-small-size, 13px);
  line-height: 1.5;
  color: var(--dqz-text-muted, #9aa6d0);
}

/* --- notes ------------------------------------------------------------- */

/*
 * An in-card note. Distinct from `.dqz-card--warn`, which is a whole card: this
 * is a block INSIDE one, so it has no card padding, border or shadow of its own
 * and nesting it never reads as two stacked cards. The offline and resume
 * messages on all three screens use it.
 */
.dqz-app .dqz-note {
  padding: var(--dqz-space-s) var(--dqz-space);
  border-left: 3px solid var(--dqz-tone, #3d63ff);
  border-radius: 0 var(--dqz-radius-small, 8px) var(--dqz-radius-small, 8px) 0;
  background: var(--dqz-glass, rgba(255, 255, 255, 0.04));
  font-size: var(--dqz-small-size, 13px);
  line-height: 1.5;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-note--warn {
  --dqz-tone: var(--dqz-warn, #f5a623);
}

.dqz-app .dqz-note__title {
  margin: 0 0 var(--dqz-space-xs);
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-body-size, 16px);
  font-weight: 750;
  color: var(--dqz-text, #f2f5ff);
}

/* --- home -------------------------------------------------------------- */

.dqz-app .dqz-lede {
  margin: 0;
  font-size: calc(var(--dqz-body-size, 16px) * 1.1);
  line-height: 1.4;
  color: var(--dqz-text, #f2f5ff);
  text-wrap: pretty;
}

/* The "what you are walking into" list: three questions, one go, the clock. */
.dqz-app .dqz-facts {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--dqz-small-size, 13px);
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-facts li {
  display: flex;
  align-items: baseline;
  gap: var(--dqz-space-xs);
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-facts li + li {
  margin-top: 2px;
}

/* --- summary ----------------------------------------------------------- */

.dqz-app .dqz-score {
  display: flex;
  gap: var(--dqz-space-l);
}

.dqz-app .dqz-score__item {
  flex: 1 1 0;
  min-width: 0;
}

.dqz-app .dqz-score__value {
  display: block;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-h1-size, 30px);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-score__label {
  display: block;
  margin-top: 2px;
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dqz-text-muted, #9aa6d0);
}

/* The scoring breakdown, a <details> so a bare total never invites "where did
   that come from" without answering it. */
.dqz-app .dqz-breakdown > ul {
  margin: var(--dqz-space-s) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--dqz-small-size, 13px);
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-breakdown > ul > li + li {
  margin-top: 2px;
}

/* Every question, in review. Terminal by the time this screen exists, so
   prompts, options and answers are all disclosed in full. */
.dqz-app .dqz-review {
  display: grid;
  gap: var(--dqz-space-l);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dqz-app .dqz-review__prompt {
  margin: 0 0 var(--dqz-space-s);
  font-weight: 700;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-review__options {
  display: grid;
  gap: 4px;
  margin: 0 0 var(--dqz-space-xs);
  padding: 0;
  list-style: none;
}

/*
 * The same three states as the live choices, at list scale. They reuse the
 * choice letter chip so a player recognises the row they tapped, and they are
 * NOT buttons — nothing on this screen is pressable any more.
 */
.dqz-app .dqz-review__option {
  display: flex;
  align-items: center;
  gap: var(--dqz-space-s);
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 5px;
  font-size: var(--dqz-small-size, 13px);
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-review__option.is-answer {
  border-color: var(--dqz-answer-reveal, #a97bff);
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-review__option.is-correct {
  border-color: var(--dqz-correct, #3ddc84);
  background: var(--dqz-correct, #3ddc84);
  color: var(--dqz-bg, #0a0e1c);
}

.dqz-app .dqz-review__option.is-wrong {
  border-color: var(--dqz-wrong, #ff5470);
  background: var(--dqz-wrong, #ff5470);
  color: var(--dqz-bg, #0a0e1c);
}

.dqz-app .dqz-review__option.is-correct .dqz-choice__letter,
.dqz-app .dqz-review__option.is-wrong .dqz-choice__letter {
  background: var(--dqz-ink-soft, rgba(0, 0, 0, 0.28));
}

.dqz-app .dqz-review__why,
.dqz-app .dqz-review__note {
  margin: 0;
  font-size: var(--dqz-small-size, 13px);
  line-height: 1.5;
  color: var(--dqz-text-muted, #9aa6d0);
}

/* --- the beat between questions ---------------------------------------- */

/*
 * The held screen between a reveal and the next question, and the sponsor
 * position inside it. Centred and given the question block's own minimum height
 * so the card does not resize during the transition — the beat exists to stop
 * things jumping, and a beat that itself changes the card's height would be
 * self-defeating.
 */
.dqz-app .dqz-interlude {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space);
  align-items: center;
  justify-content: center;
  min-height: var(--dqz-question-min-height, 120px);
  text-align: center;
  animation: dqz-enter calc(260ms * var(--dqz-motion, 1)) ease-out both;
}

.dqz-app .dqz-interlude__beat {
  margin: 0;
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dqz-text-muted, #9aa6d0);
}

/* The slot brings its own reserved height; it only needs to not be stretched
   by the flex column above. */
.dqz-app .dqz-interlude .dqz-slot {
  flex: 0 0 auto;
  width: 100%;
}

/*
 * The entrance for a freshly drawn question. Applied to the elements rather than
 * toggled with a class on their container, because `draw()` rebuilds these nodes
 * on every question and a brand-new element runs its animation without anything
 * having to remember to remove a class first.
 *
 * The reduced-motion block sets `animation: none !important`, so these become
 * static — which is why every keyframe animates TO the element's natural state
 * and never away from it. A player who asked for less motion sees the finished
 * layout, not an empty card.
 */
.dqz-app .dqz-question,
.dqz-app .dqz-choices,
.dqz-app .dqz-reveal {
  animation: dqz-enter calc(240ms * var(--dqz-motion, 1)) ease-out both;
}

.dqz-app .dqz-choices {
  animation-delay: calc(60ms * var(--dqz-motion, 1));
}
/* --- the leaderboard ---------------------------------------------------- */

/*
 * Seven numeric columns do not fit a phone, and a table that reflows into
 * stacked label/value pairs is harder to read than one that scrolls: the whole
 * value of a board is comparing your row against the one above it, which
 * stacking destroys. So the table keeps its shape and scrolls sideways inside
 * its own box — the one exception this stylesheet makes to "nothing scrolls
 * horizontally", and it is confined to a wrapper rather than reaching the page.
 */
.dqz-app .dqz-board__scroll {
  overflow-x: auto;
  margin: 0 calc(var(--dqz-card-pad) * -1);
  padding: 0 var(--dqz-card-pad);
  -webkit-overflow-scrolling: touch;
}

.dqz-app .dqz-board__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--dqz-small-size, 13px);
  white-space: nowrap;
}

.dqz-app .dqz-board__table th {
  padding: 0 var(--dqz-space-s) var(--dqz-space-xs);
  border-bottom: 1px solid var(--dqz-hairline-strong);
  color: var(--dqz-text-muted, #9aa6d0);
  font-size: calc(var(--dqz-small-size, 13px) * 0.85);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: right;
  text-transform: uppercase;
}

/* The first two columns are identity, not quantity, so they read left. */
.dqz-app .dqz-board__table th:first-child,
.dqz-app .dqz-board__table th:nth-child(2) {
  text-align: left;
}

.dqz-app .dqz-board__table td {
  padding: var(--dqz-space-xs) var(--dqz-space-s);
  border-bottom: 1px solid var(--dqz-hairline);
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-board__rank {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  text-align: left;
  color: var(--dqz-text-muted, #9aa6d0);
}

.dqz-app .dqz-board__who {
  max-width: 14em;
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
}

/*
 * Tabular figures on every number, so a column of ranks and points lines up
 * digit for digit. Proportional numerals make a leaderboard look broken at a
 * glance and it is never obvious why.
 */
.dqz-app .dqz-board__num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/*
 * THE VIEWER'S OWN ROW. Marked with the tone channel rather than a hue of its
 * own, so it follows whatever the operator has set the primary colour to — and
 * with a left rule as well as a tint, because a colour-only marker is invisible
 * in a forced-colours mode and to a good number of players.
 */
.dqz-app .dqz-board__row.is-you td {
  background: var(--dqz-glass-strong);
  font-weight: 700;
}

.dqz-app .dqz-board__row.is-you td:first-child {
  box-shadow: inset 3px 0 0 var(--dqz-tone, #3d63ff);
}

.dqz-app .dqz-board__medals {
  display: inline-flex;
  gap: 3px;
  margin-left: var(--dqz-space-xs);
  vertical-align: middle;
}

.dqz-app .dqz-board__medal {
  padding: 0 5px;
  font-size: calc(var(--dqz-small-size, 13px) * 0.78);
}

/*
 * The own-row callout, rendered even when the viewer also appears in the table.
 * Up there it is one highlighted line among fifty; here it is the sentence they
 * came for — "you are #412 of 3,180, 40 points off #411" is the number that
 * makes tomorrow worth playing for everybody who will never see a top fifty.
 */
.dqz-app .dqz-board__you {
  margin-top: var(--dqz-space);
  padding: var(--dqz-space-s) var(--dqz-space);
  border-left: 3px solid var(--dqz-tone, #3d63ff);
  border-radius: 0 var(--dqz-radius-small, 8px) var(--dqz-radius-small, 8px) 0;
  background: var(--dqz-glass, rgba(255, 255, 255, 0.04));
}

.dqz-app .dqz-board__yourank {
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-body-size, 16px) * 1.15);
  font-weight: 800;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-board__tabs {
  gap: var(--dqz-space-s);
  margin-bottom: var(--dqz-space-s);
}

/* --- where you stand ----------------------------------------------------- */

/*
 * The standing block: a rank, or the shadow rank a player has not claimed yet.
 *
 * It renders EMPTY until its own request lands, so it must collapse to nothing
 * rather than reserve a gap — an empty bordered box under the score would read
 * as something that failed to load. `:empty` does that, and it is the honest
 * shape here: the block genuinely has nothing to say until it does.
 */
.dqz-app .dqz-standing {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
  margin-top: var(--dqz-space);
  padding: var(--dqz-space-s) var(--dqz-space);
  border-left: 3px solid var(--dqz-tone, #3d63ff);
  border-radius: 0 var(--dqz-radius-small, 8px) var(--dqz-radius-small, 8px) 0;
  background: var(--dqz-glass, rgba(255, 255, 255, 0.04));
}

.dqz-app .dqz-standing:empty {
  display: none;
}

/*
 * The rank itself, at heading weight. This is the sentence the screen exists to
 * deliver — "that would have put you #38 this week" is the strongest thing the
 * product can say to somebody who has just finished a day without an account —
 * so it is not set in body copy.
 */
.dqz-app .dqz-standing__rank {
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-body-size, 16px) * 1.2);
  font-weight: 800;
  line-height: 1.2;
  color: var(--dqz-text, #f2f5ff);
  text-wrap: balance;
}

.dqz-app .dqz-standing p {
  margin: 0;
}

/* ===================================================================== */
/* ===== 9. MODAL ====================================================== */
/* ===================================================================== */

/* A hard document scroll lock is not reachable from a stylesheet scoped to
   .dqz-app (modal.js sets it on <html>), so here the background is made inert
   and bounce-scrolling contained. */
.dqz-app.has-modal {
  overscroll-behavior: contain;
}

.dqz-app.has-modal .dqz-shell {
  pointer-events: none;
}

.dqz-app .dqz-modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 50;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  pointer-events: auto;
}

.dqz-app .dqz-modal:not(.is-open) {
  display: none;
}

.dqz-app .dqz-modal__backdrop {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: var(--dqz-ink-strong);
  -webkit-backdrop-filter: blur(8px) saturate(0.9);
  backdrop-filter: blur(8px) saturate(0.9);
  animation: dqz-flash calc(220ms * var(--dqz-motion, 1)) ease-out 1;
}

.dqz-app .dqz-modal__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--dqz-max-width, 560px);
  max-height: 88vh;
  border: 1px solid var(--dqz-border, #2d3767);
  border-bottom: 0;
  /* Bottom-sheet on narrow screens. */
  border-radius: var(--dqz-radius, 14px) var(--dqz-radius, 14px) 0 0;
  background-color: var(--dqz-surface, #131a33);
  background-image: linear-gradient(180deg, var(--dqz-glass), transparent 40%);
  color: var(--dqz-text, #f2f5ff);
  box-shadow: var(--dqz-shadow-3);
  animation: dqz-slide-in calc(280ms * var(--dqz-motion, 1)) cubic-bezier(0.2, 0.8, 0.2, 1) 1;
}

/* The drag-handle cue that sells the sheet. */
.dqz-app .dqz-modal__panel::before {
  content: "";
  position: absolute;
  top: 6px;
  left: 50%;
  width: 40px;
  height: 4px;
  margin-left: -20px;
  border-radius: var(--dqz-pill-radius);
  background: var(--dqz-hairline-strong);
}

.dqz-app .dqz-modal__head {
  display: flex;
  flex: none;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--dqz-space-s);
  padding: var(--dqz-space-l) var(--dqz-space) var(--dqz-space-s);
  border-bottom: 1px solid var(--dqz-border, #2d3767);
}

.dqz-app .dqz-modal__title {
  margin: 0;
  min-width: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: var(--dqz-h2-size, 21px);
  font-weight: 800;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

/*
 * STACKED ABOVE THE PANEL'S OWN CONTENT, AND THE REASON IS NOT A DIAGNOSIS.
 * The X was reported as dead on the profile dialog — only a click outside the
 * panel would dismiss it — and nothing in the dialog's markup, listeners or
 * paint order could be shown to cause that. This pair closes the one class of
 * cause a stylesheet can close: a body element with its own stacking context
 * covering the header's top-right corner would swallow the press without
 * leaving a visible trace, because the button would still be there, still
 * focusable and still look pressable. `z-index` only applies to a positioned
 * box, so the `position` is not decoration — without it the `z-index` is
 * ignored outright. modal.js carries the listener half of the same defence.
 */
.dqz-app .dqz-modal__close {
  position: relative;
  z-index: 2;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -8px -8px 0 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background-color: var(--dqz-glass);
  color: var(--dqz-text-muted, #9aa6d0);
  transition: color calc(140ms * var(--dqz-motion, 1)) ease,
    background-color calc(140ms * var(--dqz-motion, 1)) ease;
}

.dqz-app .dqz-modal__close:hover {
  background-color: var(--dqz-glass-strong);
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-modal__body {
  flex: 1 1 auto;
  padding: var(--dqz-space);
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.dqz-app .dqz-modal__body > * + * {
  margin-top: var(--dqz-space-s);
}

.dqz-app .dqz-modal__foot {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--dqz-space-s);
  padding: var(--dqz-space-s) var(--dqz-space) var(--dqz-space);
  border-top: 1px solid var(--dqz-border, #2d3767);
  background-color: var(--dqz-glass);
}

.dqz-app .dqz-modal__foot .dqz-btn {
  flex: 1 1 auto;
}

/* Size variants. */
.dqz-app .dqz-modal--confirm,
.dqz-app .dqz-modal--danger {
  max-width: 420px;
}

.dqz-app .dqz-modal--danger {
  border-color: var(--dqz-bad, #c93a4d);
}

.dqz-app .dqz-modal--danger .dqz-modal__title {
  color: var(--dqz-bad, #c93a4d);
}

.dqz-app .dqz-modal--about .dqz-modal__body {
  padding-bottom: var(--dqz-space-l);
}

/* --- the About / privacy body ------------------------------------------- */

.dqz-app .dqz-about {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
}

/* Server-sanitised prose (the one wp_kses'd body in the app, and the only
   innerHTML path on the whole front end). */
.dqz-app .dqz-about__body {
  color: var(--dqz-text-muted, #9aa6d0);
  font-size: var(--dqz-body-size, 16px);
  line-height: 1.6;
}

.dqz-app .dqz-about__body > * + * {
  margin-top: var(--dqz-space-s);
}

.dqz-app .dqz-about__body h1,
.dqz-app .dqz-about__body h2,
.dqz-app .dqz-about__body h3,
.dqz-app .dqz-about__body h4 {
  color: var(--dqz-text, #f2f5ff);
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-weight: 800;
}

.dqz-app .dqz-about__body h2 {
  font-size: var(--dqz-h2-size, 21px);
}

.dqz-app .dqz-about__body h3,
.dqz-app .dqz-about__body h4 {
  font-size: calc(var(--dqz-body-size, 16px) * 1.05);
}

.dqz-app .dqz-about__body strong,
.dqz-app .dqz-about__body b {
  color: var(--dqz-text, #f2f5ff);
  font-weight: 800;
}

/* Colour has to be set on each element, not just inherited from the container.
   A theme rule as ordinary as `.entry-content p { color: #333 }` targets the
   paragraph directly, and a direct declaration always beats an inherited value
   no matter how specific the ancestor selector is — which is how this prose
   ends up black on a dark panel. */
.dqz-app .dqz-about__body p,
.dqz-app .dqz-about__body li,
.dqz-app .dqz-about__body ul,
.dqz-app .dqz-about__body ol,
.dqz-app .dqz-about__body small,
.dqz-app .dqz-about__body em,
.dqz-app .dqz-about__body i,
.dqz-app .dqz-about__body span,
.dqz-app .dqz-about__body div {
  color: var(--dqz-text-muted, #9aa6d0);
  -webkit-text-fill-color: currentColor;
}

.dqz-app .dqz-about__body a,
.dqz-app .dqz-about__body a:visited {
  color: var(--dqz-accent, #00e0b8);
  -webkit-text-fill-color: currentColor;
  text-decoration: underline;
}

.dqz-app .dqz-about__body a:hover,
.dqz-app .dqz-about__body a:focus-visible {
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-about__body hr {
  border: 0;
  border-top: 1px solid var(--dqz-hairline);
}

.dqz-app .dqz-about__body ul,
.dqz-app .dqz-about__body ol {
  padding-left: 1.3em;
  list-style: disc outside;
}

.dqz-app .dqz-about__body ol {
  list-style: decimal outside;
}

.dqz-app .dqz-about__body li + li {
  margin-top: 4px;
}

.dqz-app .dqz-about__body blockquote {
  padding-left: var(--dqz-space-s);
  border-left: 3px solid var(--dqz-border, #2d3767);
  font-style: italic;
}

/* ===================================================================== */
/* ===== 10. TOAST ===================================================== */
/* ===================================================================== */

/*
 * A toast is INFORMATION, not decoration, so it stays on the far side of the
 * juice split from confetti and celebration: at --dqz-motion: 0 it must still
 * appear, it simply appears without sliding. The animation below is the only
 * thing motion removes.
 */

.dqz-app .dqz-toast {
  position: fixed;
  bottom: 16px;
  left: 50%;
  z-index: 90;
  max-width: min(92vw, 420px);
  padding: 11px var(--dqz-space);
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: var(--dqz-pill-radius);
  background-color: var(--dqz-surface, #131a33);
  color: var(--dqz-text, #f2f5ff);
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
  transform: translateX(-50%);
  pointer-events: none;
  box-shadow: var(--dqz-shadow-3);
  animation: dqz-toast-in calc(240ms * var(--dqz-motion, 1)) cubic-bezier(0.2, 0.8, 0.2, 1) 1;
}

.dqz-app .dqz-toast--good {
  border-color: var(--dqz-good, #2e9e5b);
  box-shadow: 0 0 0 1px var(--dqz-good, #2e9e5b), var(--dqz-shadow-3);
}

.dqz-app .dqz-toast--bad {
  border-color: var(--dqz-bad, #c93a4d);
  box-shadow: 0 0 0 1px var(--dqz-bad, #c93a4d), var(--dqz-shadow-3);
}

/* ===================================================================== */
/* ===== 11. KEYFRAMES ================================================= */
/* ===================================================================== */

/*
 * The entrance used by the question block, the answer list and the beat between
 * them. Ends on the natural state — no transform, full opacity — so that when
 * the reduced-motion block cancels it the element is simply itself.
 */
@keyframes dqz-enter {
  0% {
    opacity: 0;
    transform: translateY(6px);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

@keyframes dqz-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

@keyframes dqz-slide-in {
  0% {
    transform: translateY(14px);
    opacity: 0.001;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

@keyframes dqz-toast-in {
  0% {
    transform: translateX(-50%) translateY(14px);
    opacity: 0.001;
  }
  100% {
    transform: translateX(-50%) translateY(0);
    opacity: 1;
  }
}

@keyframes dqz-flash {
  0% {
    filter: brightness(1);
  }
  35% {
    filter: brightness(1.5);
  }
  100% {
    filter: brightness(1);
  }
}

/* ===================================================================== */
/* ===== 12. RESPONSIVE ================================================ */
/* ===================================================================== */

@media (min-width: 640px) {
  .dqz-app .dqz-shell {
    padding: 0 var(--dqz-space-l) var(--dqz-space-xl);
  }

  .dqz-app .dqz-topbar {
    margin: 0 calc(-1 * var(--dqz-space-l));
    padding: var(--dqz-space-xs) var(--dqz-space-l);
  }

  .dqz-app .dqz-topbar__main {
    min-height: 54px;
  }

  .dqz-app .dqz-topbar__brand {
    font-size: calc(var(--dqz-body-size, 16px) * 0.95);
  }

  .dqz-app .dqz-presented__img {
    max-height: 34px;
  }

  .dqz-app .dqz-anchor {
    scroll-margin-top: 80px;
  }

  .dqz-app .dqz-card {
    --dqz-card-pad: var(--dqz-space-l);
  }

  .dqz-app .dqz-card--tight {
    --dqz-card-pad: var(--dqz-space);
  }

  .dqz-app .dqz-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .dqz-app .dqz-actions > .dqz-btn--primary:first-child,
  .dqz-app .dqz-actions > .dqz-field__help {
    grid-column: 1 / -1;
  }

  /* Centred dialog from here up. */
  .dqz-app .dqz-modal {
    align-items: center;
    padding: var(--dqz-space-l);
  }

  .dqz-app .dqz-modal__panel {
    max-height: 88vh;
    border-bottom: 1px solid var(--dqz-border, #2d3767);
    border-radius: var(--dqz-radius, 14px);
  }

  .dqz-app .dqz-modal__panel::before {
    display: none;
  }

  .dqz-app .dqz-modal__head {
    padding: var(--dqz-space) var(--dqz-space-l) var(--dqz-space-s);
  }

  .dqz-app .dqz-modal__body {
    padding: var(--dqz-space) var(--dqz-space-l);
  }

  .dqz-app .dqz-modal__foot {
    padding: var(--dqz-space-s) var(--dqz-space-l) var(--dqz-space);
  }

  .dqz-app .dqz-modal__foot .dqz-btn {
    flex: 0 1 auto;
  }
}

/* The banner's wide-viewport height. Its own breakpoint on purpose: 760px is
   where a 90px strip stops crowding the question and starts looking deliberate,
   and it is the height the Sponsors tab quotes to whoever supplies the image. */
@media (min-width: 760px) {
  .dqz-app .dqz-slot--banner {
    height: var(--dqz-slot-banner-height-wide, 90px);
  }
}

@media (min-width: 900px) {
  .dqz-app .dqz-shell {
    max-width: var(--dqz-max-width, 560px);
  }

  .dqz-app .dqz-h1 {
    font-size: calc(var(--dqz-h1-size, 30px) * 1.16);
  }

  .dqz-app .dqz-empty__hero {
    padding-top: var(--dqz-space-xl);
  }
}

/* Short landscape phones: a dialog must not become taller than the screen. */
@media (max-height: 520px) and (orientation: landscape) {
  .dqz-app .dqz-modal__panel {
    max-height: 94vh;
  }

  .dqz-app .dqz-empty__hero {
    padding-top: var(--dqz-space-s);
  }
}

/* ===================================================================== */
/* ===== 13. REDUCED MOTION ============================================ */
/* ===================================================================== */

@media (prefers-reduced-motion: reduce) {
  .dqz-app {
    /* Zeroes every calc(... * var(--dqz-motion)) duration in one move, and is
       also what the JS reads to skip its own sequences. */
    --dqz-motion: 0;
  }

  .dqz-app,
  .dqz-app *,
  .dqz-app *::before,
  .dqz-app *::after {
    animation: none !important;
    animation-duration: 0s !important;
    animation-delay: 0s !important;
    transition: none !important;
    transition-duration: 0s !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  /* Static end states must all remain visible and legible: the animation was
     the entrance, not the content. */
  .dqz-app .dqz-toast,
  .dqz-app .dqz-modal__panel,
  .dqz-app .dqz-empty__card {
    opacity: 1 !important;
    filter: none;
  }

  .dqz-app .dqz-toast {
    transform: translateX(-50%) !important;
  }

  .dqz-app .dqz-modal__panel,
  .dqz-app .dqz-empty__card {
    transform: none !important;
  }

  /* The countdown in this mode is the pip strip, and it is INFORMATION: the
     blanket rule above has already stopped every transition, which is what
     makes the sliding bar inert, so the pips must remain fully visible. They
     are switched by a class rather than a transition and so still work. */
  .dqz-app .dqz-timer__pip {
    opacity: 1 !important;
  }

  /* A spinner that cannot spin must not read as a stuck progress indicator. */
  .dqz-app .dqz-boot::before,
  .dqz-app .dqz-spinner {
    border-top-color: var(--dqz-border, #2d3767);
  }
}

/* ===================================================================== */
/* ===== 14. PRINT ===================================================== */
/* ===================================================================== */

@media print {
  .dqz-app {
    --dqz-motion: 0;
    max-width: none;
    background: none !important;
    background-color: var(--dqz-print-paper, #ffffff) !important;
    color: var(--dqz-print-ink, #000000) !important;
    box-shadow: none !important;
  }

  .dqz-app .dqz-shell {
    max-width: none;
    padding: 0;
  }

  /* Chrome, controls, advertising and decoration all go away. A reserved slot
     in particular must not print as a blank rectangle on someone's paper. */
  .dqz-app .dqz-topbar,
  .dqz-app .dqz-footer,
  .dqz-app .dqz-btn,
  .dqz-app .dqz-actions,
  .dqz-app .dqz-modal,
  .dqz-app .dqz-toast,
  .dqz-app .dqz-slot,
  .dqz-app .dqz-presented,
  .dqz-app .dqz-boot,
  .dqz-noscript {
    display: none !important;
  }

  .dqz-app .dqz-card {
    border-color: var(--dqz-print-rule, #999999) !important;
    background: none !important;
    color: var(--dqz-print-ink, #000000) !important;
    box-shadow: none !important;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .dqz-app .dqz-h1,
  .dqz-app .dqz-h2,
  .dqz-app .dqz-card__title,
  .dqz-app .dqz-empty__body {
    color: var(--dqz-print-ink, #000000) !important;
    background: none !important;
    -webkit-text-fill-color: currentColor !important;
    text-shadow: none !important;
    filter: none !important;
  }

  .dqz-app .dqz-muted,
  .dqz-app .dqz-small,
  .dqz-app .dqz-eyebrow {
    color: var(--dqz-print-ink-soft, #444444) !important;
  }

  /* A tone wash prints as a grey slab; the label carries the meaning anyway. */
  .dqz-app .dqz-badge,
  .dqz-app .dqz-pill {
    border-color: var(--dqz-print-ink, #000000) !important;
    color: var(--dqz-print-ink, #000000) !important;
  }

  .dqz-app .dqz-badge::before,
  .dqz-app .dqz-pill::before {
    display: none !important;
  }

  .dqz-app .dqz-empty__mark {
    border-color: var(--dqz-print-rule, #999999) !important;
    background: none !important;
    color: var(--dqz-print-ink, #000000) !important;
    box-shadow: none !important;
  }

  /* A collapsed <details> holds the only copy of some content, so print it. */
  .dqz-app details,
  .dqz-app .dqz-howto {
    display: block !important;
  }

  .dqz-app .dqz-screen {
    display: block !important;
  }
}

/* ===================================================================== */
/* ===== 15. FORCED COLOURS (Windows high contrast) ==================== */
/* ===================================================================== */

/*
 * In a forced-colours mode the palette is replaced wholesale, so anything this
 * design communicates with colour alone has to survive on system colours —
 * borders, labels and states carried by a border style rather than a hue.
 */
@media (forced-colors: active) {
  .dqz-app .dqz-card,
  .dqz-app .dqz-modal__panel,
  .dqz-app .dqz-toast,
  .dqz-app .dqz-empty__mark {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    box-shadow: none;
  }

  .dqz-app .dqz-btn {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    box-shadow: none;
    forced-color-adjust: none;
  }

  /* The primary action must still read as the primary action. */
  .dqz-app .dqz-btn--primary {
    border: 2px solid Highlight;
    background: Highlight;
    color: HighlightText;
    background-image: none;
  }

  .dqz-app .dqz-btn[disabled],
  .dqz-app .dqz-btn:disabled {
    opacity: 1;
    filter: none;
    border-color: GrayText;
    color: GrayText;
  }

  /* The tone channel is exactly the kind of colour-only signal this mode
     flattens, so the badge falls back to a plain outlined chip and lets its
     text label do the work. */
  .dqz-app .dqz-badge,
  .dqz-app .dqz-pill {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
  }

  .dqz-app .dqz-badge::before,
  .dqz-app .dqz-pill::before {
    display: none;
  }

  /* A warning card's left rule is a colour cue; give it a shape too. */
  .dqz-app .dqz-card--warn {
    border-width: 3px;
    border-style: double;
  }

  .dqz-app .dqz-card--warn::before {
    background: CanvasText;
  }

  /* The reserved slot is a held space, not content: outline it so it does not
     read as a broken image, and keep the "Advertisement" label legible. */
  .dqz-app .dqz-slot {
    border: 1px dashed CanvasText;
    background: Canvas;
  }

  .dqz-app .dqz-slot.is-empty {
    border-color: GrayText;
  }

  .dqz-app .dqz-slot__label {
    color: CanvasText;
    opacity: 1;
  }

  /* The answer buttons and their three reveal states. In a forced-colours mode
     every hue collapses, so each state is given a border STYLE as well — the
     one difference that survives. Text is not injected to mark them: generated
     content cannot be translated, and the reveal already states the verdict in
     words above the list. */
  .dqz-app .dqz-choice {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    box-shadow: none;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-choice__letter {
    background: Canvas;
    border: 1px solid CanvasText;
    color: CanvasText;
  }

  .dqz-app .dqz-choice.is-chosen {
    border-width: 2px;
  }

  .dqz-app .dqz-choice.is-correct,
  .dqz-app .dqz-review__option.is-correct {
    border: 4px double Highlight;
    background: Canvas;
    color: CanvasText;
  }

  .dqz-app .dqz-choice.is-wrong,
  .dqz-app .dqz-review__option.is-wrong {
    border: 2px dashed CanvasText;
    background: Canvas;
    color: CanvasText;
  }

  .dqz-app .dqz-choice.is-answer,
  .dqz-app .dqz-review__option.is-answer {
    border: 2px dotted Highlight;
    background: Canvas;
    color: CanvasText;
  }

  /* The countdown is information, so it must not vanish into the background. */
  .dqz-app .dqz-timer {
    border: 1px solid CanvasText;
    background: Canvas;
  }

  .dqz-app .dqz-timer__fill,
  .dqz-app .dqz-timer__pip {
    background: Highlight;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-timer__pip.is-out {
    background: Canvas;
    border: 1px solid GrayText;
  }

  /* A note's left rule is a colour cue; give it a shape too, exactly as the
     warning card above does. */
  .dqz-app .dqz-note {
    border: 1px solid CanvasText;
    border-left-width: 4px;
    background: Canvas;
    color: CanvasText;
  }

  .dqz-app :focus-visible {
    outline: 3px solid Highlight;
    outline-offset: 2px;
  }
}

/* ===================================================================== */
/* ===== 16. BADGES, TARGETS & PROFILE ================================= */
/* ===================================================================== */

/*
 * The habit engine, finally on screen: the badge wall and the target bars that
 * fill the profile modal, and the freshly-earned strip on the summary.
 *
 * EVERY LEAF IN THIS SECTION IS SELECTED THROUGH ITS BLOCK CONTAINER —
 * `.dqz-badges .dqz-badge__name`, never a bare `.dqz-badge__name` — and both
 * reasons are load-bearing rather than housekeeping:
 *
 * 1. `.dqz-badge` IS ALREADY TAKEN. The badges & pills block above defines it
 *    as the inline tone pill: pill radius, uppercase, `white-space: nowrap`,
 *    and it is on the leaderboard right now carrying medal counts. The grid
 *    tile shares the name by contract, so the tile rules have to out-weigh the
 *    pill rules without editing them, and a container class is the cheapest
 *    weight going. What the tile deliberately KEEPS from the pill is the
 *    `--dqz-tone` channel and its `::before` wash: a tile re-points the channel
 *    and gets its frame and its backing for free, which is the file header's
 *    fourth convention doing exactly what it is for.
 * 2. The card block's blanket `.dqz-card span, .dqz-card strong` colour rule is
 *    (0,2,1), so it beats any bare one-class rule written here. The strip
 *    renders inside `.dqz-summary`, which is a card — a bare `.dqz-badge__name`
 *    or `.dqz-stat__label` would come out at full brightness there and the
 *    earned/locked distinction would collapse on that one screen only, which is
 *    the worst kind of bug to find.
 *
 * So `.dqz-stat` is styled only inside `.dqz-profile__stats` and `.dqz-medal`
 * only inside `.dqz-medals`. A leaf used outside its block is unstyled by
 * design rather than by accident.
 *
 * This surface's print and forced-colours corrections live at the bottom of
 * this section rather than being threaded back into the two blocks above it:
 * the section is appended whole, and a correction is worth more sitting next to
 * the rule it corrects than three hundred lines away from it.
 */

/* --- the badge wall ----------------------------------------------------- */

/*
 * `auto-fill`, not `auto-fit`. The two surfaces that share this class hold
 * fifteen tiles and sometimes two: `auto-fit` collapses the empty tracks, which
 * would stretch a two-badge celebration into two half-width slabs the size of
 * nothing else in the product. `auto-fill` keeps them, so a tile is the same
 * size whether it has fourteen neighbours or one.
 *
 * `minmax(min(82px, 100%), 1fr)` and not `minmax(82px, 1fr)`, because the floor
 * has to be able to give way: a grid whose minimum track is wider than its
 * container overflows it, and the one hard rule in this file is that the page
 * body never scrolls sideways. At 400px this lands on four columns inside the
 * modal and three inside the summary card, with no breakpoint of its own —
 * auto-fill IS the breakpoint.
 */
.dqz-app .dqz-badges {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(82px, 100%), 1fr));
  gap: var(--dqz-space-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
 * The tile. Everything here that looks like a reset is one: it is undoing the
 * pill above — `inline-flex` to a centred grid, the pill radius to the small
 * card radius, and above all `white-space: nowrap` to `normal`, which left
 * alone would let a two-word badge name push a column off the screen.
 *
 * `min-width: 0` here and `overflow-wrap: anywhere` on the label are the same
 * guard from the other end: a grid item's default `min-width: auto` refuses to
 * shrink below its longest word, so one long badge name is enough to breach the
 * container even though every track was sized correctly.
 */
.dqz-app .dqz-badges .dqz-badge {
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 6px;
  min-width: 0;
  padding: var(--dqz-space-s) var(--dqz-space-xs);
  border-radius: var(--dqz-radius-small, 8px);
  color: var(--dqz-text, #f2f5ff);
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  letter-spacing: 0;
  text-align: center;
  text-transform: none;
  white-space: normal;
}

/*
 * EARNED AND LOCKED DIFFER IN THREE WAYS, ONLY ONE OF WHICH IS COLOUR: the
 * frame goes solid against dashed, the medallion goes filled against hollow,
 * and the label goes `text` against `textMuted`. A player with any form of
 * colour blindness, a greyscale screenshot and a forced-colours mode all still
 * read the wall correctly, which a lit-versus-dim hue alone would not survive.
 *
 * An earned tile re-points the tone channel to `text` rather than to `gold` or
 * `streak`, and that is a deliberate refusal of both: DQZ_Defaults::colors()
 * reserves gold for board rank 1 and orange for a hot streak, and a badge is
 * neither. The one hue on the tile is the rosette gradient on the medallion
 * below, which is the gradient that exists for precisely this ("Badge and medal
 * rosettes"). `accent` was the other candidate and lost because it is the
 * focus-ring colour — a wall of accent-washed tiles reads as fifteen focused
 * elements.
 */
.dqz-app .dqz-badges .dqz-badge--earned {
  --dqz-tone: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-badges .dqz-badge--locked {
  border-style: dashed;
}

/* A locked tile is a silhouette, not a hole. The wash drops but does not go:
   the tile still has to look like a tile so the wall reads as "fifteen of these
   exist and you have four", which is the entire point of showing the locked
   ones at all. */
.dqz-app .dqz-badges .dqz-badge--locked::before {
  opacity: 0.05;
}

/*
 * The medallion. Sized in px rather than off a token because it is a glyph
 * frame, not a piece of layout: it has to stay a circle that fits inside the
 * 82px track floor, and following `--dqz-spacing` would let an admin nudge the
 * spacing slider and overflow the tile.
 *
 * Written to hold up whether `.dqz-badge__icon` is a wrapper around the svg or
 * the svg itself — icon() puts its own class on the element it builds, so both
 * are honest readings of the contract. As a wrapper this is a disc with a 24px
 * glyph centred in it; as the svg it is a 44px glyph on the same disc. Neither
 * is broken, which is the most a stylesheet can promise about markup it does
 * not own.
 */
.dqz-app .dqz-badges .dqz-badge__icon {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--dqz-tone);
  border-radius: 50%;
  background: var(--dqz-glass, rgba(255, 255, 255, 0.04));
  color: var(--dqz-tone);
}

/* A CSS length beats the svg's own width/height attributes, which is how a 20px
   default icon becomes a 24px one without the caller passing a size. */
.dqz-app .dqz-badges .dqz-badge__icon .dqz-icon {
  width: 24px;
  height: 24px;
}

/* The luminous fill, and `bg` as its ink: the rosette runs gold to orange and
   dark text is the only thing legible on it. Pairing it with `text` would put
   near-white on light gold, which is the failure the whole foreground/background
   convention in colors() exists to prevent. */
.dqz-app .dqz-badges .dqz-badge--earned .dqz-badge__icon {
  border-color: transparent;
  background: var(--dqz-grad-rosette, linear-gradient(140deg, #ffd34d 0%, #ff8c42 100%));
  color: var(--dqz-bg, #0a0e1c);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, calc(0.2 * var(--dqz-shadow-on, 1))),
    var(--dqz-shadow-1);
}

/* Hollow, dashed and desaturated. The `grayscale()` is insurance rather than
   decoration: the muted token ships as a grey-blue, but an operator is free to
   set `textMuted` to something saturated and a locked badge must not then
   compete with an earned one. */
.dqz-app .dqz-badges .dqz-badge--locked .dqz-badge__icon {
  border-style: dashed;
  background: none;
  color: var(--dqz-text-muted, #9aa6d0);
  box-shadow: none;
  filter: grayscale(1);
  opacity: 0.72;
}

/*
 * The label: full strength on an earned tile, the muted token on a locked one.
 * The muted TOKEN rather than an `opacity` on the tile, because opacity
 * multiplies against the surface behind it and would drag a 6:1 label under
 * 3:1. Locked is meant to be quieter, not harder to read — a wall nobody can
 * read the names on is a wall that motivates nobody.
 */
.dqz-app .dqz-badges .dqz-badge__name {
  display: block;
  min-width: 0;
  font-size: calc(var(--dqz-small-size, 13px) * 0.92);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0;
  text-transform: none;
  overflow-wrap: anywhere;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-badges .dqz-badge--locked .dqz-badge__name {
  color: var(--dqz-text-muted, #9aa6d0);
}

/*
 * The count for a countable badge, in the corner. Absolute rather than a third
 * grid row so that a tile with a count and a tile without are the same height —
 * a ragged row of tiles reads as a rendering fault, not as data. It sits inside
 * the pill's `overflow: hidden`, so it is inset rather than hung off the edge,
 * and it takes `bg` as its ink for the same reason the medallion does: the tone
 * filling it is a light one in both states.
 */
.dqz-app .dqz-badges .dqz-badge__count {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 18px;
  padding: 0 5px;
  border-radius: var(--dqz-pill-radius, 999px);
  background: var(--dqz-tone);
  color: var(--dqz-bg, #0a0e1c);
  font-size: calc(var(--dqz-small-size, 13px) * 0.78);
  font-weight: 900;
  line-height: 1.6;
  letter-spacing: 0;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* --- the celebration ---------------------------------------------------- */

/*
 * THE FRESHLY-EARNED STRIP IS THE ONE BADGE SURFACE THAT MOVES, and the summary
 * card is what tells it apart from the wall: the class list gives the strip no
 * modifier of its own, so the container it lands in is the only hook there is.
 * That is also the right cut — fifteen tiles popping in one after another every
 * time somebody opens their profile would be nauseating; two or three arriving
 * on the screen that just awarded them is the payoff.
 *
 * Built out of the keyframes block's existing entrances on purpose. `dqz-enter`
 * carries the arrival and `dqz-flash` the glint, and both were written to END
 * on the element's natural state, which is what makes the juice split work here
 * for free: the reduced-motion block sets `animation: none !important` and the
 * tiles are simply themselves, while an admin `--dqz-motion: 0` zeroes the
 * durations and the `both` fill holds the 100% frame — the finished, fully
 * legible strip. No entry in the reduced-motion block is needed and none should
 * be added: the information in this strip is the tiles, and the tiles never
 * depended on the animation.
 *
 * The stagger rides on an INHERITED custom property rather than on per-tile
 * animation-delay declarations, so it survives either markup: if the tiles are
 * each wrapped in an `li`, the property is set on the wrapper and inherits down
 * to the tile. A tile past the sixth gets the sixth delay, because a queue that
 * keeps growing stops reading as a sequence and starts reading as lag.
 */
.dqz-app .dqz-summary .dqz-badges {
  --dqz-badge-in: 0ms;
}

.dqz-app .dqz-summary .dqz-badges .dqz-badge {
  animation: dqz-enter calc(260ms * var(--dqz-motion, 1)) ease-out
      calc(var(--dqz-badge-in, 0ms) * var(--dqz-motion, 1)) both,
    dqz-flash calc(420ms * var(--dqz-motion, 1)) ease-out
      calc((var(--dqz-badge-in, 0ms) + 120ms) * var(--dqz-motion, 1)) 1;
}

.dqz-app .dqz-summary .dqz-badges > :nth-child(2) {
  --dqz-badge-in: 70ms;
}

.dqz-app .dqz-summary .dqz-badges > :nth-child(3) {
  --dqz-badge-in: 140ms;
}

.dqz-app .dqz-summary .dqz-badges > :nth-child(4) {
  --dqz-badge-in: 210ms;
}

.dqz-app .dqz-summary .dqz-badges > :nth-child(5) {
  --dqz-badge-in: 280ms;
}

.dqz-app .dqz-summary .dqz-badges > :nth-child(n + 6) {
  --dqz-badge-in: 350ms;
}

/* --- progress towards the next one -------------------------------------- */

.dqz-app .dqz-targets {
  display: grid;
  gap: var(--dqz-space);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dqz-app .dqz-targets .dqz-target {
  display: block;
  min-width: 0;
}

/* Label left, number right, both allowed to wrap. `min-width: 0` on the parts
   again: `space-between` on a flex row will happily push a long metric name
   past the edge of the container rather than break it. */
.dqz-app .dqz-targets .dqz-target__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--dqz-space-s);
  margin: 0 0 5px;
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  line-height: 1.3;
  color: var(--dqz-text, #f2f5ff);
  font-variant-numeric: tabular-nums;
}

.dqz-app .dqz-targets .dqz-target__head > * {
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * The track. Height comes off the spacing token and NOT off
 * `--dqz-timer-height`, which is the tempting one: borrowing it would mean an
 * operator thinning the countdown bar also thinned every progress bar in the
 * profile, coupling two things that have nothing to do with each other and are
 * tuned on different screens.
 *
 * The track is the part that has to carry meaning on its own, because at 0% the
 * fill has no width at all — see below.
 */
.dqz-app .dqz-targets .dqz-target__bar {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: var(--dqz-space-s);
  border-radius: var(--dqz-pill-radius, 999px);
  background: var(--dqz-hairline, rgba(255, 255, 255, 0.08));
}

/*
 * The fill. Its `width` is set inline by the JS as a percentage; this rule
 * supplies the 0% default, which is the whole point — a bar whose inline style
 * has not landed yet, or a target genuinely at zero, renders as an empty track
 * instead of a full one. The countdown's fill defaults to `scaleX(1)` because a
 * clock starts full; a target starts empty, and defaulting the wrong way would
 * show every player a screen of completed badges for as long as the fetch takes.
 *
 * WIDTH RATHER THAN A TRANSFORM, and the countdown's own fill rule is why:
 * `--dqz-grad-rosette` painted on a `scaleX()`-ed element squashes with it, so
 * the gradient ends up spatially fixed to the bar and communicates nothing.
 * That trap is already documented on `.dqz-timer__fill`; this is the same trap
 * and the same class of gradient, so the fill animates its width and the
 * gradient stays put.
 *
 * The transition is decoration and is allowed to be: the number is spelled out
 * in `__head` as text, so a player with motion off reads the progress rather
 * than watching it.
 */
/*
 * DISPLAY FIRST, AND WITHOUT IT NOTHING BELOW WORKS. The fill is a <span>
 * inside a plain block <div>, so it is a non-replaced inline box — and width
 * and height do not apply to one. Every bar rendered empty at every value,
 * with no error and no broken layout, because the inline `width: 43%` the
 * script writes was discarded exactly as the `width: 0` here was.
 *
 * `.dqz-timer__fill` above needs no such line, and the difference is not the
 * CSS: timer.js builds that one as a <div>.
 */
.dqz-app .dqz-targets .dqz-target__fill {
  display: block;
  width: 0;
  height: 100%;
  background: var(--dqz-grad-rosette, linear-gradient(140deg, #ffd34d 0%, #ff8c42 100%));
  transition: width calc(420ms * var(--dqz-motion, 1)) cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* --- the profile body --------------------------------------------------- */

/*
 * The modal's content column. It sets its own gaps rather than leaning on
 * `.dqz-card > * + *`: this body is not a card, it is the inside of a panel,
 * and the modal block spaces nothing for it.
 */
.dqz-app .dqz-profile {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-l);
  min-width: 0;
}

.dqz-app .dqz-profile p {
  margin: 0;
}

/*
 * Eight totals, four of which are legitimately a dash. The same `min()` floor as
 * the badge wall, for the same reason: two columns at 400px, three from the
 * point the panel is wide enough to hold them.
 */
.dqz-app .dqz-profile__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(112px, 100%), 1fr));
  gap: var(--dqz-space) var(--dqz-space-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dqz-app .dqz-profile__stats .dqz-stat {
  min-width: 0;
}

/*
 * Deliberately a step down from `.dqz-score__value` on the summary. That one is
 * the result of the thing the player has just done; these are running totals,
 * and six of them at h1 size would read as six headlines with no hierarchy
 * between them.
 *
 * Tabular figures because these sit in a grid and get read as a column: mixed
 * numerals make a stat block look subtly broken and it is never obvious why.
 * `overflow-wrap: anywhere` covers the mean-milliseconds value, the one figure
 * here with no upper bound on its length.
 */
.dqz-app .dqz-profile__stats .dqz-stat__value {
  display: block;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-h2-size, 21px) * 1.05);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--dqz-text, #f2f5ff);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.dqz-app .dqz-profile__stats .dqz-stat__label {
  display: block;
  margin-top: 2px;
  font-size: calc(var(--dqz-small-size, 13px) * 0.92);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dqz-text-muted, #9aa6d0);
}

/*
 * The medal row. `.dqz-medal` states no `--dqz-tone` of its own, and that
 * omission is the design: the leaderboard already builds its medal chips as
 * `dqz-pill dqz-pill--gold`, so re-pointing the channel here at (0,3,0) would
 * out-weigh `.dqz-pill--gold` at (0,2,0) and paint gold, silver and bronze the
 * same colour. This rule only makes the chip bigger than a board chip — it is a
 * headline here and a footnote there — and leaves the metal to the modifier.
 */
.dqz-app .dqz-medals {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dqz-space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dqz-app .dqz-medals .dqz-medal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 5px 11px;
  border: 1px solid var(--dqz-tone);
  border-radius: var(--dqz-radius-small, 8px);
  color: var(--dqz-tone);
  font-size: var(--dqz-small-size, 13px);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* --- print -------------------------------------------------------------- */

/*
 * The print block above hides `.dqz-modal`, so the wall, the target bars and
 * the profile body never reach paper — the only badge surface that prints is
 * the strip on the summary, and these four rules are for it alone.
 *
 * That block already flattens `.dqz-badge` to black ink and kills the tone
 * wash. What it cannot know about is the medallion, which would otherwise print
 * a gold gradient as a grey slab with near-invisible dark strokes on top of it,
 * and the desaturating `filter`, which prints as a faded ghost. Both are
 * undone; the dashed frame is left alone, so earned and locked still differ on
 * paper by border style.
 */
@media print {
  .dqz-app .dqz-badges .dqz-badge__icon {
    border-color: var(--dqz-print-ink, #000000) !important;
    background: none !important;
    color: var(--dqz-print-ink, #000000) !important;
    box-shadow: none !important;
    filter: none !important;
    opacity: 1 !important;
  }

  .dqz-app .dqz-badges .dqz-badge__name {
    color: var(--dqz-print-ink, #000000) !important;
  }

  .dqz-app .dqz-badges .dqz-badge--locked .dqz-badge__name {
    color: var(--dqz-print-ink-soft, #444444) !important;
  }

  .dqz-app .dqz-badges .dqz-badge__count {
    background: none !important;
    color: var(--dqz-print-ink, #000000) !important;
  }
}

/* --- forced colours ----------------------------------------------------- */

/*
 * The forced-colours block above flattens `.dqz-badge` to an outlined chip and
 * lets its text label do the work, which is right for the pill and not enough
 * for the wall: the label on a locked tile says the badge's name, never that it
 * is locked. So the state is re-stated in the two things this mode preserves —
 * border STYLE and the system colour keywords — exactly as that block does for
 * the three choice states.
 *
 * `forced-color-adjust: none` appears on the two fills that are INFORMATION
 * rather than decoration: the earned medallion, which is the whole earned/locked
 * signal, and the progress fill, which is a quantity. Everything else is left
 * for the system palette to replace.
 */
@media (forced-colors: active) {
  .dqz-app .dqz-badges .dqz-badge--earned {
    border: 2px solid Highlight;
  }

  .dqz-app .dqz-badges .dqz-badge--locked {
    border: 1px dashed GrayText;
    color: GrayText;
  }

  .dqz-app .dqz-badges .dqz-badge--earned .dqz-badge__icon {
    border: 1px solid Highlight;
    background: Highlight;
    color: HighlightText;
    box-shadow: none;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-badges .dqz-badge--locked .dqz-badge__icon,
  .dqz-app .dqz-badges .dqz-badge--locked .dqz-badge__name {
    border-color: GrayText;
    background: Canvas;
    color: GrayText;
    filter: none;
    opacity: 1;
  }

  .dqz-app .dqz-badges .dqz-badge__count {
    background: CanvasText;
    color: Canvas;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-targets .dqz-target__bar {
    border: 1px solid CanvasText;
    background: Canvas;
  }

  .dqz-app .dqz-targets .dqz-target__fill {
    background: Highlight;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-medals .dqz-medal {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
  }
}

/*
 * The profile panel is the densest thing in the product — eight stat tiles, a
 * week block, a medal cabinet, a chase list and a fifteen-tile wall — so it
 * takes more width than a confirm dialog and it scrolls. `dqz-modal--profile`
 * was emitted by profile.js with no rule behind it, which is row 2 of
 * HANDOFF.md's table of this exact mistake.
 */
/*
 * THE MODIFIER IS THE PANEL, NOT ITS PARENT. `openModal()` puts `className`
 * into the panel's own class list, so `.dqz-modal--profile .dqz-modal__panel`
 * asks for a panel inside a panel and matches nothing — which the shipped
 * default hid, because 34rem and the default 560px max-width differ by 16px.
 * The Design tab allows 320 to 1200, and at 1200 the fifteen-tile wall
 * flattens to a single row. `--confirm` and `--danger` in section 9 have the
 * correct form and are the precedent.
 */
.dqz-app .dqz-modal--profile,
.dqz-modal--profile {
  max-width: 34rem;
}

/*
 * A SECOND HEIGHT CAP ON THE PROFILE BODY USED TO LIVE HERE AND IS GONE. It
 * read `max-height: 74vh` and it was the ONLY declaration the profile dialog
 * carried that no other dialog carried — which made it the only candidate for
 * a fault that showed up on the profile dialog alone, namely a dead close
 * button. It was never shown to be the cause; it was removed because it bought
 * nothing to weigh against that. The panel is already capped at 88vh in
 * section 9 and the body is already `flex: 1 1 auto` with `overflow-y: auto`,
 * so the wall still scrolls and is still not cut off. A second cap on an inner
 * box could only ever end the body short of the panel it sits in.
 */

/*
 * The description on a wall tile. Clamped to three lines because two of the
 * fifteen run past a hundred characters in an 82px track — `week.speed.slow10`
 * is the worst — and a grid row is as tall as its tallest item, so one long
 * description inflates the three beside it. Clamping keeps the sentence
 * reachable by a thumb and a keyboard, which a `title` attribute is not.
 *
 * `-webkit-line-clamp` is the only cross-browser spelling of this, and the
 * `max-height` under it is what happens where the clamp is unsupported: three
 * lines of the same text, cut rather than faded. Never `display: none`.
 */
.dqz-app .dqz-badges .dqz-badge__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  max-height: 3.9em;
  overflow: hidden;
  font-size: 0.78em;
  line-height: 1.3;
  color: var(--dqz-text-muted);
  text-align: center;
  overflow-wrap: anywhere;
}

/* The celebration strip names what landed; it does not explain it. */
.dqz-app .dqz-summary .dqz-badges .dqz-badge__desc {
  display: none;
}

/*
 * The same sentence as `.dqz-standing__rank`, in the profile panel, and it
 * carries an explicit colour for the same reason that one does: a bare <p>
 * takes its colour by inheritance, and an inherited value loses to any theme
 * rule that matches the element directly. This one was reported black on the
 * dark panel while every muted line around it was fine.
 *
 * (0,2,0), deliberately level with `.dqz-app .dqz-muted` rather than above it:
 * the muted siblings sit in the same block and must keep their own colour.
 */
.dqz-app .dqz-profile__rank {
  margin: 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-body-size, 16px) * 1.1);
  font-weight: 800;
  line-height: 1.2;
  color: var(--dqz-text, #f2f5ff);
  text-wrap: balance;
}

/* ===================================================================== */
/* ===== 17. THE SHARE IMAGE =========================================== */
/* ===================================================================== */

/*
 * The share control and the PNG it produces, on the day summary.
 *
 * ALMOST NOTHING HERE STYLES THE IMAGE ITSELF, and that is the point worth
 * stating: the card is pixels by the time it reaches the document, painted on a
 * canvas from the same custom properties this stylesheet reads. Trying to style
 * it from here would be styling a photograph. What this section governs is the
 * button, the space the finished image occupies, and the frame that stops a
 * dark card from bleeding into a dark panel with no edge between them.
 */
.dqz-app .dqz-share {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
  margin-top: var(--dqz-space);
}

/*
 * NO WIDTH OF ITS OWN. The button is `.dqz-btn--block` and the container is a
 * column; a width here would be a third opinion about the same measurement.
 * The label swaps between `summary.share` and `summary.sharePreparing` while
 * the card is being built, so the height is pinned to the button token and the
 * row cannot grow under a finger that is still resting on it.
 */
.dqz-app .dqz-share__btn {
  min-height: var(--dqz-button-height, 52px);
}

/*
 * The caption copier, which exists only where there is no share sheet to carry
 * the caption for us.
 *
 * `align-self` rather than a width, because the container is a stretching flex
 * column and `.dqz-btn--link` is an underlined text button: stretched, its
 * 44px tap target would run the full width of the card and read as a second
 * primary action sitting under the first. Centred, it reads as what it is — a
 * smaller offer attached to the button above it. The 44px minimum is the
 * link button's own and is not relaxed here.
 */
.dqz-app .dqz-share__copy {
  align-self: center;
  padding-inline: var(--dqz-space-s);
}

/*
 * The finished PNG, once there is one.
 *
 * CAPPED, BECAUSE THE RASTER IS 1080 SQUARE. Left to the global `img` rule it
 * would fill the card's whole width and push the review list off the screen —
 * on a phone, a square image at full width is most of a viewport. 360px is
 * large enough to read the score off and small enough that the thing below it
 * stays visible, which matters because on a browser with no working `download`
 * attribute this image IS the delivery: the player long-presses it.
 *
 * `height: auto` comes from the global image reset and is what keeps the square
 * square; the `width`/`height` attributes share.js sets reserve the ratio
 * before the blob decodes, so nothing jumps when it appears.
 */
.dqz-app .dqz-share__preview {
  width: 100%;
  max-width: 360px;
  margin: 0 auto;
  border: 1px solid var(--dqz-border, #2d3767);
  border-radius: var(--dqz-radius-small, 8px);
  box-shadow: var(--dqz-shadow-1);
}

/* --- print -------------------------------------------------------------- */

/*
 * The global print block already hides every `.dqz-btn`, which would leave the
 * preview behind on its own — a picture of the summary, printed underneath the
 * summary. The whole block goes.
 */
@media print {
  .dqz-app .dqz-share {
    display: none !important;
  }
}

/* --- forced colours ----------------------------------------------------- */

/*
 * A raster is not repainted by a forced-colours mode — it cannot be — so the
 * card keeps its own dark palette while everything around it turns to system
 * colours. Without an explicit edge it reads as a hole in the page rather than
 * as content, so the frame is restated in `CanvasText` and kept.
 */
@media (forced-colors: active) {
  .dqz-app .dqz-share__preview {
    border: 1px solid CanvasText;
    box-shadow: none;
  }
}

/* ===================================================================== */
/* ===== 18. PROMINENCE & THE WAY BACK ================================= */
/* ===================================================================== */

/*
 * The last pass before launch, and it answers two questions the owner asked a
 * screen at a time: WHERE AM I, and WHAT DO I DO NEXT.
 *
 * Every rule here is an override of something defined earlier rather than a new
 * component, which is why it sits at the end of the file instead of inside the
 * sections it adjusts: those sections are read by whoever is working on the
 * screen they belong to, and a late emphasis pass rewriting them in place would
 * hide what the shipped values originally were and why.
 *
 * NOTHING IN THIS SECTION IS CARRIED BY MOTION. Every signal added below is a
 * weight, a fill, a border or a bar that is painted and then stays painted, so
 * `--dqz-motion: 0` and the reduced-motion block in section 13 take nothing
 * away from any of it. That is deliberate rather than a coincidence of the
 * design: an emphasis that exists only during a transition is an emphasis a
 * player with motion switched off never receives at all.
 */

/* --- the header navigation ----------------------------------------------- */

/*
 * THE ACTION ROW MUST NOW BE ABLE TO SHRINK, because there are three controls
 * in it where there were two. `flex: none` in section 3 sizes this row at its
 * max-content width and refuses to give any of it back, which means its own
 * `flex-wrap` can never fire — a flex container wraps when it is narrower than
 * its contents, and a container that is never narrowed never is. The brand
 * beside it already ellipsises to nothing, so the row simply ran past the right
 * edge and `.dqz-app`'s `overflow-x: hidden` cut the last control off. Home,
 * Leaderboard and Profile together do not fit beside a brand at 400px; with
 * `0 1 auto` and a floor of zero they drop to a second line instead, which is
 * what section 3's comment intended `flex-wrap` to do all along.
 */
.dqz-app .dqz-topbar__actions {
  flex: 0 1 auto;
  min-width: 0;
}

.dqz-app .dqz-nav {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--dqz-space-xs);
  min-width: 0;
}

.dqz-app .dqz-nav__item {
  position: relative;
  max-width: 100%;
  min-width: 0;
}

/*
 * The label truncates rather than widening its control past the row. The three
 * English labels fit one line at 400px and wrap to two below that, which is the
 * intended behaviour — this is the backstop for a translation that is longer
 * than all three of them, where a control wider than the viewport would be cut
 * off at the edge with no indication that anything was lost. `.dqz-nav__item`
 * is a labelled control, never `.dqz-btn--icon`, so there is no screen-reader
 * label hidden in here for this to clip.
 */
.dqz-app .dqz-nav__item > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * THE CURRENT LOCATION, AND IT IS SAID THREE WAYS BECAUSE ONE OF THEM IS A
 * COLOUR. The fill is the fastest to read and the least reliable: it is the
 * signal that goes missing for a player with a colour vision deficiency and the
 * one a forced-colours mode replaces wholesale. So the weight goes up as well,
 * and a bar is drawn under the label — a shape, which survives both. The markup
 * carries `aria-current="page"` for anyone reading none of the three.
 *
 * `--dqz-accent` is luminous, so the ink on it is `--dqz-bg`, exactly as the
 * reveal states and the medals do it. A hardcoded near-black here would go
 * unreadable the moment an operator sets a dark accent on the Design tab.
 */
.dqz-app .dqz-nav__item.is-current {
  border-color: var(--dqz-accent, #00e0b8);
  background-color: var(--dqz-accent, #00e0b8);
  background-image: none;
  color: var(--dqz-bg, #0a0e1c);
  /*
   * 900, NOT 800. `.dqz-btn--secondary` already sets 800, so an 800 here
   * changed nothing: the "three ways" the comment above promises were two,
   * leaving the underline as the only non-colour signal rather than one of a
   * pair.
   */
  font-weight: 900;
}

/*
 * Restated against `.dqz-btn--secondary`'s own hover, which is more specific
 * than the rule above and would otherwise hand the current control back its
 * uncurrent colours under a pointer or a finger.
 */
.dqz-app .dqz-nav__item.is-current:hover,
.dqz-app .dqz-nav__item.is-current:focus-visible {
  background-color: var(--dqz-accent, #00e0b8);
  color: var(--dqz-bg, #0a0e1c);
}

/* Inset rather than hung below the control, so a nav that has wrapped to a
   second row cannot clip it against the header's edge. */
.dqz-app .dqz-nav__item.is-current::after {
  content: "";
  position: absolute;
  right: 26%;
  bottom: 5px;
  left: 26%;
  height: 2px;
  border-radius: var(--dqz-pill-radius);
  background: var(--dqz-bg, #0a0e1c);
}

/* --- the home screen's day identity -------------------------------------- */

/*
 * WHAT DAY THIS IS AND WHAT IT COSTS, at the top of the home card instead of
 * spread across an eyebrow, a heading and a date line further down. The left
 * rule and the glass wash are `.dqz-board__you`'s treatment reused on purpose:
 * that is already the "this block is about you, now" shape in this product, and
 * inventing a second one would have been a second thing to learn.
 */
.dqz-app .dqz-today {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-xs);
  min-width: 0;
  padding: var(--dqz-space-s) var(--dqz-space);
  border-left: 3px solid var(--dqz-accent, #00e0b8);
  border-radius: 0 var(--dqz-radius-small, 8px) var(--dqz-radius-small, 8px) 0;
  background: var(--dqz-glass, rgba(255, 255, 255, 0.04));
}

/* The one margin in this block, and it buys the heading above it air that a
   single column gap cannot give to one seam without giving it to all four. */
.dqz-app .dqz-today__count {
  margin: var(--dqz-space-xs) 0 0;
  font-family: var(--dqz-font-heading, var(--dqz-font, inherit));
  font-size: calc(var(--dqz-h2-size, 21px) * 1.1);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--dqz-text, #f2f5ff);
  overflow-wrap: anywhere;
}

/* Beaten down into a label rather than a sentence, because the count above it
   is the line that should be read first and a full localised date is longer. */
.dqz-app .dqz-today__date {
  margin: 0;
  font-size: var(--dqz-small-size, 13px);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dqz-text-muted, #9aa6d0);
  overflow-wrap: anywhere;
}

/* --- the primary action -------------------------------------------------- */

/*
 * THE ONE CONTROL THE LANDING SCREEN EXISTS FOR, and the reason it needs a
 * wrapper at all is that a button cannot be given breathing room by itself:
 * `.dqz-card > * + *` hands it the same gap it hands a paragraph, which is how
 * the most important control on the product ended up reading as the sixth item
 * in a list.
 *
 * The halo is a `box-shadow` rather than a border or an outline, because both
 * of those take layout space and this control sits a fixed distance above the
 * supporting detail. The two shadow tokens fold the `--dqz-shadow-on` switch
 * through their alpha channel; the glass ring does not, so an operator who
 * turns shadows off keeps a quiet ring instead of losing the emphasis outright.
 */
.dqz-app .dqz-start {
  display: flex;
  flex-direction: column;
  gap: var(--dqz-space-s);
  margin-top: var(--dqz-space-s);
  min-width: 0;
}

.dqz-app .dqz-start > .dqz-btn {
  width: 100%;
  height: calc(var(--dqz-button-height, 52px) * 1.15);
  font-size: calc(var(--dqz-button-font-size, 17px) * 1.06);
  letter-spacing: 0.01em;
  box-shadow: var(--dqz-shadow-2), var(--dqz-shadow-inset), 0 0 0 4px var(--dqz-glass);
}

/* The press still has to read as a press. The rule above is specific enough to
   have kept the halo through `.dqz-btn:active`, which removes it. */
.dqz-app .dqz-start > .dqz-btn:active {
  box-shadow: none;
}

/* --- the play screen ----------------------------------------------------- */

/*
 * WHICH QUESTION AM I ON. It was muted small-caps, indistinguishable from the
 * supporting text around it; it is now at full text colour with a mark beside
 * it, because this line is the only thing on the screen that tells a player how
 * much of their one daily attempt is left.
 *
 * The flex box is what positions the mark. It changes no height: the line holds
 * one string of constant shape across all three questions, so nothing below it
 * moves between question 3 and question 4.
 */
.dqz-app .dqz-play__progress {
  display: flex;
  align-items: center;
  gap: var(--dqz-space-s);
  font-size: calc(var(--dqz-small-size, 13px) * 1.08);
  font-weight: 800;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-play__progress::before {
  content: "";
  flex: none;
  width: 18px;
  height: 3px;
  border-radius: var(--dqz-pill-radius);
  background: var(--dqz-accent, #00e0b8);
}

/*
 * THE QUESTION, AND THE CEILING ON THIS IS ARITHMETIC RATHER THAN TASTE.
 * `--dqz-question-min-height` is 120px and exists so the answer buttons sit at
 * identical coordinates on every question — a player must never have the board
 * move under a thumb already travelling towards it. That floor only holds while
 * the prompt still fits inside it. At the shipped `h2Size` of 21px and a line
 * height of 1.25 a line is 26.25px, so four of them occupy 105px and clear the
 * floor. At 1.12 a line is 29.4px and four of them are 117.6px, which still
 * clears it. At 1.18 they are 123.9px and they do not. So 1.12 is the largest
 * step that leaves the four-line headroom exactly where it was found, and that
 * is why this is not a round number.
 *
 * `min-height` is untouched here on purpose. It is section 8's to own, and two
 * places setting the same floor is how a floor stops being one.
 */
.dqz-app .dqz-question {
  /*
   * 1.06, AND THE CEILING IS WHY IT IS NOT MORE.
   *
   * `--dqz-question-min-height` is a FLOOR on this element, so four wrapped
   * lines have to fit inside it or the choice buttons move between a short
   * question and a long one — the single thing that floor exists to prevent.
   * At 1.12 the four-line budget was 117.6px against a 120px floor, which made
   * the shipped heading size the ceiling: the Design tab allows 13 to 44, and
   * one step to 22 broke it at 123.2px.
   *
   * 1.06 restores about the headroom the screen had before this pass, so an
   * operator nudging the heading size does not quietly bring the jump back.
   * The question still reads as the loudest thing here — the weight and the
   * colour do that, and neither costs height.
   */
  font-size: calc(var(--dqz-h2-size, 21px) * 1.06);
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--dqz-text, #f2f5ff);
}

/* --- the summary's headline ---------------------------------------------- */

/*
 * THE SCORE IS THE SCREEN. It was one block among several at heading size; as
 * the hero it gets a surface of its own, a rule between the two figures, and
 * roughly half as much again in size.
 *
 * `min()` CAPS IT AGAINST THE VIEWPORT AND THAT IS THE LOAD-BEARING PART. A
 * flat 45px figure is comfortable for "3/3" and runs out of room for a
 * four-figure score in the 155px column a 400px phone leaves — and the overflow
 * would not even be visible, because `.dqz-app` carries `overflow-x: hidden`
 * and would quietly clip the digits off the end of the number. 12vw hands the
 * narrow case a smaller figure instead of a wrong one.
 */
.dqz-app .dqz-score--hero {
  gap: var(--dqz-space);
  padding: var(--dqz-space) var(--dqz-space-s);
  border: 1px solid var(--dqz-hairline-strong);
  border-radius: var(--dqz-radius, 14px);
  background-image: linear-gradient(180deg, var(--dqz-glass-strong), transparent 70%);
  text-align: center;
}

.dqz-app .dqz-score--hero .dqz-score__item + .dqz-score__item {
  border-left: 1px solid var(--dqz-hairline);
}

.dqz-app .dqz-score--hero .dqz-score__value {
  font-size: min(calc(var(--dqz-h1-size, 30px) * 1.5), 12vw);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--dqz-text, #f2f5ff);
}

.dqz-app .dqz-score--hero .dqz-score__label {
  margin-top: var(--dqz-space-xs);
  color: var(--dqz-text-muted, #9aa6d0);
}

/* --- the way back off the leaderboard ------------------------------------ */

/*
 * THE BOARD IS A SCREEN AND A SCREEN NEEDS A BACK-TARGET. §14 asked for a modal
 * precisely so this control would never have to exist, and the board shipped as
 * a screen anyway: a player who had not yet played could reach it and then had
 * no way home short of reloading the page. The Home control in the header is
 * the general fix. This is the local one, and it is not redundant — the header
 * is sticky, but a fifty-row board runs well past the fold on a phone and a
 * dead end found at the bottom of a long table is still a dead end.
 *
 * Full width and set apart from the table above it, because an exit that has to
 * be hunted for at the end of a long page is one most people will not find. The
 * pair of selectors covers the control and a wrapper around one, so whichever
 * shape the screen emits gets the same spacing.
 */
.dqz-app .dqz-board__back {
  display: flex;
  width: 100%;
  margin-top: var(--dqz-space-l);
}

.dqz-app .dqz-board__back > .dqz-btn {
  flex: 1 1 auto;
  width: 100%;
}

/* --- wider viewports ----------------------------------------------------- */

@media (min-width: 640px) {
  .dqz-app .dqz-today {
    padding: var(--dqz-space-s) var(--dqz-space-l);
  }

  .dqz-app .dqz-score--hero {
    padding: var(--dqz-space-l) var(--dqz-space);
  }

  /* The viewport cap has done its job by here and would otherwise start
     shrinking the figure again on a tablet held in portrait. */
  .dqz-app .dqz-score--hero .dqz-score__value {
    font-size: calc(var(--dqz-h1-size, 30px) * 1.5);
  }
}

/* --- print --------------------------------------------------------------- */

/*
 * Section 14 already hides the header and every `.dqz-btn`, so the nav, the
 * start control and the back control all go on their own. What is left is the
 * two blocks carrying a wash: on paper a glass fill prints as a grey slab under
 * black ink, and the left rule prints as a bar of colour nobody asked to spend
 * toner on.
 */
@media print {
  .dqz-app .dqz-today,
  .dqz-app .dqz-score--hero {
    border-color: var(--dqz-print-rule, #999999) !important;
    background: none !important;
    background-image: none !important;
    color: var(--dqz-print-ink, #000000) !important;
  }

  .dqz-app .dqz-today__count,
  .dqz-app .dqz-score--hero .dqz-score__value {
    color: var(--dqz-print-ink, #000000) !important;
  }

  .dqz-app .dqz-today__date,
  .dqz-app .dqz-score--hero .dqz-score__label {
    color: var(--dqz-print-ink-soft, #444444) !important;
  }

  .dqz-app .dqz-play__progress::before {
    display: none !important;
  }
}

/* --- forced colours ------------------------------------------------------ */

/*
 * The accent fill, the glass washes and the hairlines are all exactly the kind
 * of signal this mode flattens to nothing, and the one that matters most is the
 * current-location marker: a nav that cannot say where you are is how somebody
 * presses Home twice. `forced-color-adjust: none` on the current control is the
 * same exemption section 15 already grants the primary button and the choice
 * buttons, and for the same reason — here the state IS the information.
 */
@media (forced-colors: active) {
  .dqz-app .dqz-nav__item.is-current {
    border: 2px solid Highlight;
    background: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-nav__item.is-current::after {
    background: HighlightText;
  }

  .dqz-app .dqz-today {
    border: 1px solid CanvasText;
    border-left-width: 4px;
    background: Canvas;
  }

  .dqz-app .dqz-score--hero {
    border: 1px solid CanvasText;
    background: Canvas;
    background-image: none;
  }

  .dqz-app .dqz-score--hero .dqz-score__item + .dqz-score__item {
    border-left-color: CanvasText;
  }

  .dqz-app .dqz-play__progress::before {
    background: CanvasText;
    forced-color-adjust: none;
  }

  .dqz-app .dqz-start > .dqz-btn {
    box-shadow: none;
  }
}

/* ===================================================================== */
/* ===== 19. THE HEADER MENU =========================================== */
/* ===================================================================== */

/*
 * THE TABS COLLAPSE BEHIND A HAMBURGER BELOW ONE BREAKPOINT AND ARE INLINE
 * ABOVE IT, AND THE BREAKPOINT IS WRITTEN ONCE.
 *
 * Section 18 raised the prominence of these controls deliberately; hiding them
 * behind a toggle on a screen with room for all three would hand that back for
 * nothing. The crowding is a phone problem — four controls in a row beside a
 * brand — so the collapse is a phone answer, and the wrapping action row of
 * sections 3 and 18 stays the backstop above it, for a translation longer than
 * the three English labels.
 *
 * TO MAKE IT A HAMBURGER AT EVERY WIDTH, change the `@media` prelude below and
 * the `--dqz-nav-collapse` value beside it to the same new condition. They are
 * adjacent so that they are edited together: app.js reads the custom property
 * through getComputedStyle and hands it to matchMedia, because a breakpoint
 * written once in CSS and once in JS drifts, and the drift is silent — the tabs
 * lay out inline while the script still believes they are behind a toggle. A
 * media query cannot read a custom property, which is the only reason the
 * literal appears twice at all. `639.98px` rather than `639px` so there is no
 * gap against the `min-width: 640px` enhancements in section 12 at fractional
 * zoom levels.
 */
.dqz-app {
  --dqz-nav-collapse: (max-width: 639.98px);
}

/*
 * AN ICON CONTROL SIZED TO THE ROW IT JOINS, not to `.dqz-btn--icon`'s 44px
 * circle: it stands beside the account button and the tabs, and three header
 * controls of three different heights read as three unrelated things. So the
 * height comes from `--small` like theirs, and only the width is set, to keep
 * the 44px target `.dqz-btn` asks for on a control with no label to widen it.
 *
 * It is only ever in the document below the breakpoint — app.js removes the
 * node rather than hiding it, so there is no `display` declaration here for a
 * theme to win and no state in which this rule paints a control the player
 * cannot use.
 */
.dqz-app .dqz-nav__toggle {
  flex: none;
  width: 44px;
  min-width: 44px;
  padding: 0;
  gap: 0;
}

/*
 * OPEN IS SAID AS A SHAPE FIRST. The glyph itself swaps from three bars to an X
 * — that is the signal that survives a colour vision deficiency, a forced
 * colours mode and a monochrome screenshot, and it is drawn by app.js rather
 * than here. This fill is the second signal, not the only one, and it is the
 * same accent-on-bg pair `.dqz-nav__item.is-current` uses so that "this header
 * control is active" looks like one idea in both places.
 */
.dqz-app .dqz-nav__toggle.is-open {
  border-color: var(--dqz-accent, #00e0b8);
  background-color: var(--dqz-accent, #00e0b8);
  background-image: none;
  color: var(--dqz-bg, #0a0e1c);
}

/*
 * RESTATED AGAINST THE BUTTON'S OWN HOVER, which is more specific than the
 * rule above: `.dqz-btn--secondary:hover:not([disabled])` is (0,4,0) against
 * (0,3,0), because `:not([disabled])` carries the attribute selector's weight.
 * Without this the open toggle repaints as a closed one under a pointer.
 *
 * It matters more here than it did for `.dqz-nav__item.is-current`, which
 * needed the same restatement: this control only exists below the breakpoint,
 * where a tap leaves `:hover` applied until something else is touched — so
 * the open state would never be visible at all on the device it was built
 * for, while a keyboard user saw it correctly.
 */
.dqz-app .dqz-nav__toggle.is-open:hover:not([disabled]),
.dqz-app .dqz-nav__toggle.is-open:focus-visible {
  border-color: var(--dqz-accent, #00e0b8);
  background-color: var(--dqz-accent, #00e0b8);
  background-image: none;
  color: var(--dqz-bg, #0a0e1c);
}

/* ----- the one breakpoint ------------------------------------------------ */

@media (max-width: 639.98px) {
  /*
   * COLLAPSED AND CLOSED IS `display: none`, WHICH IS NOT THE SAME AS OFF
   * SCREEN. A nav moved out of view with `position: absolute` is still in the
   * accessible tree and still in the tab order, so a keyboard or a screen
   * reader player would walk straight into three tabs the toggle has just said
   * are closed. This takes them out of the tree entirely, which is what
   * `aria-expanded="false"` is promising on their behalf.
   *
   * READ OFF THE TOGGLE, NOT OFF A CLASS ON THE NAV. The toggle is in the
   * document only below the breakpoint and only when there is more than one tab
   * to collapse, and app.js inserts it immediately before the nav — so "there
   * is a toggle and it is closed" is the whole condition, held in one place
   * instead of mirrored into a second class that can disagree with it. Above the
   * breakpoint there is no toggle, so this cannot match however stale anything
   * else has become; break the adjacency and it stops matching too, which shows
   * the tabs rather than hiding them.
   *
   * `!important` for the reason the standing note gives about `[hidden]`: that
   * attribute is a user-agent `display: none`, and a theme carrying its own
   * `nav` display rule beats a user-agent one and pins the menu open. This
   * selector is (0,4,0) and already outranks a bare element rule, so the
   * `!important` is guarding the narrower case of a theme that scopes a display
   * of its own this far in — and `display` is the one property here where
   * losing the race is not a cosmetic difference but a menu that cannot be
   * closed.
   */
  .dqz-app .dqz-nav__toggle:not(.is-open) + .dqz-nav {
    display: none !important;
  }

  /*
   * The row has to be able to grow now, because the open panel is sized from
   * it: with section 18's `flex: 0 1 auto` the actions row is only as wide as
   * the toggle and the account button, and the menu beneath them would be that
   * narrow too.
   */
  .dqz-app .dqz-topbar__actions {
    flex: 1 1 auto;
  }

  /*
   * The open menu is a full-width row of its own IN THE FLOW, not an absolutely
   * positioned dropdown. The header is `position: sticky` inside an ancestor
   * with `overflow-x: clip` and a stacking context of its own; a panel that
   * escapes its parent is the thing most easily clipped, mis-stacked or pushed
   * sideways by a theme, and sideways is the axis the 400px rule protects.
   *
   * NO `order` HERE, AND THAT IS THE POINT. It carried `order: 1` so the
   * panel laid out after the account button while the DOM had it before —
   * which made a keyboard player tab from the toggle DOWN into the panel,
   * through the tabs, then back UP to a button sitting above them. The DOM
   * now puts the account button first instead, so the two orders agree and
   * nothing has to be reordered in the cascade.
   *
   * SCOPED TO A NAV THAT HAS A TOGGLE, for the same reason the hide rule
   * above is: with accounts switched off there is one tab and no toggle, and
   * these rules turned that single button into a full-width left-aligned row
   * under a divider — a layout change in a shipping configuration that has
   * no menu to open.
   */
  .dqz-app .dqz-nav__toggle + .dqz-nav {
    flex: 1 1 100%;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: var(--dqz-space-xs);
    margin-top: var(--dqz-space-xs);
    padding-top: var(--dqz-space-xs);
    border-top: 1px solid var(--dqz-hairline);
  }

  /* Full-width rows with their labels on a common left edge: three centred
     pills of different widths read as three unrelated controls. */
  .dqz-app .dqz-nav__toggle + .dqz-nav .dqz-nav__item {
    width: 100%;
    justify-content: flex-start;
  }

  /*
   * The current-location bar is inset to the label in a horizontal row, where
   * the control is only as wide as its text. In a full-width left-aligned row
   * that same 26% inset leaves it floating under the middle of the button with
   * nothing above it, so here it runs between the control's own paddings
   * instead. It is still the non-colour half of the current-page signal, which
   * is the only property of it that has to survive this.
   */
  .dqz-app .dqz-nav__item.is-current::after {
    right: calc(var(--dqz-button-padding-x, 22px) * 0.6);
    left: calc(var(--dqz-button-padding-x, 22px) * 0.6);
  }
}

/* --- forced colours ------------------------------------------------------ */

/*
 * The open fill flattens like every other accent here, and the glyph swap is
 * what is left — which is the right way round, since the shape was always the
 * primary signal. The border is restated so the toggle keeps an edge against
 * Canvas either way.
 */
@media (forced-colors: active) {
  .dqz-app .dqz-nav__toggle.is-open {
    border: 2px solid Highlight;
    background: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }
}
