/* ai.heart — the responsive floor. One file, three named bands, no retyped
   pixel values anywhere else.
   ─────────────────────────────────────────────────────────────────────────
   Written 2026-08-08 for BestYou·AI phone + tablet, and shared from the
   start so the rest of the estate can adopt it without a second dialect.

   THE MEASUREMENT THAT DROVE THIS FILE
   ────────────────────────────────────
   `upscale.css` sets html{zoom:1.333333} on every page that links it. Zoom
   does not change what a media query sees — a 390px phone still matches
   `(max-width:390px)` — but it does divide the room the layout actually
   gets. Measured in Chrome at a 390px viewport on wall.html:

       viewport 390px  ->  layout had 292.5 CSS px   (grid column, measured)

   So every existing breakpoint was reasoning about a width the content
   never receives. The gap widens as the screen narrows:

       320 -> 240      390 -> 293      430 -> 323
       600 -> 450      768 -> 576      834 -> 626
      1024 -> 768     1280 -> 960     1440 -> 1080

   THE FIX IS A GRADUATED UPSCALE, NOT AN AMPUTATION
   ─────────────────────────────────────────────────
   The zoom is not a bug to delete. At 390px it renders body copy at 22.7px
   physical and headings at 61px — genuinely legible, and part of why the
   site feels generous rather than cramped. Deleting it would shrink every
   phone page to spec-compliant blandness, which the brief forbids: a
   reduction must be a translation, never a deletion.

   So the upscale is relaxed by band rather than removed. Each step keeps
   type comfortably above the 16px floor while handing the layout back the
   room it needs to hold its shape:

       desktop  1.3333  unchanged — the look everyone signed off
       tablet   1.1500  ~668 CSS px at 768 — two columns still fit
       phone    1.0000  full width to the layout; type carries itself

   Type is re-scaled per band on its own terms below, so relaxing the zoom
   does not silently shrink the reading experience. */

:root {
  /* The bands, named once. Referenced by comment in every file that uses
     them — CSS cannot interpolate custom properties into media queries, so
     the contract is: these three numbers, and nowhere else. */
  --bp-phone:  599px;   /* phone   <= 599 */
  --bp-tablet: 1023px;  /* tablet   600 – 1023 */
                        /* desktop >= 1024 */

  /* Rhythm — one scale, so spacing never gets retyped either. */
  --gap-xs: 8px;
  --gap-s: 14px;
  --gap-m: 22px;
  --gap-l: 34px;
  --gap-xl: 52px;

  /* The page's own side padding, band-aware. */
  --pad-side: 34px;

  /* Minimum comfortable touch target. */
  --touch: 44px;
}

/* ── tablet: 600 – 1023 ────────────────────────────────────────────────── */
@media (min-width: 600px) and (max-width: 1023px) {
  html:not([data-no-upscale]) { zoom: 1.15; }
  :root { --pad-side: 26px; --gap-xl: 40px; }
}

/* ── phone: <= 599 ─────────────────────────────────────────────────────── */
@media (max-width: 599px) {
  html:not([data-no-upscale]) { zoom: 1; }
  :root { --pad-side: 18px; --gap-xl: 30px; --gap-l: 24px; }
}

/* ═══ TYPE ════════════════════════════════════════════════════════════════
   Headings tighten harder than body copy, because a heading at phone size
   is decoration competing with the words underneath it. Body never drops
   below 16px — below that iOS zooms the page on input focus, which breaks
   the layout the moment someone taps a field.

   Specificity note, learned by measuring rather than guessing: the pages
   set type through two-class selectors like `.block h2` and `.block p`,
   which outrank a bare `h2`. Matching that weight is what makes these
   rules land — so the component selectors are named explicitly below.
   Escalating with !important would win too, but it would also make every
   future per-page override impossible, so it is not used. */

@media (max-width: 1023px) {
  h1, .block h1, .hero h1 { font-size: clamp(30px, 6.2vw, 52px); line-height: 1.12; }
  h2, .block h2, .hero h2 { font-size: clamp(25px, 5.0vw, 42px); line-height: 1.16; }
  h3, .block h3           { font-size: clamp(19px, 3.2vw, 25px); line-height: 1.25; }
  body, p, li,
  .block p, .hero p       { font-size: clamp(16px, 2.1vw, 18px); line-height: 1.62; }
}

@media (max-width: 599px) {
  h1, .block h1, .hero h1 { font-size: clamp(29px, 7.6vw, 38px); letter-spacing: -.015em; }
  h2, .block h2, .hero h2 { font-size: clamp(24px, 6.4vw, 32px); letter-spacing: -.012em; }
  h3, .block h3           { font-size: clamp(18px, 4.6vw, 21px); }
  body, p, li,
  .block p, .hero p       { font-size: 16.5px; line-height: 1.6; }

  /* The wall's headings cap at 11em, which at phone width squeezes them
     into a narrow ribbon. Let them use the room. */
  .block h2 { max-width: none; }
  .block p  { max-width: none; margin-bottom: var(--gap-m); }

  /* Long words and URLs must not push the page sideways. */
  p, li, h1, h2, h3, .kicker, .note { overflow-wrap: break-word; }
}

/* ── the last 16px, measured at 320 ──────────────────────────────────────
   Two separate causes found at a 320px viewport, where the page ran to
   336px:

   1. `.chip-fw` sets white-space:nowrap on framework names that are up to
      298px long ("IPSFF Fraud Prevention Savings…"). At 320 minus padding
      there is nowhere for them to go, so they pushed the page open.
   2. The masthead's brand + nav + theme-toggle row simply does not fit at
      320, and its overflow is invisible because .nav is display:none — so
      it was 16px of pressure from a row that looks empty.

   Both are fixed at the smallest band only, so nothing changes above it. */
@media (max-width: 380px) {
  .chip, .chip-fw {
    white-space: normal;
    overflow-wrap: anywhere;
    max-width: 100%;
  }

  .mast-in, .bar {
    flex-wrap: wrap;
    gap: var(--gap-xs);
    max-width: 100%;
  }
  .mast-in > *, .bar > * { min-width: 0; }
}

/* ═══ NO HORIZONTAL SCROLL, EVER ══════════════════════════════════════════
   The body never scrolls sideways. Anything genuinely wide scrolls inside
   its own container instead, so the page frame stays still. */

@media (max-width: 1023px) {
  /* `overflow-x:hidden` on <html> is the classic way to stop sideways
     scroll, and it is a trap: it makes <html> a scroll container, and where
     a page also sets a fixed body height that container swallows vertical
     scrolling entirely. Measured on wall.html at 390px before this was
     corrected — content ran to 1,720px, html reported 844px, and
     window.scrollTo(0,900) did not move. The second block, the seals and
     the footer were all unreachable on a phone.

     So the clip goes on <body> only, and <html> is left alone. Sideways
     overflow is still contained; the page still scrolls. */
  body { max-width: 100%; overflow-x: clip; }

  img, svg, video, canvas { max-width: 100%; height: auto; }

  table, pre, .scroll-x {
    display: block;
    max-width: 100%;
    overflow-x: auto;
  }
}

/* ═══ TOUCH ═══════════════════════════════════════════════════════════════
   Anything pressable clears 44px on a touch device. Applied by pointer
   capability rather than width, because a small laptop with a mouse does
   not need fat targets and a large tablet does. */

@media (pointer: coarse) {
  a.act, button, .tbtn, [role="button"], .wax, summary {
    min-height: var(--touch);
  }
  a.act, button, [role="button"] {
    min-width: var(--touch);
  }
}

/* Measured findings from the 9-width sweep, fixed at their source.
   These are width-based rather than pointer-based because the sweep runs
   in a desktop browser reporting a fine pointer — and a phone-width window
   should behave like a phone regardless of what is driving it. */
@media (max-width: 1023px) {

  /* Footer and nav lists set `line-height:2` on the UL, which styles the
     line box but leaves each <a> only ~18px of hit area. Padding goes on
     the link itself so the target grows, not just the gap around it. */
  .foot ul li, .nav-panel li { line-height: 1.25; }
  .foot ul a, .nav-panel a {
    display: inline-flex;
    align-items: center;
    min-height: var(--touch);
    padding: 4px 0;
  }

  /* The read-aloud speaker is the control this whole estate is built
     around — it must never be the hardest thing on the page to press. */
  .spk {
    min-width: var(--touch);
    min-height: var(--touch);
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* The read-aloud dock's own rows (voice.css) measured 37–39px. They are
     the controls that start a reading, so they get the same floor as
     everything else that is meant to be pressed. */
  .kd-row, .kd-all { min-height: var(--touch); }

  /* The tour's own control — held to the rule it was written under. */
  .tour-start { min-height: var(--touch); }
}

/* Small-caps furniture — wax captions, tags, flags, hints.
   ─────────────────────────────────────────────────────────────────────────
   CORRECTED 2026-08-08 after a red-team pass measured the live site. These
   were set to 13px here and called "lifted to a legible floor", which was
   wrong: on desktop 13px × 1.333 zoom reads as 17px physical, but at phone
   width the zoom is 1, so 13px is simply 13px. Measured at 320px, 112
   elements across 11 classes rendered under 15.5px physical — including
   `.wax-cap` at 10px, which is the caption on the governance seal, and
   `.chip` at 12px across 44 instances of the course metadata people
   actually read.

   The floor is now 15.5px minimum for anything carrying meaning. These
   stay visibly secondary through weight, colour and letter-spacing rather
   than through being too small to read — which is the honest way to make
   something quiet.

   ON !important, USED DELIBERATELY AND ONLY HERE
   ──────────────────────────────────────────────
   Measured on the live site after the first attempt: these rules did
   nothing. `.stamp .wax-cap` (two classes, in the page's own inline CSS at
   a 640px breakpoint) sets 10px and beats a single-class selector here no
   matter what value it carries. Raising specificity by guessing at every
   page's ancestor chain would be a guessing game this file loses on the
   next page that adds a wrapper.

   A floor is exactly the case !important exists for: it is not a
   preference competing with page styles, it is a minimum that must hold
   whatever a page asks for. It is confined to font-size, inside the phone
   band, on this named list — nothing else in this file uses it. */
@media (max-width: 599px) {
  .wax-cap, .tag, .hint, .card-flag, .card-lvl, .card-code, .pill,
  .bcap, .sub, .grade, .eyebrow, .gov-stamps-lead, .step-n, .spec-kind,
  .spec-code, .aud-c b, .chip, .chip-fw {
    font-size: 15.5px !important;
    letter-spacing: .03em;
  }

  /* Genuine fine print — the legal note and the disclaimers. Still the
     smallest thing on the page, but above the readable floor. */
  .foot-note, .honest, .note, .aud-c em, .card-foot, .msg, .back,
  .sig, .sf b, .lrow code {
    font-size: 15.5px !important;
    line-height: 1.55;
  }

  /* Secondary description text sits just above the fine print. */
  .aud-c span, .tier p, .tier ul { font-size: 16px !important; }
}

/* Tablet carries a 1.15 zoom, so a 13px rule reads as ~15px physical —
   close enough to the floor to matter. Lift the same set there. */
@media (min-width: 600px) and (max-width: 1023px) {
  .wax-cap, .tag, .card-flag, .card-lvl, .card-code, .pill,
  .bcap, .sub, .grade, .eyebrow, .chip {
    font-size: 14px;
  }
}

/* The certificates ladder keeps a fixed 80px code column down to 820px. At
   320px that leaves ~230px for a course title beside an uppercase code with
   no break opportunity, so the row jams. Below the phone band the code
   stacks above its title and takes the full width it needs. */
@media (max-width: 599px) {
  .lrow {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .lrow code { justify-self: start; }
}

/* The skip link is a keyboard affordance, not a touch target — it sits
   offscreen until focused, so a min-height on it would be measuring
   something no thumb can reach. It is sized when it is actually visible. */
.skip:focus {
  min-height: var(--touch);
  display: inline-flex;
  align-items: center;
}

/* ═══ THE PHONE MENU ══════════════════════════════════════════════════════
   `.nav` is display:none below 860px on index, course and certificates —
   four real destinations with nothing in their place. navmenu.js rebuilds
   them from the links already in the page; this dresses the result in the
   site's own register (navy ground, gold rule) rather than a new one. */

.nav-toggle { display: none; }

@media (max-width: 1023px) {
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--touch);
    min-height: var(--touch);
    padding: 0;
    background: none;
    border: 0;
    color: inherit;
    cursor: pointer;
    border-radius: 8px;
  }
  .nav-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
  .nav-toggle.on { color: #e6cf92; }
}

@media (min-width: 1024px) {
  .nav-panel, .nav-scrim { display: none !important; }
}

.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: 96;
  background: rgba(6, 14, 28, .55);
}

.nav-panel {
  position: fixed;
  z-index: 97;
  top: 0; right: 0;
  width: min(84vw, 320px);
  height: 100dvh;
  padding: calc(var(--touch) + var(--gap-m)) var(--pad-side) var(--gap-l);
  overflow-y: auto;
  background: #0a1b33;
  border-left: 1px solid rgba(230, 207, 146, .28);
  box-shadow: -18px 0 46px rgba(0, 0, 0, .42);
}

.nav-panel ul { list-style: none; margin: 0; padding: 0; }

.nav-panel li + li { border-top: 1px solid rgba(255, 255, 255, .08); }

.nav-panel a {
  display: flex;
  align-items: center;
  min-height: var(--touch);
  padding: var(--gap-xs) 0;
  color: #eaf2fb;
  text-decoration: none;
  font-size: 17px;
  letter-spacing: .01em;
}
.nav-panel a:hover, .nav-panel a:focus-visible { color: #e6cf92; }

/* The page behind the menu stays put. */
html.nav-open, html.nav-open body { overflow: hidden; }

/* ═══ FOCUS ═══════════════════════════════════════════════════════════════
   The red-team pass found focus styling defined only on `.skip`, `.tbtn`
   and `.wax` — so ordinary links in the nav, the body and the footer, and
   the sign-in form's submit button, could be tabbed to with nothing on
   screen showing where you were. A keyboard user cannot use a page whose
   focus is invisible.

   This is a floor, not a per-page decision: every focusable thing gets a
   visible ring unless a page has deliberately styled its own. `:focus-visible`
   means a mouse press does not draw it — only keyboard navigation does. */
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid #c8a95e;
  outline-offset: 2px;
  border-radius: 3px;
}

:root[data-theme="dark"] a:focus-visible,
:root[data-theme="dark"] button:focus-visible,
:root[data-theme="dark"] [role="button"]:focus-visible,
:root[data-theme="dark"] input:focus-visible,
:root[data-theme="dark"] select:focus-visible {
  outline-color: #e6cf92;
}

/* ═══ MOTION ══════════════════════════════════════════════════════════════
   Honour the system preference everywhere, including anything added later
   by script. This is a floor, not a per-component decision. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}
