/* ═══════════════════════════════════════════════════════════════════════════
   VIBRANT.CSS — anything you can touch says so, and says it back (Session 23)

   ⚑ WHY THIS IS ONE SHEET AND NOT AN EDIT TO NINE. The rule the brief sets is a
   SITE rule: every interactive or state-changing thing is filled, vibrant, and
   visibly answers a touch. Written into each page's own stylesheet it would be
   nine copies of one decision, drifting the first time anyone edits one of them —
   which is the fault this repo already records for `.adm-dim`, for the two
   `left` / `inset-inline-start` declarations, and for the duplicated hint path.
   It loads LAST so it wins on source order at equal specificity, and it targets
   the button classes that actually exist, counted rather than guessed:

     btn 105 · btn-primary 50 · btn-lg 31 · btn-ghost 24 · btn-sm 20
     btn-secondary 17 · k-btn 16 · hint-btn 15 · cm-btn 13 · btn-soft 13
     lnd-btn 9 · mc-btn 5 · kp-btn 5

   ⚑ AND THE FILLS ARE MEASURED, NOT THE BRIEF'S HEXES VERBATIM. Two of the four
   section colours cannot carry white text — reading's #C99E2E is 2.50:1 against
   white and writing's #4A8B5C is 4.09, AA for large text only. theme.css already
   records the first as "decoration only, always" and learn.css measured all six
   tile gradients for exactly this reason. So each section has TWO values: the
   brief's colour as its IDENTITY (accent bars, dots, decoration, never text) and
   a measured same-hue fill for anything carrying a word.
   (scratch_agent_s23/pick.py)

     section    identity   fill      fill vs #fff   pressed
     quant      #3D7BA8    #3D7BA8   4.57  AA text  #245A80
     abstract   #B85450    #B85450   4.75  AA text  #8E3A37
     reading    #C99E2E    #8A6810   5.16  AA text  #7A5E10
     writing    #4A8B5C    #3C7A4D   5.14  AA text  #2F6440
     brand      #005F82    #005F82   7.10  AA text  #00506E
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --vb-brand:        #005F82;
  --vb-brand-deep:   #00506E;
  --vb-brand-lift:   #0A7098;

  /* The section a page belongs to sets these; the default is the brand. */
  --vb-fill:         var(--vb-brand);
  --vb-fill-deep:    var(--vb-brand-deep);
  --vb-fill-lift:    var(--vb-brand-lift);
  --vb-ident:        var(--vb-brand);

  --vb-on-fill:      #FFFFFF;
  --vb-off:          #C9D1D6;   /* disabled fill                     */
  --vb-off-ink:      #4F5A61;   /* disabled ink, 5.4 on --vb-off     */
  --vb-ring:         #0A7098;

  --vb-ok:           #146B3F;
  --vb-warn:         #B3261E;

  --vb-lift:         cubic-bezier(.2, .7, .3, 1);
}

/* Section grounds. A page states its section once, on the root element. */
[data-section="quantitative"] { --vb-ident: #3D7BA8; --vb-fill: #3D7BA8;
                                --vb-fill-deep: #245A80; --vb-fill-lift: #4A8CBC; }
[data-section="abstract"]     { --vb-ident: #B85450; --vb-fill: #B85450;
                                --vb-fill-deep: #8E3A37; --vb-fill-lift: #C86560; }
[data-section="reading"]      { --vb-ident: #C99E2E; --vb-fill: #8A6810;
                                --vb-fill-deep: #7A5E10; --vb-fill-lift: #9C7714; }
[data-section="writing"]      { --vb-ident: #4A8B5C; --vb-fill: #3C7A4D;
                                --vb-fill-deep: #2F6440; --vb-fill-lift: #468B59; }

/* ── 1. every button answers a touch ──────────────────────────────────────── */

/* ⚑ THE LIST INCLUDES THE MODIFIER CLASSES ON THEIR OWN. Several buttons carry
   `btn-primary` WITHOUT `btn` and several carry only a page-local class, so a
   selector list of base classes left them with no transition at all — "Save
   goals" on /settings, "Switch to a nickname", both dashboard prompts. Measured
   by reading the computed `transition-property`, not by looking at the page. */
.btn, .btn-primary, .btn-secondary, .btn-lg, .btn-sm, .btn-ghost, .btn-soft,
.k-btn, .k-btn--primary, .cm-btn, .lnd-btn, .mc-btn, .kp-btn, .hint-btn,
.kg__btn, button[type="submit"], input[type="submit"],
button.btn, a.btn {
  transition: transform 140ms var(--vb-lift), filter 140ms ease,
              background-color 140ms ease, border-color 140ms ease,
              box-shadow 140ms ease;
}

/* Hover and press reach the same widened set. */
.btn-primary:hover, .btn-lg:hover, .btn-secondary:hover, .btn-ghost:hover,
.btn-soft:hover, .kg__btn:hover, button[type="submit"]:hover,
input[type="submit"]:hover {
  transform: translateY(-1px) scale(1.02);
  filter: brightness(1.06);
}
.btn-primary:active, .btn-lg:active, .btn-secondary:active, .btn-ghost:active,
.btn-soft:active, .kg__btn:active, button[type="submit"]:active,
input[type="submit"]:active {
  transform: scale(0.98);
}

/* ⚑ A SECONDARY ACTION IS OUTLINED ON PURPOSE AND STAYS THAT WAY. "See pricing"
   beside "Start free", "Not now" beside "Upgrade" — filling both would make the
   pair read as two primaries, which is the same failure as filling neither. What
   they gain here is the RESPONSE, not the fill. */
.btn-secondary:hover, .btn-ghost:hover, .btn-soft:hover, .cb-btn--quiet:hover {
  border-color: var(--vb-fill);
  color: var(--vb-fill);
  background: rgba(10, 112, 152, 0.07);
}

/* ⚑ FILLED, NOT OUTLINED. A primary action drawn as an outline reads as the
   secondary one beside it — the single most common thing a child misses, and the
   first thing the Y6 tester said. */
.btn-primary, .k-btn--primary, .cm-btn--primary, .btn.btn-primary,
button.btn-primary, a.btn-primary {
  background: var(--vb-fill) !important;
  border: 2px solid var(--vb-fill) !important;
  color: var(--vb-on-fill) !important;
  font-weight: 750;
  box-shadow: 0 2px 0 0 var(--vb-fill-deep), 0 4px 14px rgba(0, 0, 0, 0.14);
}

/* 60px on the primary action: what the brief asks for, and comfortably above the
   44px WCAG 2.5.5 floor this repo already holds every target to. */
.btn-primary.btn-lg, .btn-lg.btn-primary, .k-btn--primary.k-btn--lg {
  min-height: 60px; font-size: 1.06rem; padding-inline: 26px;
}

.btn:hover, .k-btn:hover, .cm-btn:hover, .lnd-btn:hover,
.mc-btn:hover, .kp-btn:hover, .hint-btn:hover {
  transform: translateY(-1px) scale(1.02);
  filter: brightness(1.06);
}
.btn-primary:hover, .k-btn--primary:hover, .cm-btn--primary:hover {
  background: var(--vb-fill-lift) !important;
  border-color: var(--vb-fill-lift) !important;
}

/* Pressed: down and deeper, with the shadow collapsing so it visibly sinks. */
.btn:active, .k-btn:active, .cm-btn:active, .lnd-btn:active,
.mc-btn:active, .kp-btn:active, .hint-btn:active {
  transform: scale(0.98);
  filter: brightness(0.96);
}
.btn-primary:active, .k-btn--primary:active, .cm-btn--primary:active {
  background: var(--vb-fill-deep) !important;
  border-color: var(--vb-fill-deep) !important;
  box-shadow: 0 0 0 0 var(--vb-fill-deep), 0 1px 4px rgba(0, 0, 0, 0.18);
}

/* ⚑ DISABLED IS A DIFFERENT COLOUR, NOT A FADED ONE. `opacity: .5` on a blue
   button is a pale blue button, which reads as a blue button on a bad screen and
   gets tapped anyway. A grey fill reads as off. */
.btn:disabled, .btn[disabled], .btn[aria-disabled="true"],
.k-btn:disabled, .cm-btn:disabled, .mc-btn:disabled, .hint-btn:disabled,
button:disabled {
  background: var(--vb-off) !important;
  border-color: var(--vb-off) !important;
  color: var(--vb-off-ink) !important;
  box-shadow: none !important;
  transform: none !important;
  filter: none !important;
  cursor: not-allowed;
}

/* The keyboard has to see everything the pointer sees. */
.btn:focus-visible, .k-btn:focus-visible, .cm-btn:focus-visible,
.lnd-btn:focus-visible, .mc-btn:focus-visible, .kp-btn:focus-visible,
.hint-btn:focus-visible, a:focus-visible, button:focus-visible {
  outline: 3px solid var(--vb-ring);
  outline-offset: 2px;
}

/* ── 2. a button that has just become available says so, once ─────────────── */

/* ⚑ TWO BEATS, THEN STILL. A control that pulses forever is a control a child
   stops seeing, and it competes with the board for attention for the whole
   lesson. Long enough to catch the eye at the moment it appears, and no longer. */
@keyframes vb-ready {
  0%, 100% { box-shadow: 0 2px 0 0 var(--vb-fill-deep),
                         0 4px 14px rgba(0, 0, 0, 0.14),
                         0 0 0 0 rgba(10, 112, 152, 0.55); }
  50%      { box-shadow: 0 2px 0 0 var(--vb-fill-deep),
                         0 4px 14px rgba(0, 0, 0, 0.14),
                         0 0 0 10px rgba(10, 112, 152, 0); }
}
.is-ready { animation: vb-ready 1s var(--vb-lift) 2; }

/* ── 3. fields, toggles, tiles, nav ───────────────────────────────────────── */

input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], select, textarea {
  transition: border-color 140ms ease, background-color 140ms ease,
              box-shadow 140ms ease;
}
input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus,
input[type="number"]:focus, input[type="search"]:focus, select:focus,
textarea:focus {
  border-color: var(--vb-ring) !important;
  background: #F6FBFD;
  box-shadow: 0 0 0 3px rgba(10, 112, 152, 0.18);
  outline: none;
}

input[type="checkbox"], input[type="radio"] {
  accent-color: var(--vb-fill);
  width: 20px; height: 20px; cursor: pointer;
}

/* A card you can choose lifts under the pointer and sinks when taken. */
/* ⚑ THE CLASS LIST IS COUNTED FROM THE TEMPLATES, NOT GUESSED. The kid's home,
   the practice hub and the section pages draw their choices as `kpath-tile`,
   `ks-tile` and `kx-act` — none of which a list of `lh-tile`/`mc-tile` reaches,
   so those three surfaces had no hover feedback at all while the sweep reported
   them clean by finding nothing to measure. */
.lh-tile, .mc-tile, .kh-tile, .sec-tile, .k-card--pick, .cm-thread,
.kpath-tile, .ks-tile, .kx-act {
  transition: transform 160ms var(--vb-lift), box-shadow 160ms ease,
              border-color 160ms ease;
}
.lh-tile:hover, .mc-tile:hover, .kh-tile:hover, .sec-tile:hover,
.k-card--pick:hover, .cm-thread:hover,
.kpath-tile:hover, .ks-tile:hover, .kx-act:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.14);
}
.lh-tile:active, .mc-tile:active, .kh-tile:active, .sec-tile:active,
.kpath-tile:active, .ks-tile:active, .kx-act:active {
  transform: translateY(-1px) scale(0.995);
}

/* Where you are is a filled state, not a slightly darker word. */
.knav-bottom__item.is-active, .adm-nav a.is-active, .k-nav a.is-active {
  color: var(--vb-fill);
  font-weight: 750;
}
.knav-bottom__item.is-active .knav-bottom__ico { transform: scale(1.08); }

/* ── 4. it worked / it did not ────────────────────────────────────────────── */

@keyframes vb-pop {
  0%   { transform: scale(0.94); }
  60%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}
.is-confirmed { animation: vb-pop 320ms var(--vb-lift); }

.flash-success, .k-flash--ok   { border-left: 5px solid var(--vb-ok); }
.flash-error, .k-flash--error  { border-left: 5px solid var(--vb-warn); }
.flash-success, .flash-error, .k-flash--ok, .k-flash--error {
  animation: vb-pop 320ms var(--vb-lift);
}

/* ── 5. and none of it moves for someone who asked it not to ──────────────── */
/* ⚑ THE COLOUR SURVIVES, THE MOVEMENT GOES. Session 21 recorded getting this
   backwards on the bubble flash: `animation: none` under `reduce` removed the
   SIGNAL along with the motion, and a colour change is not motion. */
@media (prefers-reduced-motion: reduce) {
  .btn, .k-btn, .cm-btn, .lnd-btn, .mc-btn, .kp-btn, .hint-btn,
  .lh-tile, .mc-tile, .kh-tile, .sec-tile {
    transition: background-color 120ms ease, border-color 120ms ease,
                color 120ms ease;
  }
  .btn:hover, .k-btn:hover, .cm-btn:hover, .lnd-btn:hover, .mc-btn:hover,
  .kp-btn:hover, .hint-btn:hover, .lh-tile:hover, .mc-tile:hover,
  .kh-tile:hover, .sec-tile:hover, .btn:active, .k-btn:active {
    transform: none;
  }
  .is-confirmed, .flash-success, .flash-error { animation: none; }
  .is-ready { animation: none; box-shadow: 0 0 0 4px rgba(10, 112, 152, 0.45); }
}

/* ── 6. the Masterclass ground, for the surfaces that are not the chalkboard ─ */

/* ⚑ THE SAME BRAND GROUND ON ALL FOUR SECTIONS. The chalkboard sets its own in
   mc_quant.css because it also restyles its header and panels; abstract and the
   shared reading/writing board only need the ground and the white surfaces they
   already have to sit on it. Stated once here rather than copied into two more
   sheets. */
/* ⚑ `.mx.mx--lesson`, NOT `.mx--lesson`. mc_abstract.css sets `background` on
   `.mx` as a shorthand at the same (0,1,0) specificity; source order put vibrant
   later so it should have won, and measured it did not — the abstract lesson
   still computed `background-image: none`. Two classes settle it without relying
   on which sheet loads last, which is the safer form for a rule that has to hold
   across four surfaces and three stylesheets. */
.mx.mx--lesson, .mc--lesson, .mc[data-phase] {
  background: linear-gradient(180deg, var(--vb-fill) 0%, var(--vb-fill-deep) 100%);
}
.mx--lesson .mx-top, .mc[data-phase] .mc-top {
  background: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, 0.22);
}
.mx--lesson .mx-top, .mx--lesson .mx-top a, .mx--lesson .mx-top span,
.mc[data-phase] .mc-top, .mc[data-phase] .mc-top a,
.mc[data-phase] .mc-top span { color: #FFFFFF; }

/* And the same footer rule: a lesson is a fixed frame, not a scrolling page. */
body:has(.mx--lesson) > .korva-footer,
body:has(.mc[data-phase]) > .korva-footer { display: none; }
/* ⚑ READING IS EXEMPT FROM THE NO-SCROLL RULE, AND THAT IS A MEASUREMENT FROM
   SESSION 17, NOT AN OMISSION. A bank passage is 41-53 numbered lines; that
   section deliberately RELEASES the fixed frame at every width because there is
   no split of a 740px board that shows a passage and the question about it at
   once. Pinning `overflow: hidden` on it would not make the passage fit — it
   would clip it, and a child could not reach the end of what they are being asked
   to read. The page scrolls there by design and the footer still goes. */
html:has(.mx--lesson), body:has(.mx--lesson),
html:has(.mc[data-phase]:not([data-section="reading"])),
body:has(.mc[data-phase]:not([data-section="reading"])) { overflow-y: hidden; }

/* ── 7. the section pages carry their section's colour too (Session 27) ───── */

/* ⚑ THREE OF THE FOUR SECTION PAGES WERE PALE BLUE, AND THE CAUSE WAS ONE
   MISSING ATTRIBUTE. Every ground rule here keys off `[data-section]`, and only
   reading's list root carried it — so quant, abstract and writing fell through to
   the `:root` default, which is the brand cerulean, and read as "some page in
   Korva" rather than as that section. The attribute is on all four roots now.

   ⚑ AND THE `--lesson` ROOTS WERE STYLED WHILE THE `--list` ROOTS WERE NOT.
   Session 26 gave each section a lesson ground and stopped there; the page that
   LISTS the week's lessons is a different root on every surface and nobody had
   painted three of them. One rule, four surfaces — reading keeps its own, which
   is why it is not in this selector.

   The whiteboard itself stays white. That is Sessions 20-25 and it is not what
   this changes: this is the page BEHIND the tiles. */
/* ⚑ THE GROUND IS DECLARED IN EACH SECTION'S OWN SHEET, NOT HERE, AND THAT IS A
   LOAD-ORDER FACT RATHER THAN A PREFERENCE. Every section page loads its
   `mc_<section>.css` in `head_last`, which is AFTER this file — so a `.cb--list`
   rule written here loses to `.cb`'s own background at equal specificity, and
   measured exactly that: `background-image: none` on all three pages while the
   rule sat in the sheet looking correct. Reading already declared its own, which
   is why reading was the one page that worked. The tokens still come from here. */

/* ⚑ THE TILES STAY WHITE AND EVERY WORD LIVES ON THEM. learn.css already records
   the measurement this follows: four of six briefed gradients cannot carry white
   text, so a coloured panel is DECORATION and the words sit on a solid surface
   beneath it. Same rule here — the ground is the section, the tile is the page. */
.cb--list .cb-qcard, .cb--list .cb-lrow,
.mx--list .mx-qcard, .mx--list .mx-lrow,
.wk--list .wk-qcard, .wk--list .wk-lrow {
  background: #FFFFFF;
}

/* Chrome on the coloured ground goes white, all of it — not just the links.
   Session 23 recorded the half-done version of this: "Lesson 1 of 4" kept a muted
   grey on the blue and measured well under AA. */
.cb--list .cb-top, .cb--list .cb-top a, .cb--list .cb-top span,
.mx--list .mx-top, .mx--list .mx-top a, .mx--list .mx-top span,
.wk--list .wk-top, .wk--list .wk-top a, .wk--list .wk-top span,
.cb--list .cb-head__h, .mx--list .mx-head__h, .wk--list .wk-head__h,
.cb--list .cb-head__p, .mx--list .mx-head__p, .wk--list .wk-head__p {
  color: #FFFFFF;
}
