/* ============================================================
   GameRoch — production adaptations (P01, revised in cycle 2)
   ============================================================
   This file is NOT approved design. It supplements css/base.css and
   css/model.css with rules for real product components those two do not
   cover at all, because the approved model was an inline widget inside
   someone else's page: it had no page canvas, no in-page game player, no
   offline banner, no loading skeletons and no toast.

   Loaded AFTER model.css, and every rule here is scoped either outside the
   product root or to an element the approved design does not contain. It
   must not override an approved colour, size, position or hierarchy.

   Cycle-2 audit: this file was the home of two high-severity visual
   defects, so every rule was re-examined and anything that fought the
   approved CSS rather than supplementing it was deleted. What was removed,
   and why, is recorded at the bottom of this file and in
   build/UI_BUILDER_REPORT.md §11.
   ============================================================ */

/* ---------- Page canvas (outside the product root) ----------
   The approved #gr-complete is background:transparent, because the model
   was embedded in a page that already had a colour. A real site has to
   paint its own canvas or the adult theme frames a dark app in white, and
   the pre-paint theme script would have nothing to colour. Strictly outside
   .gr-app; the QA harness neutralises it on both sides (normalization N4). */
html { background: #f3f6fb; }
html[data-theme="kids"]  { background: #fff8ee; }
html[data-theme="adult"] { background: #111b26; }

/* ---------- Page box ----------
   margin:0 because the UA's 8px body margin would inset the product root
   and make .gr-app 16px narrower than the approved max-width allows.
   padding:0 stated explicitly: the public app deliberately has NO page
   gutter, so .gr-app reaches its approved width at every viewport
   (viewport width up to max-width:1600px, then centred). qa/README.md L11
   puts the export wrapper's 16px page padding in the candidate QA wrapper,
   not here. background:transparent so the html canvas above shows through. */
body { margin: 0; padding: 0; background: transparent; }

/* ---------- Bold weight (P01 cycle 3, owner decision OD2) ----------
   The approved stylesheets declare no font-weight for b, strong or th at all,
   so a browser renders them at the UA default 700. The rendered fragment the
   owner approved does not: the review export's wrapper declares
   `b, strong, th { font-weight: var(--font-weight-medium) }` with
   --font-weight-medium: 500, and every <strong> in the approved picture is
   therefore 500.

   This is decided the opposite way to the icon-size conflict, deliberately.
   There, the approved PRODUCT source states 18px twice (base.css and
   initIcons()), so the wrapper's 16px !important was overriding something
   approved and QA restored 18px on both sides. Here the approved stylesheets
   are SILENT, so the only record of what was approved is the rendered
   fragment - and DESIGN_FREEZE asks for the product design byte-derived from
   the source, which for a value the stylesheets never state means the
   fragment's computed value. Shipping 700 would make every bold run in the
   product heavier than the design that was signed off.

   Measured cost of not doing this (QA diagnostic, both sides forced to 700):
   guest desktop meaningful diff 0.4253% -> 0.2021%, guest mobile 0.8205% ->
   0.5001%, home desktop 0.7161% -> 0.6823%, home mobile 1.0899% -> 1.0218%.

   Scoped under #gr-complete and deliberately at the lowest useful
   specificity, so every approved rule that does state a weight for a bare
   b/strong/th still wins: .gr-brand strong (700), .gr-stat strong (700),
   .br-review>strong (700), .dw-game strong (600), .model-runtime-header b
   (600), .model-rank>strong (600). That is exactly how the export's own rule
   behaves in the same cascade. QA confirmed it empirically in cycle 3: seven
   matched b/strong/th compared element by element on both sides, zero weight
   mismatches, and the 700s - including the .gr-brand wordmark - not
   flattened.

   P02, PLEASE READ BEFORE PORTING THE REMAINING SCREENS. The approved
   stylesheets DO state a weight at (1,0,1)/(1,0,0) for #gr-complete itself
   and for button / input / select / textarea / h1 / h2 / h3 - 14 of the 63
   weight-stating approved selectors. None of those can match a bare
   b/strong/th, so this rule is safe as things stand. But it means a <strong>
   nested inside an approved heading computes 500 here where the heading
   itself is heavier. No P01 surface has one; if a screen you port does, check
   it against the rendered fragment and, if the reference shows the heading's
   weight, add an approved-specificity rule for that case rather than
   weakening this one.

   This is a product change, not a QA normalization, and it is reversible by
   deleting this one rule. Flagged to the owner as OD2. */
#gr-complete b,
#gr-complete strong,
#gr-complete th { font-weight: 500; }

/* ---------- Loading states (truthful, never faked) ---------- */
#gr-complete .gr-skel {
  background: var(--gr-tint);
  border-radius: var(--gr-radius);
  animation: gr-pulse 1.4s ease-in-out infinite;
}
#gr-complete[data-motion="off"] .gr-skel { animation: none; }
@media (prefers-reduced-motion: reduce) { #gr-complete .gr-skel { animation: none; } }
@keyframes gr-pulse { 0%,100% { opacity: 1; } 50% { opacity: .55; } }
#gr-complete .gr-skel-card { aspect-ratio: 1 / 1; }
#gr-complete .gr-skel-line { height: 12px; margin-top: 8px; border-radius: 6px; }

/* ---------- Game player overlay ----------
   New surface: the approved model had no real player — its runtime was the
   simulated 4x4 grid DEMO_TO_PRODUCTION forbids shipping. display:none
   until it is opened, so it never occupies space in the shell. Styled from
   approved tokens only. */
#gr-complete .gr-gameplayer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  flex-direction: column;
  background: var(--gr-bg);
}
#gr-complete .gr-gameplayer[data-open="true"] { display: flex; }

/* ---------- P05 / GR-B1-13: the reserved status region ----------
   The defect this replaces was a layout OWNERSHIP defect, not a paint order
   one. The status region was

       #gr-complete .gr-gp-status { position:absolute; inset:auto 0 0 0; }

   so it was out of flow, nothing subtracted its height from the rectangle
   handed to the runtime, and it painted over whatever the game had drawn at
   the bottom of its viewport — for a quiz, the lower answer row.

   docs/RUNTIME_SCREENSHOT_FINDING.md forbids the cosmetic repairs: no blanket
   bottom padding, no global text shrink, no overflow:hidden, no z-index
   shuffle, and the save reporting is not removed. So the region is moved into
   the host chrome instead:

     .gr-gp-chrome  flex:0 0 auto   bar + status; laid out FIRST
     .gr-gp-stage   flex:1 1 auto   min-height:0; what is left is the runtime
     iframe         fills the stage

   The chrome reserves the status line whether or not there is a message
   (`min-height` on .gr-gp-status), so showing, changing or dismissing a
   status cannot resize the game — CATALOGUE_RUNTIME: success "must not resize
   the game unexpectedly". */
#gr-complete .gr-gameplayer .gr-gp-chrome {
  flex: 0 0 auto;
  background: var(--gr-panel);
  border-bottom: 1px solid var(--gr-line);
  padding-top: env(safe-area-inset-top, 0px);
}
#gr-complete .gr-gameplayer .gr-gp-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px 0;
}
#gr-complete .gr-gameplayer .gr-gp-bar strong {
  font-family: Outfit, 'DM Sans', Arial, sans-serif;
  font-size: 16px;
  letter-spacing: -.5px;
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#gr-complete .gr-gameplayer .gr-gp-bar > button { flex: 0 0 auto; }

/* The status region: its OWN row in the chrome, with a FIXED height.
   A fixed height is the whole repair. A min-height would still grow when a
   message wrapped to a second line or the Retry control appeared, and the
   stage would shrink under a game that had already laid itself out — which
   CATALOGUE_RUNTIME forbids ("must not resize the game unexpectedly") and
   which is how the reported defect would have come back in a new shape.
   Two lines are reserved below 700px and one above it, where the longest
   message this host can produce fits on one line with room to spare. The
   full text always stays in the DOM, so the polite live region announces it
   in full even where it is visually clamped, and `title` carries it to a
   pointer. */
#gr-complete .gr-gp-status {
  flex: 0 0 auto;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 4px 14px;
  font-size: 12px;
  line-height: 16px;
  color: var(--gr-sub);
  text-align: center;
  overflow: hidden;
}
#gr-complete .gr-gp-status > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  max-height: 32px;
}
@media (min-width: 700px) {
  #gr-complete .gr-gp-status { height: 26px; }
  #gr-complete .gr-gp-status > span { -webkit-line-clamp: 1; max-height: 16px; white-space: nowrap; }
}
#gr-complete .gr-gp-status[data-tone="pending"] { color: var(--gr-sub); }
#gr-complete .gr-gp-status[data-tone="success"] { color: var(--gr-ink); }
/* #be4141 is the approved .gr-error colour from base.css, reused rather than invented. */
#gr-complete .gr-gp-status[data-tone="error"]   { color: #be4141; }
#gr-complete .gr-gp-retry {
  flex: 0 0 auto;
  height: 22px;
  min-height: 22px;
  padding: 0 8px;
  font: inherit;
  font-size: 11px;
  line-height: 20px;
  border: 1px solid var(--gr-line);
  border-radius: 999px;
  background: var(--gr-bg);
  color: var(--gr-ink);
  cursor: pointer;
}

/* The stage is the ONE container the runtime is sized from. min-height:0 is
   load-bearing: without it a flex child refuses to shrink below its content
   and the iframe pushes the chrome off the top on a short window. */
#gr-complete .gr-gameplayer .gr-gp-stage {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: flex;
  position: relative;
  overflow: hidden;
  background: var(--gr-bg);
  /* The bottom inset is subtracted HERE and nowhere else, so it is never
     double-counted by the host and the game both. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}
#gr-complete .gr-gameplayer iframe {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  min-height: 0;
  min-width: 0;
  border: 0;
  display: block;
  background: var(--gr-bg);
}

/* A short window (the 1366x650 / 1280x600 rows of the test matrix, and any
   browser at 150-200% zoom) gives the chrome back to the game by tightening
   its own spacing — never by cropping the stage. The status row keeps a
   fixed height here too, so the rule above is not weakened. */
@media (max-height: 560px) {
  #gr-complete .gr-gameplayer .gr-gp-bar { padding: 6px 12px 0; }
  #gr-complete .gr-gameplayer .gr-gp-bar strong { font-size: 14px; }
  #gr-complete .gr-gp-status { height: 24px; padding: 2px 12px; }
  #gr-complete .gr-gp-status > span { -webkit-line-clamp: 1; max-height: 16px; }
}

/* ---------- Icon sprite sizing ----------
   The ported surfaces use Lucide <i data-lucide>, which the approved CSS
   already sizes (#gr-complete button svg,#gr-complete i svg{width:18px;
   height:18px}). The 46 routes P02 still owns use an inline <svg><use>
   sprite instead, which no approved selector matches. Same 18px box, same
   stroke, so the two mechanisms render identically side by side. */
#gr-complete .gr-ic {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- Toast ----------
   Transient status, a sibling of .gr-app rather than a child, opacity:0
   until shown, so it never occupies space in the product root. */
#gr-complete .gr-toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  z-index: 80;
  background: var(--gr-panel);
  border: 1px solid var(--gr-line);
  color: var(--gr-ink);
  border-radius: 99px;
  padding: 10px 18px;
  font-size: 13px;
  box-shadow: var(--gr-shadow, 0 8px 20px #17385914);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
}
#gr-complete .gr-toast[data-show="true"] { opacity: 1; }
#gr-complete[data-motion="off"] .gr-toast { transition: none; }

/* ---------- Links wearing an approved control class ----------
   The approved design has no <a> anywhere: every interactive label is a
   <button>, so `#gr-complete button{display:inline-flex;min-height:42px;
   line-height:1.25}` gives it its box. Production needs real links to real
   pages (Privacy, Terms, Support, Delete your account), and a bare inline <a>
   is both ~20px shorter than the approved control it replaces — which shifts
   the approved geometry below it — and smaller than the WCAG 2.5.8 24x24
   target minimum. This gives an <a> the same box the approved button has, and
   nothing else. No approved declaration is overridden: no approved selector
   matches an <a>. */
#gr-complete a.gr-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 42px;
  line-height: 1.25;
}
#gr-complete a.gr-link:focus-visible {
  outline: 3px solid var(--gr-blue);
  outline-offset: 2px;
}

/* ---------- Offline banner ----------
   S02 requires an offline state, not a toast: a state that disappears after
   three seconds is not a state. The approved model had no offline variant.
   [hidden] by default, so it occupies no space until the browser goes
   offline. Approved tokens only, no new colours. */
#gr-complete .gr-offline {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 23px;
  background: var(--gr-tint);
  border-bottom: 1px solid var(--gr-line);
  color: var(--gr-ink);
  font-size: 13px;
  line-height: 1.45;
}
#gr-complete .gr-offline[hidden] { display: none; }
#gr-complete .gr-offline .gr-ic { margin-top: 2px; }
@container gr-shell (max-width: 620px) {
  #gr-complete .gr-offline { padding: 11px 18px; font-size: 12px; }
}

/* ============================================================
   Deleted in cycle 2 — recorded so nothing is dropped silently
   ============================================================
   1. body{padding:24px} and @media(max-width:600px){body{padding:10px}},
      with the comment claiming they were "the reference page's own measured
      paddings". They were not: the export wrapper is body{padding:1rem} =
      16px at every width. The 24px/10px gutter made the public .gr-app
      1390px at a 1440 viewport (approved 1408 in the padded QA wrapper,
      1440 unpadded) and 392px at 414 (approved 414), which changes every
      column in a container-query layout. Defect D1. qa/README.md L11 puts
      the 16px mirroring in the candidate QA wrapper, not here.

   2. @container gr-shell (max-width:620px) { .gr-bottom{position:fixed;
      left:0;right:0;bottom:0;z-index:40;padding-bottom:calc(8px + …);
      box-shadow:…} .gr-main{padding-bottom:calc(92px + …)}
      .gr-app-footer{padding-bottom:calc(80px + …)} }
      The comment asserted that .gr-app's container-type:inline-size
      establishes a containing block for fixed descendants. It does not —
      inline-size containment is not a containing block for fixed
      positioning — so the bar pinned to the viewport, left the product root
      and spanned the full viewport width, while 172px of padding was
      reserved for a bar that was not in the flow. Defect D2. The approved
      rules stand on their own: .gr-bottom is display:none, becomes
      display:grid inside @container gr-shell (max-width:620px), and sits in
      flow at the bottom of .gr-app.

   3. #gr-complete .gr-main:focus{outline:none}. Dead: nothing focuses
      .gr-main, and if anything ever does it should show a focus indicator.

   4. #gr-complete .gr-app-footer a{...} and its :focus-visible partner.
      Dead once the approved footer text was restored (defect D4) — the
      approved footer has a span and a button, no links.

   5. #gr-complete .br-actionbar + .gr-note{margin:-16px 0 24px;…}. Dead
      once the added explanatory paragraph was removed (defect D6).

   6. -webkit-font-smoothing:antialiased and -webkit-tap-highlight-color:
      transparent on body. Both change how approved type and approved
      controls render, with no approved counterpart authorising it.

   7. #gr-complete .gr-visually-hidden{...}. Was dead: the approved shell
      markup has no visually-hidden label and nothing in the build used it.
      RESTORED in P02 cycle 8 with a live use — offBtn()'s reason element,
      defect D-P02-27. Re-added at the foot of this file rather than here,
      and kept under its original name rather than the `.gr-sr-only` the QA
      spec proposed, because the project already had a name for this and one
      concept should not acquire two.

   8. #gr-complete[data-offline=true] .gr-main [data-play]:disabled{cursor:
      not-allowed} and its [data-buy]/[data-equip] partners. Redundant:
      #gr-complete button:disabled{opacity:.65;cursor:not-allowed} is
      already approved.

   9. The five wide-screen @media{#gr-complete{zoom:1.10 … 1.75}} steps,
      added on 17 Sep 2026 after the owner reported the layout reading small
      on a large display. Removed, and this one needs an owner decision
      rather than my judgement:
        - the steps were measured against a build whose .gr-app was 1392px
          at a 1440 viewport, i.e. 48px narrower than it should have been
          because of the page padding deleted in item 1. Removing that
          padding already widens the app at every size, so the "fills ~97%
          of the window" table the steps were tuned to no longer holds;
        - a zoom on the product root scales every approved px value above
          1500px, which is a material change to approved geometry, and
          DESIGN_FREEZE requires the exact comparison first and then an
          owner design decision — neither of which exists for it;
        - P01-VISUAL captures at 320/360/390/414/768/1024/1440, so no case
          in the matrix exercised these steps and no evidence covers them.
      To restore verbatim:
        @media (min-width: 1500px) { #gr-complete { zoom: 1.10; } }
        @media (min-width: 1750px) { #gr-complete { zoom: 1.22; } }
        @media (min-width: 2000px) { #gr-complete { zoom: 1.38; } }
        @media (min-width: 2300px) { #gr-complete { zoom: 1.55; } }
        @media (min-width: 2800px) { #gr-complete { zoom: 1.75; } }
   ============================================================ */

/* ============================================================
   P02 — the confirmation dialog
   ============================================================
   NOT approved design. The approved model expressed every confirmation as a
   whole route, so the approved stylesheets define no dialog component. P02
   still needs one: SCREEN_CONTRACTS requires "focus entry/trap/restore and
   Escape/Cancel" for a modal confirmation, and P02-A11Y asks for "dialog
   cancel" — and the two consequential confirmations in the player build
   (purchase and delete) are worth an interruption that cannot be missed.

   Built entirely from approved tokens and approved classes: the card is a
   .gr-panel, its buttons are .gr-play / .gr-soft, and the only new
   declarations are the overlay, the centring and the width cap. It is
   display:none unless a dialog is actually open, so it adds nothing to any
   static capture. Same licence as the trusted game player above: a real
   product component the approved model had no equivalent of.
   ============================================================ */
.gr-modal {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(14, 22, 36, .55);
}
.gr-modal[data-open="true"] { display: flex; }
.gr-modal .gr-modal-card {
  max-width: 420px;
  width: 100%;
  margin: 0;
  max-height: calc(100vh - 32px);
  overflow: auto;
}
.gr-modal .gr-modal-card h2 { margin-top: 0; }
@media (prefers-reduced-motion: no-preference) {
  .gr-modal[data-open="true"] .gr-modal-card { animation: gr-modal-in .16s ease-out; }
}
@keyframes gr-modal-in { from { transform: translateY(8px); opacity: .6; } to { transform: none; opacity: 1; } }

/* ---------- The disabled control's reason, announced but not painted ----------
   D-P02-27. offBtn() used to carry its reason in `aria-label`, which REPLACES
   the accessible name: `report`'s primary announced a 316-character sentence
   where "Submit report" belonged. The reason now lives in a real element that
   `aria-describedby` points at, so the name is the label and the reason is a
   description.

   position:absolute is what buys the 0px. It takes the element out of flow, so
   it contributes no box — including inside .gr-buttons, where an absolutely
   positioned child is not a flex item. Coordinator ruling R2 relaxed the
   "stated in the UI" condition to "programmatically associated" on exactly this
   measurement: visible copy costs +51 to +161px on mobile against a frozen
   baseline, which would have failed the 15 cases the grant was for.

   clip and clip-path are both set: clip is deprecated but is what older screen
   readers key on, and clip-path is what current engines honour. Neither is
   display:none or visibility:hidden, because both of those remove the element
   from the accessibility tree and would defeat the whole point. */
#gr-complete .gr-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
}

/* ==================================================================
   OWNER DIRECTION — first smoke review, 25 September 2026
   ------------------------------------------------------------------
   Everything below supersedes the approved design on the owner's
   instruction, which is first in the authority order. It lives HERE and
   not in base.css or model.css deliberately: those two are the frozen
   approved stylesheets, the build asserts they are unchanged since P01,
   and an override that loads after them changes what ships without
   breaking that assertion or the byte-derivation the freeze is for.

   EVERY PRODUCT SELECTOR BELOW CARRIES `#gr-complete`. The approved
   stylesheets are written as `#gr-complete .gr-app{…}`, so a bare class
   loses on specificity and silently does nothing — which is exactly what
   the first version of this block did: `.gr-app{max-width:none}` sat in
   the cascade, parsed and ignored, while the app stayed 1600px wide.

   The cost is recorded rather than left to be discovered. P10-PARITY
   compares 49 routes against the frozen reference; the shell is on all
   49, so that comparison no longer measures what it measured. The
   reference has to be re-issued before 117/294 means anything again.
   ================================================================== */

/* ---------- Item 7: the desktop layout did not fill the screen ------
   base.css ships `#gr-complete .gr-app{max-width:1600px;margin:auto}`.
   Measured on the owner's own window: at 1870px the app was 1600px with
   a 135px gutter either side, and on a 2560px monitor it would be 480px
   either side — an app floating in a sea of page canvas, which is what
   "layout pura nahi aa raha hai" describes.

   max-width:none lets it fill any width. The 16px body gutter keeps the
   rounded corner and the border reading as an edge rather than clipping
   flush against the viewport, and 8px under 600px gives a phone its
   screen back. Behaviour below 1600px is unchanged. */
body { padding: 16px; }
@media (max-width: 600px) { body { padding: 8px; } }
#gr-complete .gr-app { max-width: none; }

/* The same gap vertically, which the first pass missed. The app box is
   only as tall as its content, so a short route — `account` is the one
   the owner was looking at — ended a third of the way down a 1090px
   window with page canvas under it. min-height makes the shell reach the
   bottom whatever the route holds; `dvh` so a phone's retracting URL bar
   does not leave a strip, with `vh` under it for engines that lack it. */
#gr-complete .gr-app { min-height: calc(100vh - 32px); }
#gr-complete .gr-app { min-height: calc(100dvh - 32px); }
@media (max-width: 600px) {
  #gr-complete .gr-app { min-height: calc(100dvh - 16px); }
}

/* ---------- Item 4: the sidebar is one flat list now ----------------
   The two `.gr-navlabel` headings are gone from the markup, so the
   spacing they provided has to come from somewhere. Thirteen items at
   the approved item height is taller than the old 4+4+4, so the column
   scrolls on a short window rather than pushing the side note out of
   view. */
#gr-complete .gr-sidebar {
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---------- Items 5 and 6: the header controls ----------------------
   Sized from the approved header tokens rather than new values, so they
   sit on the same baseline as the theme button and the player chip. */
#gr-complete .gr-header-actions { gap: 8px; }

#gr-complete .gr-signin {
  min-height: 38px;
  padding: 0 14px;
  border-radius: 12px;
  font-weight: 650;
  font-size: 13px;
  white-space: nowrap;
  /* NOT #fff, and the value here was chosen by measuring all four
     candidates against the accent in all three themes rather than by
     picking one that looked right. The first draft of this rule said
     #fff and carried invented ratios in this comment; #fff on After
     Hours' mint accent (#90d6c5) is 1.67:1, so the fix for item 5
     would have shipped a 23rd contrast failure. The second draft used
     --gr-bg and missed AA in Everyday Blue by 0.01.

     Measured, text on --gr-blue, Blue / After Hours / Playground:
       #fff          4.86  1.67  5.31   <- fails After Hours
       --gr-bg       4.49  10.43 5.03   <- fails Blue by 0.01
       --gr-ink      3.15  1.54  2.48   <- fails all three
       --gr-panel    4.86  9.10  5.22   <- passes AA in all three
     A token, not three hard-coded colours, so a fourth theme inherits
     a passing pair instead of silently inheriting a failing one. */
  color: var(--gr-panel);
  background: var(--gr-blue);
}
#gr-complete .gr-signin:hover { background: var(--gr-blue); filter: brightness(1.08); }

#gr-complete .gr-app-links { display: inline-flex; gap: 6px; }
#gr-complete .gr-applink {
  display: inline-flex;
  align-items: center;
  min-height: 38px;
  padding: 0 12px;
  border-radius: 12px;
  border: 1px solid var(--gr-line);
  background: var(--gr-panel);
  color: var(--gr-ink);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
#gr-complete .gr-applink:hover { background: var(--gr-tint); }
#gr-complete .gr-applink { gap: 6px; }
#gr-complete .gr-applink svg { width: 15px; height: 15px; flex: none; opacity: .8; }

/* Under the container breakpoint the header is already tight, so the
   store buttons step aside first and the sign-in control stays: signing
   in is what a phone visitor is most likely to need, and the store
   listing is reachable from the phone's own app store anyway. */
@container gr-shell (max-width: 900px) {
  #gr-complete .gr-app-links { display: none; }
}
@container gr-shell (max-width: 620px) {
  #gr-complete .gr-signin { padding: 0 10px; font-size: 12px; }
}

/* ---------- Owner smoke item 3: the identity row sat ON the cover ----
   Two rounds, and the second one is the owner correcting the first.

   ROUND 1 measured at 1960x1090: cover bottom 249, name top 243 — the
   name was 6px INSIDE the cover, so the ascenders of "P" and "l"
   crossed the banner and the text read as printed on the blue. base.css
   pulls the whole identity row up into the cover:

     .gr-profile-id{display:flex;align-items:center;margin:-35px 22px 0}

   I kept the -35px on the AVATAR, on the reasoning that an avatar
   straddling a cover is the approved look and only the name was wrong.

   ROUND 2, the owner, looking at the result: "yeh jo A square hai bhi
   overlap ho raha hai isko blue waale pr overlap mt karo ... isko
   middle m rakho kaafi jagah hai". He does not want the straddle at
   all. He wants the square sitting in the white band between the cover
   and the stats. So the premise of round 1 was wrong, not just its
   scope, and the -35px is gone entirely.

   WHY IT IS NOT SIMPLY "CENTRED". Measured: the white band was 96px and
   the avatar is 85px, so centring inside it leaves 6px top and bottom —
   which reads as the square jammed between two edges, not placed in a
   gap. The row is given a real margin instead and the band grows with
   it, so the spacing above and below the square is equal AND visible.

   `align-items:center` is restored and is correct again: it was only a
   defect while the row was being dragged into the banner. With the row
   below the cover it does what it was written to do — centre the name
   against the square it belongs to. */
#gr-complete .gr-profile-id {
  margin-top: 20px;
  align-items: center;
}
#gr-complete .gr-profile-id > .gr-avatar {
  margin-top: 0;
}
/* The approved h1 carries a 25px top margin from the generic heading
   rule. Inside this row it pushed the name away from the avatar it
   belongs to and broke the centring above. */
#gr-complete .gr-profile-id h1 { margin-top: 0; }

@container gr-shell (max-width: 620px) {
  /* base.css uses -33px and a 75px avatar on the narrow layout; the
     same reasoning applies, with a smaller band to work in. */
  #gr-complete .gr-profile-id { margin-top: 14px; }
  #gr-complete .gr-profile-id > .gr-avatar { margin-top: 0; }
}

/* ==========================================================================
   F-P10-05 — the eight reachable WCAG AA contrast failures
   --------------------------------------------------------------------------
   WHY THIS IS BEING DONE NOW AND WAS NOT BEFORE. P10 measured 22 colour pairs
   below the 4.5:1 AA threshold and deliberately changed none of them, for a
   reason it stated: "changing a token would move every one of the 294
   baselines, which is precisely what this phase must not do." The owner has
   since retired P10-PARITY outright — "purane design se kabhi compare krne ki
   zarurat nahi hai ... hum update krte rahenge" — so that reason no longer
   exists, and what is left is an accessibility defect with a measured fix.

   NOTHING FROZEN IS EDITED. base.css line 9 still holds the approved tokens
   byte for byte; these are overrides in the production layer, the same place
   every post-freeze change has gone, so `frozenStylesheetsUnchangedSinceP01`
   stays true.

   THE VALUES WERE RE-DERIVED, NOT COPIED FROM THE P10 TABLE. That table came
   from p10-contrast-advice.cjs, which searches a CONTINUOUS colour axis and
   rounds the winner to hex at the end — so it can return a colour that does
   not actually pass once written down. It did: it offered #e8f1ff for pair H
   at "ratioAfter 4.49". p10/contrast-fix.cjs walks real 8-bit colours and
   checks each as a stylesheet would hold it. It also found that pair B, which
   the P10 table called unreachable without darkening the background, is
   reachable after all — one value on one channel.

   EACH TOKEN IS CHECKED AGAINST EVERY BACKGROUND IT LANDS ON, not just the
   worst pair, because --gr-blue and --gr-sub each serve several surfaces and a
   fix that passes one while failing another is not a fix. Measured, before ->
   after, all four surfaces per token, in p10/runs/contrast-fix.json.

   Every one of these darkens or lightens AWAY from its background, so no
   contrast anywhere gets worse: white-on-blue buttons gain, the sign-in
   control measured in the smoke round gains, and the hero text gains at both
   ends of its gradient.
   ========================================================================== */

/* A + B. --gr-blue on --gr-tint was 4.29, on --gr-bg 4.49.
   #2469ed -> #2464ec : tint 4.51, bg 4.72, panel 5.11. */
#gr-complete { --gr-blue: #2464ec; }

/* C + D. Playground's accent on its tint was 4.47, on the soft button 4.42.
   #7854ce -> #7752ce : soft 4.51, tint 4.56, bg 5.13, panel 5.32. */
#gr-complete[data-theme=kids] { --gr-blue: #7752ce; }

/* E + F. Playground's sub on its tint was 4.35, on the wallet card 4.39.
   #796780 -> #78647f : tint 4.50, card 4.54, bg 5.07, panel 5.26. */
#gr-complete[data-theme=kids] { --gr-sub: #78647f; }

/* G. The hero eyebrow on Everyday Blue's gradient was 4.13 at the light end —
   the worst of the solid-text cases, and the only one needing more than a
   nudge. #cce5ff -> #cff1ff : light end 4.51, dark end 4.95.

   MEASURED ON PAINTED PIXELS, NOT ON THE TOKEN. The text sits on a gradient,
   so there is no single backdrop colour to compute against; #1963e2 is the
   backdrop the browser actually painted under the fully-covered glyph pixels
   at the gradient's light end, which is where the text is worst off. */
#gr-complete .gr-hero .gr-eyebrow { color: #cff1ff; }

/* H. The hero paragraph, same gradient, was 4.49 — short by 0.01.
   #e7f1ff -> #e7f2ff : light end 4.51, dark end 5.19. */
#gr-complete .gr-hero p { color: #e7f2ff; }

/* I + J ARE NOT FIXED HERE, AND CANNOT BE FIXED BY A TEXT COLOUR.
   On Playground's hero gradient the painted light end is #8b63e0, against
   which even pure white reaches only 3.9:1. The two rules above lift the
   eyebrow from 3.27 to 3.57 and the paragraph from 3.54 to 3.56 — better, and
   still short of 4.5. The lever is the gradient, not the text: --gr-hero2
   #b18bef would need to go about 13% darker (~#9a79cf), which is a visible
   change to the most prominent element on the home screen and therefore an
   owner decision, with the comparison rendered rather than described. */

/* D, PROPERLY. The first pass of this block overrode `--gr-blue` for kids and
   the a11y runner still reported "Favourited" at 4.42 in `rgb(120, 84, 206)` —
   the OLD value. The detail page does not use `--gr-blue` at all: base.css:56
   gives the Playground theme its own brand-layer set, in which `--br-accent`
   is DECLARED as #7854ce rather than inheriting, while the blue and After
   Hours themes map `--br-accent` to `var(--gr-blue)` and so were already
   carried by the override above.

   Same value, same measurement: on --br-soft #eee7ff it goes 4.42 -> 4.51.
   `--br-accent` is also the primary button's BACKGROUND with white text on it,
   which darkening can only improve. */
#gr-complete[data-theme=kids] { --br-accent: #7752ce; }

/* ==========================================================================
   QUIZ NOVA — the question screen, played like a game
   --------------------------------------------------------------------------
   The Product Book's `quizPlay` layout is still the skeleton — "Life/status
   row; large question; 2x2 answer grid; explanation after answer" — and its
   edge case still rules every size here: "all controls visible at short
   heights". What changed is how it FEELS, after the owner's second smoke
   review called the first version "bohot basic": a stage instead of a form,
   big lettered answer tiles in four colours, a progress bar, hearts, a streak
   flame that heats up, the value of the next answer shown before it is given,
   a verdict banner, and a results screen with stars.

   ONE-OFF EFFECTS ARE KEYED, NOT AUTOMATIC. The screen is redrawn for
   unrelated reasons (a balance arriving, a dictionary loading), and a CSS
   animation restarts whenever its element is redrawn. So nothing celebratory
   is attached to a STATE class: it hangs off `data-fx` on the stage, which the
   renderer sets only on the paint that immediately follows the event.

   COLOUR IS NEVER THE ONLY SIGNAL. Every answered tile also carries a word
   ("Correct answer", "Your answer", "REMOVED") and an icon, the lives are
   counted in an aria-label, and the verdict is a sentence.

   CONTRAST. Every text pair below was measured at WCAG AA (4.5:1) in all
   three themes; the four key colours carry white 15px bold letters, so they
   are the darker shades of their hues (5.0–6.3:1), not the brighter ones.
   ========================================================================== */

#gr-complete .gr-quiz-stage,
#gr-complete .gr-quiz-results {
  --qz-good: #1f9d63; --qz-good-bg: #e6f7ee; --qz-good-ink: #134a30;
  --qz-bad: #d64545; --qz-bad-bg: #fdeeed; --qz-bad-ink: #7a2323;
  --qz-heart: #e5484d;
  --qz-gold: #f5b300;
}
#gr-complete[data-tone=dark] .gr-quiz-stage,
#gr-complete[data-tone=dark] .gr-quiz-results {
  --qz-good: #3fbf82; --qz-good-bg: #163a2a; --qz-good-ink: #c7f1dc;
  --qz-bad: #ec6a6a; --qz-bad-bg: #3d2121; --qz-bad-ink: #f8cfcf;
  --qz-heart: #ff6b72;
}

/* ---- the stage ---------------------------------------------------------- */
#gr-complete .gr-quiz-stage {
  position: relative; display: grid; gap: 14px;
  padding: 18px 20px 20px; overflow: clip;
  background: var(--gr-panel);
  background: radial-gradient(120% 65% at 0% 0%,
    color-mix(in srgb, var(--gr-blue) 7%, var(--gr-panel)) 0%, var(--gr-panel) 60%);
  transition: border-color .3s ease, box-shadow .3s ease;
}
/* Ten in a row is the scoring's ceiling — the whole stage says so. */
#gr-complete .gr-quiz-stage[data-tier="3"] {
  border-color: #ff8a8a;
  box-shadow: 0 0 0 3px #ff8a8a2e, 0 10px 30px #ff6b6b1f;
}
#gr-complete .gr-quiz-stage[data-fx=right] { animation: gr-qz-ring-good .7s ease-out; }
#gr-complete .gr-quiz-stage[data-fx=wrong] { animation: gr-qz-ring-bad .7s ease-out; }
@keyframes gr-qz-ring-good { from { box-shadow: 0 0 0 0 #1f9d6366; } to { box-shadow: 0 0 0 16px #1f9d6300; } }
@keyframes gr-qz-ring-bad { from { box-shadow: 0 0 0 0 #d6454566; } to { box-shadow: 0 0 0 16px #d6454500; } }

/* ---- top bar: topic, draft chip, question language, leave -------------- */
#gr-complete .gr-quiz-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
#gr-complete .gr-quiz-topic {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%;
  font: 700 15px/1.25 Outfit, Arial, sans-serif; color: var(--gr-ink);
}
#gr-complete .gr-quiz-topic svg { color: var(--gr-blue); }
#gr-complete .gr-quiz-topic > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#gr-complete .gr-quiz-chip {
  font-size: 10px; font-weight: 700; padding: 4px 9px; border-radius: 999px;
  background: #fff1c2; color: #5c4400;
}
#gr-complete[data-tone=dark] .gr-quiz-chip { background: #4a3b10; color: #ffe08a; }
#gr-complete .gr-quiz-top-actions { margin-left: auto; display: inline-flex; gap: 6px; }
#gr-complete .gr-quiz-top-actions button {
  min-height: 36px; padding: 6px 12px; border-radius: 999px; font-size: 12px; font-weight: 700;
}

/* ---- progress ----------------------------------------------------------- */
#gr-complete .gr-quiz-progress { height: 8px; border-radius: 99px; background: var(--gr-line); overflow: hidden; }
#gr-complete .gr-quiz-progress span {
  display: block; height: 100%; min-width: 8px; border-radius: inherit;
  background: linear-gradient(90deg, var(--gr-blue), var(--gr-hero2));
}
#gr-complete .gr-quiz-progress.is-moving span { animation: gr-qz-grow .55s cubic-bezier(.2,.8,.2,1) both; }
@keyframes gr-qz-grow { from { width: var(--from, 0%); } }

/* ---- status: lives, score, streak, position ---------------------------- */
#gr-complete .gr-quiz-status { display: flex; align-items: stretch; gap: 8px; flex-wrap: wrap; }
#gr-complete .gr-quiz-lives,
#gr-complete .gr-quiz-meta {
  border-radius: 14px; background: var(--gr-bg); border: 1px solid var(--gr-line);
}
#gr-complete .gr-quiz-lives { display: flex; align-items: center; gap: 3px; padding: 7px 10px; }
#gr-complete .gr-quiz-lives .gr-life { display: inline-flex; color: var(--qz-heart); }
#gr-complete .gr-quiz-lives .gr-life svg { width: 21px; height: 21px; fill: currentColor; }
/* A spent life keeps its place (the row never reflows, and "how many did I
   start with" is never a guess) and turns into an outline of a broken heart. */
#gr-complete .gr-quiz-lives .gr-life-gone { color: var(--gr-sub); opacity: .55; }
#gr-complete .gr-quiz-lives .gr-life-gone svg { fill: none; }
#gr-complete .gr-quiz-lives .gr-life-breaking { animation: gr-qz-heartbreak .65s ease-out both; }
@keyframes gr-qz-heartbreak {
  0%   { transform: scale(1.45); color: var(--qz-heart); opacity: 1; }
  35%  { transform: scale(1.15) rotate(-14deg); }
  60%  { transform: scale(1) rotate(9deg); }
  100% { transform: none; }
}

#gr-complete .gr-quiz-meta {
  position: relative; display: grid; align-content: center; gap: 1px;
  min-width: 70px; padding: 5px 12px; text-align: center;
}
#gr-complete .gr-quiz-score { margin-left: auto; }
#gr-complete .gr-quiz-meta b {
  font: 700 20px/1.1 Outfit, Arial, sans-serif; color: var(--gr-ink);
  font-variant-numeric: tabular-nums;
}
#gr-complete .gr-quiz-meta small {
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  font-size: 10px; font-weight: 600; white-space: nowrap;
}
#gr-complete .gr-quiz-meta small svg { width: 12px; height: 12px; }

/* "+170" rises off the score. The number itself never animates — a player
   (and a test) reads its true value at every moment. */
#gr-complete .gr-quiz-pop {
  position: absolute; left: 50%; top: -4px; pointer-events: none; white-space: nowrap;
  font: 800 15px/1 Outfit, Arial, sans-serif; color: var(--qz-good);
  animation: gr-qz-pop 1s ease-out forwards;
}
@keyframes gr-qz-pop {
  0%   { opacity: 0; transform: translate(-50%, 8px) scale(.7); }
  18%  { opacity: 1; transform: translate(-50%, -4px) scale(1.2); }
  100% { opacity: 0; transform: translate(-50%, -28px) scale(1); }
}

/* The streak flame heats up at 3, 5 and 10 in a row. */
#gr-complete .gr-quiz-streak[data-tier="1"] { background: #fff4e6; border-color: #ffc078; }
#gr-complete .gr-quiz-streak[data-tier="2"] {
  background: linear-gradient(180deg, #fff4e6, #ffe3c2); border-color: #ff922b;
  box-shadow: 0 0 0 3px #ff922b2b;
}
#gr-complete .gr-quiz-streak[data-tier="3"] {
  background: linear-gradient(135deg, #fff0f3, #ffe0e0); border-color: #ff6b6b;
  box-shadow: 0 0 0 3px #ff6b6b33, 0 6px 16px #ff6b6b33;
  animation: gr-qz-glow 1.6s ease-in-out infinite;
}
#gr-complete .gr-quiz-streak:not([data-tier="0"]) b,
#gr-complete .gr-quiz-streak:not([data-tier="0"]) small { color: #8a3b0a; }
#gr-complete .gr-quiz-streak[data-tier="3"] b,
#gr-complete .gr-quiz-streak[data-tier="3"] small { color: #9c1c1c; }
#gr-complete .gr-quiz-streak:not([data-tier="0"]) small svg { color: #e8590c; fill: #ffd8a8; }
#gr-complete .gr-quiz-streak[data-tier="3"] small svg { color: #e03131; fill: #ffc9c9; }
#gr-complete[data-tone=dark] .gr-quiz-streak[data-tier="1"],
#gr-complete[data-tone=dark] .gr-quiz-streak[data-tier="2"] { background: #3b2a16; border-color: #b8702a; }
#gr-complete[data-tone=dark] .gr-quiz-streak[data-tier="3"] { background: #46202a; border-color: #e06b6b; }
#gr-complete[data-tone=dark] .gr-quiz-streak:not([data-tier="0"]) b,
#gr-complete[data-tone=dark] .gr-quiz-streak:not([data-tier="0"]) small { color: #ffd0a0; }
#gr-complete[data-tone=dark] .gr-quiz-streak[data-tier="3"] b,
#gr-complete[data-tone=dark] .gr-quiz-streak[data-tier="3"] small { color: #ffc2c2; }
#gr-complete[data-tone=dark] .gr-quiz-streak:not([data-tier="0"]) small svg { fill: none; }
@keyframes gr-qz-glow {
  0%, 100% { box-shadow: 0 0 0 3px #ff6b6b33, 0 6px 16px #ff6b6b33; }
  50%      { box-shadow: 0 0 0 5px #ff6b6b40, 0 8px 22px #ff6b6b55; }
}

/* ---- the question ------------------------------------------------------- */
#gr-complete .gr-quiz-qcard {
  display: grid; gap: 8px; padding: 16px 18px; border-radius: 18px;
  background: var(--gr-bg); border: 1px solid var(--gr-line);
}
#gr-complete .gr-quiz-qhead { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 22px; }
#gr-complete .gr-quiz-qnum {
  font-size: 11px; font-weight: 700; letter-spacing: .8px; text-transform: uppercase; color: var(--gr-sub);
}
#gr-complete .gr-quiz-worth {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 99px;
  font-size: 11px; font-weight: 700; color: var(--gr-blue); background: var(--gr-tint);
}
#gr-complete .gr-quiz-worth svg { width: 13px; height: 13px; }
/* THE SELECTOR CARRIES `.gr-quiz-play` FOR SPECIFICITY: base.css has
   `#gr-complete .gr-panel p { font-size: 12px }`, and a plain one-class
   selector lost to it — the question once rendered at 13px. */
#gr-complete .gr-quiz-play .gr-quiz-prompt {
  font: 700 clamp(19px, 2.3cqw, 28px)/1.3 Outfit, Arial, sans-serif;
  color: var(--gr-ink); margin: 0;
}
#gr-complete .gr-quiz-play .gr-quiz-prompt:focus { outline: none; }
#gr-complete .gr-quiz-play .gr-quiz-prompt:focus-visible { outline: 2px solid var(--gr-blue); outline-offset: 4px; border-radius: 6px; }
#gr-complete .gr-quiz-play .gr-quiz-fellback { margin: 0; font-size: 11px; }
#gr-complete .gr-quiz-qcard.is-entering { animation: gr-qz-in .32s ease-out both; }
@keyframes gr-qz-in { from { opacity: 0; transform: translateY(10px); } }

/* ---- the four answers --------------------------------------------------- */
#gr-complete .gr-quiz-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
#gr-complete .gr-quiz-option {
  --key: #6741d9;
  position: relative; justify-content: flex-start; gap: 12px; text-align: start;
  min-height: 60px; padding: 9px 14px 9px 10px; border-radius: 16px;
  border: 2px solid var(--gr-line); border-bottom-width: 4px;
  background: var(--gr-panel); color: var(--gr-ink);
  font-size: 14px; font-weight: 600; line-height: 1.3;
  white-space: normal; overflow-wrap: anywhere;
  transition: transform .12s ease, border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
#gr-complete .gr-quiz-option:nth-child(2) { --key: #1971c2; }
#gr-complete .gr-quiz-option:nth-child(3) { --key: #c2410c; }
#gr-complete .gr-quiz-option:nth-child(4) { --key: #c2255c; }
#gr-complete .gr-quiz-key {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;
  background: var(--key); color: #fff; font: 800 15px/1 Outfit, Arial, sans-serif;
  box-shadow: inset 0 -3px 0 #00000029;
}
#gr-complete .gr-quiz-key svg { width: 18px; height: 18px; stroke-width: 3; }
#gr-complete .gr-quiz-optext { flex: 1; min-width: 0; }
#gr-complete .gr-quiz-option:hover:not(:disabled) {
  background: var(--gr-panel); border-color: var(--key);
  transform: translateY(-2px); box-shadow: 0 8px 18px #1a1a3a1f;
}
#gr-complete .gr-quiz-option:active:not(:disabled) { transform: translateY(2px); border-bottom-width: 2px; box-shadow: none; }
#gr-complete .gr-quiz-option:focus-visible { outline: 3px solid var(--key); outline-offset: 2px; }
#gr-complete .gr-quiz-option:disabled { cursor: default; opacity: 1; }
#gr-complete .gr-quiz-grid.is-entering .gr-quiz-option {
  animation: gr-qz-tile-in .3s cubic-bezier(.2,.9,.3,1.25) both;
  animation-delay: calc(var(--i, 0) * 45ms + 60ms);
}
@keyframes gr-qz-tile-in { from { opacity: 0; transform: scale(.93); } }

/* The answered states. Each carries its WORD as well as its colour. */
#gr-complete .gr-quiz-opstate {
  margin-left: auto; flex: none; padding: 3px 8px; border-radius: 99px;
  font-size: 10px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
  background: #ffffffb8; color: inherit;
}
#gr-complete .gr-quiz-option.gr-quiz-right {
  border-color: var(--qz-good); background: var(--qz-good-bg); color: var(--qz-good-ink);
}
#gr-complete .gr-quiz-option.gr-quiz-right .gr-quiz-key { background: var(--qz-good); }
#gr-complete .gr-quiz-option.gr-quiz-wrong {
  border-color: var(--qz-bad); background: var(--qz-bad-bg); color: var(--qz-bad-ink);
}
#gr-complete .gr-quiz-option.gr-quiz-wrong .gr-quiz-key { background: var(--qz-bad); }
#gr-complete[data-tone=dark] .gr-quiz-opstate { background: #0000004d; }
/* The two answers nobody chose step back, but stay readable. */
#gr-complete .gr-quiz-option.gr-quiz-dim { opacity: .55; }
/* Removed by 50:50: still there to read, visibly struck out — the guide's
   "dimmed in place and labelled REMOVED". Colour, not opacity, so the label
   keeps its contrast. */
#gr-complete .gr-quiz-option.gr-quiz-gone {
  border-style: dashed; border-bottom-width: 2px; background: var(--gr-bg); color: var(--gr-sub);
}
#gr-complete .gr-quiz-option.gr-quiz-gone .gr-quiz-key { background: var(--gr-line); color: var(--gr-sub); box-shadow: none; }
#gr-complete .gr-quiz-option.gr-quiz-gone .gr-quiz-optext { text-decoration: line-through; }
#gr-complete .gr-quiz-option.gr-quiz-gone .gr-quiz-opstate { background: var(--gr-line); color: var(--gr-ink); }

#gr-complete .gr-quiz-stage[data-fx=right] .gr-quiz-option.gr-quiz-right { animation: gr-qz-bounce .5s cubic-bezier(.2,1.5,.4,1); }
#gr-complete .gr-quiz-stage[data-fx=wrong] .gr-quiz-option.gr-quiz-wrong { animation: gr-qz-shake .45s ease-in-out; }
#gr-complete .gr-quiz-stage[data-fx=fifty] .gr-quiz-option.gr-quiz-gone { animation: gr-qz-poof .4s ease-out; }
@keyframes gr-qz-bounce { 0% { transform: scale(1); } 40% { transform: scale(1.045); } 100% { transform: scale(1); } }
@keyframes gr-qz-shake {
  0%, 100% { transform: translateX(0); } 20% { transform: translateX(-8px); }
  40% { transform: translateX(7px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(2px); }
}
@keyframes gr-qz-poof { 0% { transform: scale(1); } 45% { transform: scale(.95); } 100% { transform: none; } }

/* ---- after an answer: the verdict, the explanation, Next ---------------- */
#gr-complete .gr-quiz-after {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px 16px;
  padding: 14px 16px; border-radius: 16px;
  border: 2px solid var(--qz-good); background: var(--qz-good-bg); color: var(--qz-good-ink);
  scroll-margin-bottom: 12px;
}
#gr-complete .gr-quiz-after[data-verdict=wrong] { border-color: var(--qz-bad); background: var(--qz-bad-bg); color: var(--qz-bad-ink); }
#gr-complete .gr-quiz-stage[data-fx=right] .gr-quiz-after,
#gr-complete .gr-quiz-stage[data-fx=wrong] .gr-quiz-after { animation: gr-qz-in .3s ease-out both; }
#gr-complete .gr-quiz-verdict { grid-column: 1; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
#gr-complete .gr-quiz-verdict-icon {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
  background: var(--qz-good); color: #fff;
}
#gr-complete .gr-quiz-after[data-verdict=wrong] .gr-quiz-verdict-icon { background: var(--qz-bad); }
#gr-complete .gr-quiz-verdict-icon svg { width: 19px; height: 19px; stroke-width: 3; }
#gr-complete .gr-quiz-verdict b { font: 800 19px/1.1 Outfit, Arial, sans-serif; }
#gr-complete .gr-quiz-verdict > span:last-child { font-size: 13px; font-weight: 700; }
#gr-complete .gr-quiz-stage .gr-quiz-after .gr-quiz-expl { grid-column: 1; margin: 0; font-size: 13px; line-height: 1.5; color: inherit; }
#gr-complete .gr-quiz-after > .gr-play {
  grid-column: 2; grid-row: 1 / span 2; min-height: 48px; padding: 12px 20px; border-radius: 14px; font-size: 14px;
}

/* ---- lifelines ---------------------------------------------------------- */
#gr-complete .gr-quiz-lifelines { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#gr-complete .gr-quiz-lifelines > button {
  min-height: 40px; padding: 6px 12px 6px 10px; border-radius: 999px; gap: 7px; font-size: 12px; font-weight: 700;
}
#gr-complete .gr-quiz-uses {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 99px;
  background: var(--gr-blue); color: var(--gr-on); font-size: 11px; font-weight: 800;
}
#gr-complete .gr-quiz-uses.is-empty { min-width: 0; padding: 0; background: none; color: inherit; font-weight: 600; }
/* Only where there is a keyboard to press them, and room to say so. */
#gr-complete .gr-quiz-keys { display: none; margin-left: auto; font-size: 11px; color: var(--gr-sub); }
@media (hover: hover) and (pointer: fine) {
  @container gr-shell (min-width: 900px) { #gr-complete .gr-quiz-keys { display: inline; } }
}

/* ---- the result --------------------------------------------------------- */
#gr-complete .gr-quiz-results {
  position: relative; overflow: clip; display: grid; justify-items: center; gap: 12px;
  padding: 26px 22px 22px; text-align: center;
}
#gr-complete .gr-quiz-results h1:focus { outline: none; }
#gr-complete .gr-quiz-stars { display: flex; align-items: flex-end; gap: 4px; }
#gr-complete .gr-quiz-stars span { display: inline-grid; color: var(--gr-line); }
#gr-complete .gr-quiz-stars svg { width: 44px; height: 44px; fill: currentColor; stroke-width: 1.4; }
#gr-complete .gr-quiz-stars span:nth-child(2) svg { width: 58px; height: 58px; }
#gr-complete .gr-quiz-stars span.is-on { color: var(--qz-gold); filter: drop-shadow(0 3px 0 #c98f0040); }
#gr-complete .gr-quiz-results[data-fx=end] .gr-quiz-stars span.is-on {
  animation: gr-qz-star .55s cubic-bezier(.2,1.6,.4,1) both;
  animation-delay: calc(var(--k, 0) * 170ms + 120ms);
}
@keyframes gr-qz-star { from { opacity: 0; transform: scale(.2) rotate(-40deg); } }
#gr-complete .gr-quiz-results .gr-quiz-res-sub { margin: 0; font-size: 13px; }
#gr-complete .gr-quiz-res-score { display: grid; }
#gr-complete .gr-quiz-res-score b { font: 800 50px/1 Outfit, Arial, sans-serif; color: var(--gr-ink); font-variant-numeric: tabular-nums; }
#gr-complete .gr-quiz-res-score small { font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; }
#gr-complete .gr-quiz-results .gr-quiz-newbest {
  display: inline-flex; align-items: center; gap: 6px; margin: 0; padding: 6px 13px; border-radius: 99px;
  background: #fff1c2; color: #5c4400; font-size: 12px; font-weight: 800;
}
#gr-complete[data-tone=dark] .gr-quiz-results .gr-quiz-newbest { background: #4a3b10; color: #ffe08a; }
#gr-complete .gr-quiz-results[data-fx=end] .gr-quiz-newbest { animation: gr-qz-in .4s .5s ease-out both; }
#gr-complete .gr-quiz-res-stats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; width: 100%; max-width: 660px;
}
#gr-complete .gr-quiz-stat {
  display: grid; gap: 3px; align-content: center; padding: 12px 8px; border-radius: 14px;
  background: var(--gr-bg); border: 1px solid var(--gr-line);
}
#gr-complete .gr-quiz-stat b { font: 700 18px/1.15 Outfit, Arial, sans-serif; color: var(--gr-ink); }
#gr-complete .gr-quiz-stat small { font-size: 10px; font-weight: 600; }
#gr-complete .gr-quiz-stat.is-text b { font-size: 14px; line-height: 1.3; }
#gr-complete .gr-quiz-results h1 { font-size: clamp(26px, 3.6cqw, 40px); line-height: 1.1; }
#gr-complete .gr-quiz-last {
  width: 100%; max-width: 660px; padding: 12px 14px; border-radius: 14px; text-align: start;
  border: 1px dashed var(--gr-line); background: var(--gr-bg);
}
#gr-complete .gr-quiz-results .gr-quiz-last p { margin: 4px 0 0; font-size: 13px; color: var(--gr-ink); }
#gr-complete .gr-quiz-res-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
#gr-complete .gr-quiz-results .gr-note { max-width: 660px; }

/* ---- confetti: a finished topic, and a streak of 3, 5 or 10 ------------- */
#gr-complete .gr-confetti { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 3; }
#gr-complete .gr-confetti i {
  position: absolute; top: -14px; left: var(--x); width: 8px; height: 13px; border-radius: 2px;
  background: var(--c); opacity: 0;
  animation: gr-qz-confetti 1.35s cubic-bezier(.25,.6,.4,1) var(--d) forwards;
}
@keyframes gr-qz-confetti {
  0%   { opacity: 1; transform: translateY(0) rotate(var(--r)) scale(var(--s)); }
  85%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(440px) rotate(calc(var(--r) + 560deg)) scale(var(--s)); }
}

/* ---- right-to-left pages ----------------------------------------------
   The QUESTIONS keep their own direction: they are content in English or
   Hindi, both left-to-right, and an English question in an Arabic page must
   still read "What is 10 + 14?" rather than "?What is 10 + 14". The markup
   says `dir="auto"`; here the answer text is kept beside its letter badge,
   which sits on the right in a right-to-left tile. A bare signed number
   ("+170") is isolated so its sign stays in front of it. */
[dir=rtl] #gr-complete .gr-quiz-optext { text-align: right; }
#gr-complete .gr-quiz-pop { direction: ltr; unicode-bidi: isolate; }

/* ---- when motion is off ------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  #gr-complete .gr-quiz-stage, #gr-complete .gr-quiz-stage *,
  #gr-complete .gr-quiz-results, #gr-complete .gr-quiz-results * { animation: none !important; transition: none !important; }
  #gr-complete .gr-confetti, #gr-complete .gr-quiz-pop { display: none; }
}
#gr-complete[data-motion=off] .gr-confetti,
#gr-complete[data-motion=off] .gr-quiz-pop { display: none; }

/* SHORT VIEWPORTS — the contract's own edge case, measured: at 1280x560 (a
   laptop with a toolbar and a dock) every answer, both lifelines and the
   language control must be on screen under the sticky header. The prompt
   gives up its size first, then the padding, because a smaller question is
   still playable and a hidden answer is not. */
@media (max-height: 640px) {
  #gr-complete .gr-quiz-stage { gap: 8px; padding: 12px 16px 14px; }
  #gr-complete .gr-quiz-top-actions button { min-height: 32px; padding: 4px 10px; }
  #gr-complete .gr-quiz-progress { height: 6px; }
  #gr-complete .gr-quiz-lives { padding: 4px 8px; }
  #gr-complete .gr-quiz-lives .gr-life svg { width: 18px; height: 18px; }
  #gr-complete .gr-quiz-meta { padding: 2px 10px; }
  #gr-complete .gr-quiz-meta b { font-size: 17px; }
  #gr-complete .gr-quiz-qcard { gap: 4px; padding: 9px 14px; }
  #gr-complete .gr-quiz-play .gr-quiz-prompt { font-size: clamp(16px, 1.8cqw, 21px); }
  #gr-complete .gr-quiz-option { min-height: 46px; padding: 5px 12px 5px 8px; border-bottom-width: 3px; }
  #gr-complete .gr-quiz-key { width: 28px; height: 28px; font-size: 13px; border-radius: 8px; }
  #gr-complete .gr-quiz-lifelines > button { min-height: 34px; }
  #gr-complete .gr-quiz-after { padding: 10px 14px; }
  #gr-complete .gr-quiz-after > .gr-play { min-height: 42px; }
}

/* DESKTOP: the header is sticky, so a stage brought into view stops below
   it rather than under it. */
@container gr-shell (min-width: 621px) {
  #gr-complete .gr-quiz-stage, #gr-complete .gr-quiz-results { scroll-margin-top: calc(var(--gr-header-h, 75px) + 12px); }
}

/* A PHONE: one column of answers, the hearts on their own row where a thumb
   is not covering them, the leave control as an icon, and the verdict above a
   full-width Next — clear of the sticky bottom bar when scrolled to. */
@container gr-shell (max-width: 620px) {
  #gr-complete .gr-quiz-stage, #gr-complete .gr-quiz-results { scroll-margin-top: 8px; }
  #gr-complete .gr-quiz-stage { gap: 10px; padding: 14px 12px 16px; }
  #gr-complete .gr-quiz-grid { grid-template-columns: 1fr; gap: 8px; }
  #gr-complete .gr-quiz-option { min-height: 54px; }
  #gr-complete .gr-quiz-status { gap: 6px; }
  #gr-complete .gr-quiz-lives { flex: 1 1 100%; justify-content: center; }
  #gr-complete .gr-quiz-meta { flex: 1 1 0; min-width: 0; padding: 4px 6px; }
  #gr-complete .gr-quiz-score { margin-left: 0; }
  #gr-complete .gr-quiz-leave-text {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  #gr-complete .gr-quiz-top-actions button:last-child { padding: 6px 9px; }
  #gr-complete .gr-quiz-after { grid-template-columns: 1fr; scroll-margin-bottom: 86px; }
  #gr-complete .gr-quiz-after > .gr-play { grid-column: 1; grid-row: auto; width: 100%; }
  #gr-complete .gr-quiz-res-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #gr-complete .gr-quiz-res-score b { font-size: 42px; }
}

/* ==========================================================================
   LANGUAGES — the picker
   --------------------------------------------------------------------------
   This replaced a read-only list of sixty language names, fifty-two of them
   marked "Registered only". Every tile here is a working choice, so every
   tile is a real control: a pointer, a hover, a focus ring and a pressed
   state, because a thing that changes the whole site when tapped must look
   like a thing that can be tapped.

   THE NAME IS IN ITS OWN SCRIPT and carries its own `lang`/`dir`, so the
   Devanagari in हिन्दी gets Devanagari line metrics and العربية lays itself
   out right-to-left inside its tile while the page around it is still
   left-to-right. That is why the name is a block of its own rather than part
   of a sentence: a bidirectional run mixed into left-to-right text reorders
   unpredictably, and a player scanning for their own language must find it
   spelled the way they write it.
   ========================================================================== */

#gr-complete .gr-lang-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 10px; margin-top: 10px;
}
#gr-complete .gr-lang {
  /* `button` is a flex row by default in base.css; a tile needs a column, and
     it needs to start at the top so two-line names do not shift the English
     name below them out of alignment with its neighbours. */
  display: flex; flex-direction: column; align-items: flex-start;
  justify-content: flex-start; gap: 2px;
  position: relative; text-align: left;
  min-height: 62px; padding: 11px 13px;
  border: 1px solid var(--gr-line); border-radius: 13px;
  background: var(--gr-panel); cursor: pointer;
}
#gr-complete .gr-lang:hover { border-color: var(--gr-blue); background: var(--gr-tint); }
#gr-complete .gr-lang[aria-pressed=true] {
  border-color: var(--gr-blue); border-width: 2px; padding: 10px 12px;
  background: var(--gr-tint);
}
#gr-complete .gr-lang-name {
  font-family: Outfit, 'DM Sans', Arial, sans-serif;
  font-size: 16px; font-weight: 650; line-height: 1.3;
  color: var(--gr-ink);
  /* Its own writing direction, kept inside the tile. */
  display: block; max-width: 100%; overflow-wrap: anywhere;
}
#gr-complete .gr-lang-name[dir=rtl] { text-align: right; align-self: stretch; }
#gr-complete .gr-lang-en {
  font-size: 11px; line-height: 1.35; color: var(--gr-sub);
  /* The coverage percentage rides here, so it must not wrap away from the
     name it qualifies. */
  display: block;
}
#gr-complete .gr-lang-tick {
  position: absolute; top: 9px; right: 9px;
  display: inline-flex; color: var(--gr-blue);
}
#gr-complete .gr-lang-tick svg { width: 15px; height: 15px; }

/* ==========================================================================
   RIGHT-TO-LEFT — what flips when the whole document does
   --------------------------------------------------------------------------
   `dir=rtl` on <html> already mirrors text alignment, the block direction of
   every flex and grid row, and the browser's own scrollbar. What it does NOT
   mirror is anything positioned with an explicit `left`/`right` or padded
   asymmetrically in a frozen stylesheet — and base.css and model.css are
   frozen, so the corrections live here.

   Arabic, Persian, Hebrew and Urdu are among the languages a platform in 177
   countries has to serve, so this is not a future nicety: it is the layout
   half of shipping those dictionaries at all.
   ========================================================================== */

[dir=rtl] #gr-complete .gr-lang { text-align: right; align-items: flex-end; }
[dir=rtl] #gr-complete .gr-lang-name,
[dir=rtl] #gr-complete .gr-lang-en { text-align: right; }
[dir=rtl] #gr-complete .gr-lang-tick { right: auto; left: 9px; }

/* A ROW'S LABEL AND ITS CONTROL. `.gr-row` is a flex row whose first child
   takes the space and whose control sits after it, so `dir=rtl` on <html>
   already puts the label on the right and the control on the left — no
   correction needed. What DOES need correcting is the text inside a cell that
   base.css left at its default, because `.gr-row b`/`small` are blocks and a
   block inherits the document direction only for its own content flow. This
   is a no-op in a left-to-right document. */
[dir=rtl] #gr-complete .gr-row b,
[dir=rtl] #gr-complete .gr-row small { text-align: right; }

/* A DIRECTIONAL ICON IS MIRRORED; A SYMBOLIC ONE IS NOT. `arrow-right` means
   "onward", and onward is leftward in a right-to-left layout, so it flips.
   Every other icon in the set — a heart, a check, a trophy — means what it
   draws and must not flip, which is why this is a named list rather than a
   blanket rule on `svg`. lucide replaces `<i data-lucide>` with an <svg> and
   keeps the attribute, so the attribute is what to select on. */
[dir=rtl] #gr-complete [data-lucide=arrow-right],
[dir=rtl] #gr-complete [data-lucide=arrow-left],
[dir=rtl] #gr-complete [data-lucide=chevron-right],
[dir=rtl] #gr-complete [data-lucide=chevron-left],
[dir=rtl] #gr-complete [data-lucide=corner-down-right],
[dir=rtl] #gr-complete [data-lucide=trending-up] { transform: scaleX(-1); }

/* A RANK IS A NUMBER, AND A NUMBER IS NOT MIRRORED. The bidi algorithm keeps
   the digits themselves in order; what moves is the column, and in a
   right-to-left reading order the rank belongs first, which is the right.
   `.model-rank` is already a flex row, so the document direction does that on
   its own — this only keeps the rank's own box from stretching. */
[dir=rtl] #gr-complete .model-rank strong { direction: ltr; unicode-bidi: isolate; }

/* An English fragment inside a right-to-left sentence — a game's name, a
   locale code, a handle — is isolated so it cannot drag the punctuation
   around it to the wrong end of the line. This is the single most common
   right-to-left defect and it is invisible to anyone who does not read the
   language. */
[dir=rtl] #gr-complete .gr-lang-en { direction: ltr; text-align: right; }

/* ==========================================================================
   OWNER DIRECTION — second smoke review, 28 September 2026
   --------------------------------------------------------------------------
   Four things the owner hit while playing the Hindi build. Everything here
   overrides the approved design on his instruction, which is first in the
   authority order, and lives in the production layer so base.css and
   model.css stay frozen.
   ========================================================================== */

/* ---------- 1. Navigation that stays put ------------------------------
   "Jab screen neeche scroll down kr rahe hai toh side ke option hatt jaa
   rahe hai."

   WHY IT SCROLLED AWAY, measured rather than guessed: the sidebar is a grid
   item stretched to the full height of the page, and the owner-smoke item-4
   rule above makes it `overflow-y: auto`. A scroll box that is as tall as its
   content never scrolls, and anything sticky inside it sticks to THAT box —
   so no descendant could ever have followed the screen.

   So the scrolling moves one level in. The column keeps its full height (it
   carries the background and the dividing line down the whole page); its
   contents — .gr-sidebar-inner — stick beneath the header and are limited to
   the screen's height, scrolling on their own if the window is short. That
   finally does what item 4 meant: "the column scrolls on a short window
   rather than pushing the side note out of view".

   THE HEADER STICKS TOO, above the phone breakpoint. Search, sign-in and the
   balance are what a player reaches for from anywhere on a long page, and
   every web game portal keeps them on screen. Below the breakpoint the header
   is two rows tall and a phone screen cannot spare that, so there it scrolls
   and the BOTTOM bar sticks instead — the phone equivalent of the sidebar,
   which had the same fault: it sat at the very end of the page, so the menu
   was only reachable by scrolling all the way down.

   `--gr-header-h` is the header's measured height (appearance.js,
   watchHeaderHeight); 75px is only the value before the first measurement. */
#gr-complete { --gr-header-h: 75px; }
#gr-complete .gr-sidebar { overflow: visible; }
@container gr-shell (min-width: 621px) {
  #gr-complete .gr-header {
    position: sticky;
    top: 0;
    z-index: 30;
  }
  /* THE HEIGHT LEAVES ROOM FOR THE END OF THE PAGE. A sticky block can only
     travel inside its column, and the column ends 72px above the bottom of
     the page (the footer and the page padding sit below it — measured, the
     same at every width). At the very end of the scroll the column's bottom
     therefore rises into view and pushes a full-height block up UNDER the
     header — the first test of this caught Home 64px behind it. The column's
     own 22px bottom padding (base.css) is off-limits to it as well, so the
     room is 72 + 22, plus a little. Reserving it keeps the whole list clear
     of the header all the way down; on
     a short window the block scrolls inside itself instead, and what goes
     below its fold is the decorative note, never a destination. */
  #gr-complete .gr-sidebar-inner {
    --gr-foot-room: 100px;
    position: sticky;
    top: var(--gr-header-h, 75px);
    max-height: calc(100vh - var(--gr-header-h, 75px) - var(--gr-foot-room));
    max-height: calc(100dvh - var(--gr-header-h, 75px) - var(--gr-foot-room));
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    /* The approved padding lives on the column; the sticky block needs a
       little of its own so the first item does not touch the header's
       border once it is stuck. */
    padding: 2px 0 12px;
  }
}
@container gr-shell (max-width: 620px) {
  #gr-complete .gr-bottom {
    position: sticky;
    bottom: 0;
    z-index: 30;
    padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -8px 20px rgba(16, 32, 60, .08);
  }
}

/* ---------- 2. The language switcher at the foot of the sidebar ------- */
#gr-complete .gr-side-lang {
  width: 100%;
  justify-content: flex-start;
  gap: 10px;
  margin: 14px 0 0;
  padding: 10px 12px;
  min-height: 44px;
  border: 1px solid var(--gr-line);
  border-radius: 12px;
  background: var(--gr-panel);
  color: var(--gr-ink);
  font-size: 12px;
  font-weight: 650;
}
#gr-complete .gr-side-lang:hover { border-color: var(--gr-blue); background: var(--gr-tint); }
#gr-complete .gr-side-lang[aria-current="page"] { border-color: var(--gr-blue); color: var(--gr-blue); }
#gr-complete .gr-side-lang-name {
  flex: 1;
  min-width: 0;
  text-align: start;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#gr-complete .gr-side-lang svg:last-child { width: 15px; height: 15px; opacity: .55; }
[dir=rtl] #gr-complete .gr-side-lang svg:last-child { transform: scaleX(-1); }

/* ---------- 3. A settings row is one tap target ----------------------
   "Bhasha pr click nhi ho raha." The owner clicked the row's label. A row
   whose only control is one enabled navigation now forwards a click anywhere
   on it to that control (v3-app.js, rowTarget); this is the part the eye
   needs — the pointer, and the row's button lighting up while the pointer is
   anywhere on the row, so it is obvious what the click will do. `:has()` is
   only the affordance: an engine without it still gets the working click. */
#gr-complete .gr-row:has(> button[data-route]:not(:disabled):only-of-type) { cursor: pointer; }
#gr-complete .gr-row:has(> button[data-route]:not(:disabled):only-of-type):hover > button[data-route] {
  border-color: var(--gr-blue);
  background: var(--gr-tint);
  color: var(--gr-blue);
}

/* ---------- 4. The platform balance, in the header -------------------- */
#gr-complete .gr-coin {
  display: inline-block;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #fff8d6 0 16%, #ffd54f 17% 58%, #e3a514 59% 100%);
  box-shadow: inset 0 0 0 2px #f8cb45, inset 0 -3px 0 rgba(170, 110, 0, .35), 0 1px 3px rgba(150, 100, 0, .35);
}
#gr-complete .gr-coins {
  gap: 8px;
  min-height: 38px;
  padding: 0 13px 0 8px;
  border: 1px solid #f0cf6a;
  border-radius: 12px;
  background: #fff8e1;
  /* 7.9:1 on the cream, in every theme — the pill carries its own surface
     rather than a theme token, because gold on a dark theme's panel would
     be a different colour pair in each theme. */
  color: #5a4210;
  font: 700 13px Outfit, 'DM Sans', Arial, sans-serif;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
#gr-complete .gr-coins:hover { background: #ffefb8; }
#gr-complete .gr-coins.is-gaining { animation: gr-coins-gain .7s ease-out; }
@keyframes gr-coins-gain {
  0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 200, 40, .55); }
  35% { transform: scale(1.1); box-shadow: 0 0 0 8px rgba(255, 200, 40, .0); }
  100% { transform: scale(1); }
}
@container gr-shell (max-width: 620px) {
  #gr-complete .gr-coins { padding: 0 10px 0 6px; font-size: 12px; min-height: 36px; }
  #gr-complete .gr-coin { width: 19px; height: 19px; }
}

/* Roch Points, beside the coins (owner, 1 Oct 2026). Like the coin pill it
   carries its own surface, so it is the same violet pair in every theme —
   #3a1f9a on #f1ecff is 9.6:1. */
#gr-complete .gr-rpill {
  gap: 7px;
  min-height: 38px;
  padding: 0 12px 0 9px;
  border: 1px solid #cdbcff;
  border-radius: 12px;
  background: #f1ecff;
  color: #3a1f9a;
  font: 700 13px Outfit, 'DM Sans', Arial, sans-serif;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
#gr-complete .gr-rpill:hover { background: #e4dbff; }
#gr-complete .gr-rpill small { font-size: 10px; font-weight: 800; letter-spacing: .4px; opacity: .8; }
#gr-complete .gr-rpgem {
  flex: none; width: 15px; height: 15px; border-radius: 28%; rotate: 45deg;
  background: linear-gradient(135deg, #cdbcff, #7b4dff 55%, #4f2bc4);
  box-shadow: inset 0 0 0 1.5px #ffffff80, 0 1px 0 #3a1f9a;
}
@container gr-shell (max-width: 850px) {
  #gr-complete .gr-rpill small { display: none; }
}
@container gr-shell (max-width: 620px) {
  #gr-complete .gr-rpill { padding: 0 9px 0 7px; font-size: 12px; min-height: 36px; }
}

/* ---------- 5. Notices arrive at the top ----------------------------
   The pill used to float at the BOTTOM of the screen, and the same message
   was also written into a line below the footer that never cleared — the
   "+10 coins" the owner found at the end of the page. It now arrives just
   under the header, where the eye already is and where the balance it just
   changed is shown. Nothing here ever blocks a click. */
#gr-complete .gr-toast {
  top: calc(var(--gr-header-h, 75px) + 14px);
  bottom: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(92vw, 520px);
  width: max-content;
  padding: 11px 18px;
  border-radius: 14px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  box-shadow: 0 14px 34px rgba(16, 32, 60, .18);
  transform: translate(-50%, -14px) scale(.97);
  transition: opacity .2s ease, transform .38s cubic-bezier(.2, 1.35, .4, 1);
}
#gr-complete .gr-toast[data-show="true"] { opacity: 1; transform: translate(-50%, 0) scale(1); }
@media (max-width: 640px) {
  /* The header scrolls away on a phone, so the notice anchors to the
     screen's own top edge, clear of the notch. */
  #gr-complete .gr-toast { top: calc(12px + env(safe-area-inset-top, 0px)); }
}
#gr-complete .gr-toast[data-kind="reward"] {
  padding: 10px 20px 10px 12px;
  border: 1px solid #f0c64c;
  border-radius: 16px;
  background: linear-gradient(135deg, #fffaeb, #ffe7a1);
  color: #4d3809;
}
#gr-complete .gr-toast .gr-toast-coin {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #fff8d6 0 16%, #ffd54f 17% 58%, #e3a514 59% 100%);
  box-shadow: inset 0 0 0 3px #f8cb45, inset 0 -4px 0 rgba(170, 110, 0, .35), 0 3px 8px rgba(150, 100, 0, .35);
}
#gr-complete .gr-toast[data-kind="reward"][data-show="true"] .gr-toast-coin { animation: gr-coin-spin .95s ease-out; }
@keyframes gr-coin-spin {
  0% { transform: rotateY(0) scale(.55); }
  60% { transform: rotateY(540deg) scale(1.14); }
  100% { transform: rotateY(720deg) scale(1); }
}
#gr-complete .gr-toast-copy b { display: block; font: 750 16px/1.2 Outfit, 'DM Sans', Arial, sans-serif; }
#gr-complete .gr-toast-copy small { display: block; margin-top: 2px; font-size: 12px; color: #6b5213; }
@media (prefers-reduced-motion: reduce) {
  #gr-complete .gr-toast { transition: opacity .15s ease; transform: translate(-50%, 0); }
  #gr-complete .gr-toast .gr-toast-coin,
  #gr-complete .gr-coins.is-gaining { animation: none; }
}

/* ==========================================================================
   THE NEXT LEVEL — premium themes, characters, the Home hub
   --------------------------------------------------------------------------
   OWNER DIRECTION, 29 September 2026. Themes worth unlocking, a character for
   every player, and a UI lift across the site. Tokens stay the single source
   of colour: a premium theme writes its palette onto #gr-complete as custom
   properties (js/core/themes.js), so every component below — and every
   approved component — reads the theme without knowing which one it is.
   ========================================================================== */

/* ---- premium themes ------------------------------------------------------ */
#gr-complete[data-premium] .gr-app { background: var(--gr-decor, none), var(--gr-bg); }
/* White hero copy on every premium hero: the palettes are checked for white
   at 4.5:1 on both hero stops, and the approved pale-blue copy was only ever
   checked against Everyday Blue. */
#gr-complete[data-premium] .gr-hero p,
#gr-complete[data-premium] .gr-hero .gr-eyebrow { color: #fff; }
#gr-complete[data-premium] .gr-play { box-shadow: 0 6px 18px #00000030; }
#gr-complete[data-tone=dark] .gr-brand strong span { color: #c797ff; }
#gr-complete[data-tone=dark] { color-scheme: dark; }

/* The slow ambient light of the Epic and Legendary themes: one blurred layer
   behind the page, moved with a transform so the compositor does the work. */
#gr-complete[data-ambient] .gr-app { position: relative; isolation: isolate; }
#gr-complete[data-ambient] .gr-app::before {
  content: ''; position: absolute; inset: -12%; z-index: -1; pointer-events: none;
  background: var(--gr-ambient, none);
  animation: gr-ambient 46s ease-in-out infinite alternate;
}
@keyframes gr-ambient {
  from { transform: translate3d(-4%, -3%, 0) scale(1.04); }
  to   { transform: translate3d(4%, 3%, 0) scale(1.12); }
}
#gr-complete[data-motion=off] .gr-app::before { animation: none; }
@media (prefers-reduced-motion: reduce) { #gr-complete .gr-app::before { animation: none; } }

/* ---- currency marks and requirement chips -------------------------------- */
#gr-complete .gr-cur { display: inline-block; width: 14px; height: 14px; flex: none; vertical-align: -2px; }
#gr-complete .gr-cur-coins { border-radius: 50%; background: radial-gradient(circle at 35% 30%, #fff3b0, #f5b300 55%, #b98100); box-shadow: inset 0 0 0 1.5px #b98100; }
#gr-complete .gr-cur-diamonds { background: linear-gradient(135deg, #b8f0ff, #3aa6ff 55%, #1f5fd1); clip-path: polygon(50% 0, 100% 38%, 50% 100%, 0 38%); }
#gr-complete .gr-cur-roch { border-radius: 4px; background: linear-gradient(135deg, #b794ff, #7c3aed); transform: rotate(45deg) scale(.8); }
#gr-complete .gr-req {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 99px;
  font-size: 11px; font-weight: 700; line-height: 1.3; background: var(--gr-tint); color: var(--gr-ink);
}
#gr-complete .gr-req.is-free { color: var(--gr-blue); }
#gr-complete .gr-reqs { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---- rarity -------------------------------------------------------------- */
#gr-complete .gr-rarity {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 99px;
  font-size: 11px; font-weight: 800; letter-spacing: .3px;
}
#gr-complete .gr-rarity[data-r=common]    { background: #e7ecf3; color: #33415a; }
#gr-complete .gr-rarity[data-r=rare]      { background: #dcecff; color: #0f4c9c; }
#gr-complete .gr-rarity[data-r=epic]      { background: #efe2ff; color: #5b21b6; }
#gr-complete .gr-rarity[data-r=legendary] { background: linear-gradient(90deg, #fff1c2, #ffd9ef); color: #7a3b00; }

/* ---- the theme gallery --------------------------------------------------- */
#gr-complete .gr-collection { margin-top: 30px; }
#gr-complete .gr-collection-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
#gr-complete .gr-collection-head h2 { font-size: 22px; }
#gr-complete .gr-collection-head p { margin: 4px 0 0; font-size: 12px; }
#gr-complete .gr-theme-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
#gr-complete .gr-ptheme {
  display: grid; grid-template-rows: auto 1fr; overflow: hidden; border-radius: 18px;
  border: 1px solid var(--gr-line); background: var(--gr-panel); box-shadow: var(--gr-shadow);
  transition: transform .2s ease, box-shadow .2s ease;
}
#gr-complete .gr-ptheme:hover { transform: translateY(-3px); box-shadow: 0 14px 30px #1a1a3a26; }
#gr-complete .gr-ptheme.is-previewing { outline: 3px solid var(--gr-blue); outline-offset: 2px; }
/* The swatch is the theme itself, drawn with ITS tokens: page, panel and
   accent, over its own background art. */
#gr-complete .gr-ptheme-swatch {
  position: relative; height: 124px; padding: 10px; display: grid; grid-template-columns: 16px 1fr; gap: 8px;
  background: var(--sw-decor), var(--sw-bg);
}
#gr-complete .gr-ptheme-swatch i { border-radius: 6px; background: var(--sw-panel); opacity: .92; }
#gr-complete .gr-sw-main { display: grid; grid-template-rows: 1fr 30px; gap: 7px; min-width: 0; }
/* The theme's own hero, with its accent button on it. */
#gr-complete .gr-ptheme-swatch em {
  position: relative; border-radius: 10px; background: linear-gradient(120deg, var(--sw-hero), var(--sw-hero2));
}
#gr-complete .gr-ptheme-swatch em::before {
  content: ''; position: absolute; left: 9px; top: 9px; width: 46%; height: 7px; border-radius: 4px; background: #ffffffd9;
}
#gr-complete .gr-ptheme-swatch em::after {
  content: ''; position: absolute; left: 9px; bottom: 9px; width: 34%; height: 11px; border-radius: 6px; background: var(--sw-accent);
}
#gr-complete .gr-sw-row { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
#gr-complete .gr-sw-row b { position: relative; border-radius: 8px; background: var(--sw-panel); }
#gr-complete .gr-sw-row b::before {
  content: ''; position: absolute; left: 7px; top: 8px; width: 55%; height: 5px; border-radius: 3px; background: var(--sw-ink); opacity: .7;
}
#gr-complete .gr-sw-row b::after {
  content: ''; position: absolute; left: 7px; bottom: 7px; width: 30%; height: 5px; border-radius: 3px; background: var(--sw-accent);
}
#gr-complete .gr-ptheme-body { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 12px 12px 14px; }
#gr-complete .gr-ptheme-body h3 { font-size: 15px; }
#gr-complete .gr-ptheme-body > button { width: 100%; min-height: 38px; font-size: 12px; margin-top: auto; }
#gr-complete .gr-gallery-note {
  display: flex; gap: 10px; align-items: center; padding: 12px 14px; border-radius: 14px;
  background: var(--gr-tint); color: var(--gr-ink); font-size: 12px; margin-top: 18px;
}
#gr-complete .gr-gallery-note svg { color: var(--gr-blue); flex: none; }

@container gr-shell (max-width: 1100px) { #gr-complete .gr-theme-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@container gr-shell (max-width: 620px) { #gr-complete .gr-theme-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } }

/* ---- the preview bar ----------------------------------------------------- */
#gr-complete .gr-preview-bar {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 60;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: center;
  width: min(820px, calc(100vw - 24px)); padding: 10px 12px 10px 16px; border-radius: 18px;
  background: var(--gr-panel); color: var(--gr-ink); border: 1px solid var(--gr-line);
  box-shadow: 0 16px 40px #00000040;
}
#gr-complete .gr-preview-bar[hidden] { display: none; }
#gr-complete .gr-preview-bar .gr-pv-name { display: grid; gap: 2px; flex: 1 1 220px; min-width: 0; }
#gr-complete .gr-preview-bar .gr-pv-name b { font: 700 15px/1.2 Outfit, Arial, sans-serif; }
#gr-complete .gr-preview-bar .gr-pv-name small { font-size: 11px; }
#gr-complete .gr-preview-bar button { min-height: 38px; font-size: 12px; border-radius: 12px; }
@media (max-width: 640px) {
  #gr-complete .gr-preview-bar { bottom: calc(84px + env(safe-area-inset-bottom)); justify-content: flex-start; }
  /* On a phone the way out becomes a corner button, so the bar is two rows, not three. */
  #gr-complete .gr-preview-bar .gr-pv-name { padding-right: 40px; }
  #gr-complete .gr-preview-bar .gr-pv-exit { position: absolute; top: 8px; right: 8px; width: 38px; padding: 0; }
  #gr-complete .gr-preview-bar .gr-pv-exit-label {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
}

/* ---- characters ---------------------------------------------------------- */
#gr-complete .gr-char { display: block; flex: none; }
#gr-complete .gr-face {
  display: inline-grid; place-items: center; flex: none; width: 38px; height: 38px; border-radius: 12px;
  background: var(--gr-tint);
}
#gr-complete .gr-face .gr-char { width: 32px; height: 32px; }
/* The header avatar and the profile avatar become the character. */
#gr-complete .gr-avatar.has-char { background: var(--gr-tint); box-shadow: none; padding: 0; overflow: hidden; }
#gr-complete .gr-avatar.has-char .gr-char { width: 86%; height: 86%; }
/* Leaderboard rows: the face sits between the rank and the name. */
/* `.model-rank>span` makes every direct span a stretching grid column; the
   face is a span too, so it states its own box. */
#gr-complete .model-rank .gr-face { flex: none; display: inline-grid; gap: 0; width: 34px; height: 34px; border-radius: 11px; }
#gr-complete .model-rank .gr-face .gr-char { width: 29px; height: 29px; }

#gr-complete .gr-char-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
#gr-complete .gr-char-card {
  display: grid; justify-items: center; gap: 8px; padding: 16px 12px 14px; border-radius: 18px; text-align: center;
  background: var(--gr-panel); border: 1px solid var(--gr-line); box-shadow: var(--gr-shadow);
}
#gr-complete .gr-char-card .gr-char-stage {
  display: grid; place-items: center; width: 96px; height: 96px; border-radius: 28px;
  background: radial-gradient(circle at 50% 35%, #ffffff, var(--gr-tint) 70%);
}
#gr-complete[data-tone=dark] .gr-char-card .gr-char-stage { background: radial-gradient(circle at 50% 35%, #ffffff26, var(--gr-tint) 70%); }
/* Locked characters stay in full colour — wanting one starts with seeing it —
   and carry a small lock on their stage instead. */
#gr-complete .gr-char-card .gr-char-stage { position: relative; }
#gr-complete .gr-char-card .gr-lock {
  position: absolute; right: -4px; bottom: -4px; display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: 50%; background: var(--gr-ink); color: var(--gr-panel); box-shadow: 0 0 0 3px var(--gr-panel);
}
#gr-complete .gr-char-card .gr-lock svg { width: 14px; height: 14px; }
#gr-complete .gr-char-card.is-yours { border-color: var(--gr-blue); box-shadow: 0 0 0 3px color-mix(in srgb, var(--gr-blue) 25%, transparent); }
#gr-complete .gr-char-card h3 { font-size: 15px; }
#gr-complete .gr-char-card .gr-reqs { justify-content: center; }
@container gr-shell (max-width: 900px) { #gr-complete .gr-char-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@container gr-shell (max-width: 620px) { #gr-complete .gr-char-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---- the Home hub -------------------------------------------------------- */
/* The one memorable thing on Home: the player's own character, big, beside
   what today holds — the reward, the streak, the balance — and the one action
   that matters, Continue. Everything else on Home stays as it was. */
#gr-complete .gr-hub {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 20px;
  padding: 18px 20px; margin-bottom: 22px; border-radius: 24px;
  background: var(--gr-panel); border: 1px solid var(--gr-line); box-shadow: var(--gr-shadow);
}
#gr-complete .gr-hub-char {
  display: grid; place-items: center; width: 96px; height: 96px; border-radius: 30px;
  background: radial-gradient(circle at 50% 30%, #ffffffcc, var(--gr-tint) 72%);
}
#gr-complete[data-tone=dark] .gr-hub-char { background: radial-gradient(circle at 50% 30%, #ffffff2e, var(--gr-tint) 72%); }
#gr-complete .gr-hub-char .gr-char { width: 80px; height: 80px; }
#gr-complete .gr-hub h1 { font-size: clamp(22px, 2.6cqw, 30px); }
#gr-complete .gr-hub .gr-hub-sub { margin: 4px 0 10px; font-size: 13px; }
#gr-complete .gr-hub-chips { display: flex; flex-wrap: wrap; gap: 8px; }
#gr-complete .gr-hub-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px; border-radius: 99px;
  background: var(--gr-tint); color: var(--gr-ink); font-size: 12px; font-weight: 700;
}
#gr-complete .gr-hub-chip svg { width: 15px; height: 15px; color: var(--gr-blue); }
#gr-complete .gr-hub-actions { display: grid; gap: 8px; justify-items: stretch; }
#gr-complete .gr-hub-actions .gr-play { min-height: 50px; padding: 12px 22px; font-size: 14px; border-radius: 14px; }
#gr-complete .gr-hub-actions .gr-soft { font-size: 12px; }
@container gr-shell (max-width: 760px) {
  #gr-complete .gr-hub { grid-template-columns: auto minmax(0, 1fr); }
  #gr-complete .gr-hub-actions { grid-column: 1 / -1; grid-template-columns: 1fr 1fr; }
}
@container gr-shell (max-width: 420px) {
  #gr-complete .gr-hub { gap: 14px; padding: 14px; }
  #gr-complete .gr-hub-char { width: 72px; height: 72px; border-radius: 22px; }
  #gr-complete .gr-hub-char .gr-char { width: 60px; height: 60px; }
  #gr-complete .gr-hub-actions { grid-template-columns: 1fr; }
}

/* ---- a quieter, firmer site-wide feel -------------------------------------
   Small things that add up: controls answer the press, focus is always
   visible, cards lift a little under a pointer, and headings get a steadier
   rhythm. No colour changes here — the palettes own colour. */
#gr-complete :focus-visible { outline: 3px solid var(--gr-blue); outline-offset: 2px; }
#gr-complete .gr-soft:hover:not(:disabled) { border-color: var(--gr-blue); }
#gr-complete .gr-soft:active:not(:disabled) { transform: translateY(1px); }
#gr-complete .gr-panel { transition: box-shadow .2s ease; }
#gr-complete .gr-section-head h2 { letter-spacing: -.4px; }
#gr-complete .gr-topic:hover { box-shadow: 0 8px 18px #1a1a3a1a; }

/* ---------- Profile: the hall of fame (V3 Live Update 1) ----------
   Three medals and one line — a glance at what the Achievements screen holds. */
#gr-complete .gr-hall { display: grid; gap: 10px; justify-items: center; text-align: center; }
#gr-complete .gr-hall-medals { display: flex; gap: clamp(14px, 5cqi, 44px); align-items: flex-end; justify-content: center; padding: 6px 0 2px; }
#gr-complete .gr-hall-line { margin: 0; font-weight: 700; }
#gr-complete .gr-hall-badges { color: var(--gr-sub); font-weight: 500; font-size: 14px; }
#gr-complete .gr-hall .gr-buttons { justify-content: center; }
