/* Threshold MLO Prep - generated by make_site_css.py from style.prototype.css.
   DO NOT EDIT THIS FILE. Edit the prototype and regenerate, or the
   next regeneration silently reverts whatever was changed here.
   Brand #322277. Hue rotated from Evergreen; luminance preserved, so every
   contrast ratio measured on the approved design still holds here. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
/* =============================================================================
   Threshold MLO Prep - derived from the PROTOTYPE stylesheet (approved on Evergreen), 14 September 2026
   Drop-in replacement for assets/style.css. No markup changes anywhere.

   WHAT CHANGED AND WHY. Four things were wrong with the old look, and they
   were all the same mistake in different clothes: nothing had a hierarchy.

     1. A saturated green slab sat at the top of every page, so the brand
        colour was spent before the page said anything. Colour is now
        CONCENTRATED: one deep-green hero on the home page, green on the
        things you can act on, and a quiet ground everywhere else.
     2. Everything was a card - hero, modules, FAQ, samples, results - each
        with its own shadow, so nothing read as more important than anything
        else. Cards are now hairline-ruled and flat; lift is reserved for
        things that respond to a pointer.
     3. One typeface did every job. Three now, each with a job: Source Serif 4
        for headings and figures (this is a study product - it should look
        like one), IBM Plex Sans for interface text, IBM Plex Mono for
        anything a candidate reads as DATA - counts, timers, option letters,
        citations, scores.
     4. No dark mode at all. There is one now.

   THE CASCADE BUG THIS FILE IS BUILT TO PREVENT. In the published mockup a
   "Start exam" button rendered white-on-white, because a rule inside a
   theme guard outscored the component rule that set its colour. So:
   TEXT-ON-ACCENT IS ALWAYS A TOKEN (--on-accent, --on-deep). Never write a
   colour for text sitting on a brand surface anywhere but in the token
   block, and never let a media query be the only place a colour is defined.

   PER-SITE BRANDING is the first block below and nothing else. The other
   four sites are a five-line swap; their values are recorded there so no
   one has to go digging in a fork for them (DESIGN-PRINCIPLES Layer 2 #7:
   a fork inherits the donor's brand unless someone stops it).
   ============================================================================= */

:root{
  /* ---------- BRAND: the only block that differs between the five sites ----
     Evergreen    #15643a   deep #0D3A28   (this file)
     Hangar       #bf3f00   deep #7A2800
     Lantern      #0e7490   deep #08475A
     Manifest     #4e2059   deep #2F1236
     Wheelhouse   #0b4f8a   deep #073156
     --accent-ink is the accent darkened until it passes AA on --surface for
     small text; --on-accent is what sits ON the accent. Both are set here,
     for every site, so no component ever guesses.                          */
  --accent:#322277;
  --accent-bright:#3E2A93;
  --accent-ink:#322277;
  --accent-wash:#F0EEF6;
  --on-accent:#FFFFFF;
  --brand:#302173;          /* every brand SURFACE: masthead and hero alike.  */
  --on-brand:#FFFFFF;       /* Never --accent, which is tuned for small fills */
  --accent-deep:var(--brand);  /* and inverts in dark.                        */
  --brand-bar:var(--brand);    /* kept as aliases so no rule had to be hunted */
  --on-brand-bar:var(--on-brand);
  --on-deep:#E7E5F0;
  --on-deep-dim:rgba(231,229,240,.88);

  /* ---------- Neutrals ---------- */
  --ground:#F6F6F8;
  --surface:#FEFEFE;
  --sunken:#F1F0F5;
  --ink:#1D1734;
  --body:#484559;
  --muted:#6C6979;
  --rule:#E0DFE6;
  --rule-soft:#ECECF1;

  /* ---------- Secondary accent: brass. Used for emphasis that must NOT
       read as "correct" - counts, freshness, flags, price.              ---- */
  --brass:#9A6B2E;          /* brass for SMALL accents: rules, counts, citations */
  --brass-ink:#7A5322;
  --brass-wash:#F4E8D6;
  --brass-on-deep:#C68C41;  /* 5.11:1 on --accent-deep; --brass is 2.73:1 there */
  /* A BUTTON is not a small accent. --brass as a fill measured 3.56:1 against its
     own dark label - below AA, and visibly muddy: a mid-tone is too dark to read
     as inviting and too light to carry white text. The buy button gets its own
     lighter fill, which clears AA at 7.4:1 and actually looks like amber. */
  --buy:#D9A441;
  --buy-hover:#C89334;
  --on-buy:#2A1C08;

  /* ---------- Semantic ---------- */
  --good:#176B48; --good-wash:#DCEDE3; --good-rule:#A9CFBA;
  --bad:#9B3124;  --bad-wash:#F7E2DE;  --bad-rule:#E0B4AB;
  --flag:#8A6212; --flag-wash:#F7ECD5;

  /* ---------- Form ---------- */
  --r-lg:16px; --r:12px; --r-sm:9px;
  --lift:0 1px 2px rgba(37,28,71,.05),0 2px 6px rgba(37,28,71,.05);
  --lift-md:0 6px 18px rgba(37,28,71,.08);
  --lift-hi:0 12px 28px -10px rgba(29,23,52,.20),0 3px 8px rgba(29,23,52,.08);
  --ring:0 0 0 3px var(--accent-wash);

  --sym:"Segoe UI Symbol","Apple Symbols","Noto Sans Symbols 2","DejaVu Sans";
  /* Inter for everything the reader reads, IBM Plex Mono for everything the
     reader SCANS. --display is kept as a separate token even though it points at
     the same family: every heading rule already references it, so trying another
     display face later is one line rather than a hunt. */
  --display:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,Arial,var(--sym),sans-serif;
  --sans:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,Arial,var(--sym),sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,Consolas,var(--sym),monospace;
  --serif:var(--display);  /* legacy alias - earlier rules referenced --serif */
  --font:var(--sans);   /* legacy aliases: still referenced from inline */
  --line:var(--rule);   /* styles in app.js and older generated markup.   */
  --bg2:var(--sunken);  /* Dropping one of these removes a border or a    */
  --card:var(--surface);/* background somewhere no CSS rule can reach.    */
  --bg:var(--ground);
  color-scheme:light dark;
}

/* Dark. Only tokens are redefined - never a component. The :not() guard lets
   an explicit light choice win over the OS preference. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --accent:#9385CF; --accent-bright:#ABA0D8; --accent-ink:#C8C1E7;
    --accent-wash:#292344;
    --brand:#302173; --on-brand:#FFFFFF;
    --accent-deep:var(--brand); --brand-bar:var(--brand); --on-brand-bar:var(--on-brand);
    --on-accent:#120C27; --on-deep:#EEECF4; --on-deep-dim:rgba(238,236,244,.88);
    --ground:#110E1A; --surface:#1C1A28; --sunken:#232031;
    --ink:#EDEDF2; --body:#C1BFCA; --muted:#9490A2;
    --rule:#312F3E; --rule-soft:#282534;
    --brass:#CB9C5D; --brass-ink:#DEB77F; --brass-wash:#2B2317;
    --brass-on-deep:#C68C41;
    --buy:#E0AE52; --buy-hover:#D19E43; --on-buy:#231704;
    --good:#4FAE80; --good-wash:#15301F; --good-rule:#2C5A40;
    --bad:#D97A66;  --bad-wash:#33191A;  --bad-rule:#5E2F28;
    --flag:#D2A85E; --flag-wash:#2E2614;
    --lift:0 1px 2px rgba(0,0,0,.4);
    --lift-md:0 6px 18px rgba(0,0,0,.5);
    --lift-hi:0 12px 28px -10px rgba(0,0,0,.65),0 3px 8px rgba(0,0,0,.45);
    --ring:0 0 0 3px rgba(147,133,207,.28);
  }
}
:root[data-theme="dark"]{
  --accent:#9385CF; --accent-bright:#ABA0D8; --accent-ink:#C8C1E7;
  --accent-wash:#292344;
  --brand:#302173; --on-brand:#FFFFFF;
  --accent-deep:var(--brand); --brand-bar:var(--brand); --on-brand-bar:var(--on-brand);
  --on-accent:#120C27; --on-deep:#EEECF4; --on-deep-dim:rgba(238,236,244,.88);
  --ground:#110E1A; --surface:#1C1A28; --sunken:#232031;
  --ink:#EDEDF2; --body:#C1BFCA; --muted:#9490A2;
  --rule:#312F3E; --rule-soft:#282534;
  --brass:#CB9C5D; --brass-ink:#DEB77F; --brass-wash:#2B2317;
  --brass-on-deep:#C68C41;
  --buy:#E0AE52; --buy-hover:#D19E43; --on-buy:#231704;
  --good:#4FAE80; --good-wash:#15301F; --good-rule:#2C5A40;
  --bad:#D97A66;  --bad-wash:#33191A;  --bad-rule:#5E2F28;
  --flag:#D2A85E; --flag-wash:#2E2614;
  --lift:0 1px 2px rgba(0,0,0,.4);
  --lift-md:0 6px 18px rgba(0,0,0,.5);
  --lift-hi:0 12px 28px -10px rgba(0,0,0,.65),0 3px 8px rgba(0,0,0,.45);
  --ring:0 0 0 3px rgba(147,133,207,.28);
}

/* ---------- reset / base ---------- */
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{font-family:var(--sans);background:var(--ground);color:var(--body);
  font-size:15px;line-height:1.55;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
.wrap{max-width:1060px;margin:0 auto;padding-inline:20px;padding-block:0 72px}
h1,h2,h3,h4{font-family:var(--display);color:var(--ink);font-weight:700;line-height:1.2;
  text-wrap:balance;letter-spacing:-.019em}
/* Inter is loose by default at large sizes and tight at small ones; the tracking
   has to move with the size or headings read flabby and captions read cramped. */
h1{letter-spacing:-.028em}
h3{letter-spacing:-.012em}
/* Figures line up in columns only if they are tabular. Inter has them; ask. */
.sicell b,.num,.timer,.score-hero .big,.herostat b,td{font-variant-numeric:tabular-nums}
a{color:var(--accent-ink);text-underline-offset:2px}
a:hover{color:var(--accent)}
b,strong{color:var(--ink);font-weight:600}
/* ...EXCEPT on a brand surface, where --ink is unreadable. This is the cascade
   bug from the header note in its third costume: a global rule painting text a
   fixed colour, meeting a surface that is not the colour that rule assumed.
   It was found by the render harness on Wheelhouse, whose hero copy contains a
   <strong> - and only there, because no other site's hero copy happens to use
   one. The same latent fault sat in all five stylesheets, and a fourth costume
   is presumably waiting; `inherit` is the answer that does not need to know
   which surface it is on, so it holds for whatever gets painted brand next.
   (.hero .price b overrides this deliberately, further down, to go pure white.) */
.hero b,.hero strong,header.app b,header.app strong,
.herostat b,.herostat strong,button b,button strong,
.btn-buy b,.btn-buy strong,.tabs button.on b,.tabs button.on strong{color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ---------- masthead ------------------------------------------------------
   Was a saturated green slab on every page. Now a quiet editorial masthead:
   the brand is the only coloured thing, the exam title is the loudest thing,
   and a 2px ink rule does the work the green block used to do badly.      */
header.app{display:flex;justify-content:space-between;align-items:flex-end;gap:16px 24px;
  flex-wrap:wrap;background:var(--brand-bar);color:var(--on-brand-bar);border-radius:var(--r-lg);
  padding:18px 26px;margin:18px 0 20px;box-shadow:var(--lift-md);border:none}
header.app h1{font-size:clamp(1.4rem,3vw,1.95rem);margin-top:10px;color:var(--on-brand-bar);letter-spacing:-.024em}
header.app .sub{color:var(--on-brand-bar);opacity:.86;font-size:.88rem;max-width:76ch;margin-top:8px;line-height:1.5}
header.app .brand{color:var(--on-brand-bar);font-size:1.16rem}
header.app .brand:hover{color:var(--on-brand-bar);opacity:.88}
header.app .brandmark{color:var(--on-brand-bar)}
.brand{display:inline-flex;align-items:center;gap:9px;font-family:var(--display);font-weight:700;
  font-size:1.1rem;color:var(--accent-ink);text-decoration:none;letter-spacing:-.018em}
.brand:hover{color:var(--accent)}
header.app .brand{font-size:1.12rem}
.brandmark{height:1.32em;width:1.32em;vertical-align:-.22em;display:inline-block;color:var(--accent);flex:none}
header.app .brandmark{height:1.32em;width:1.32em;margin-right:0}

/* ---------- freshness stamp ---------- */
.freshness{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:.82rem;
  color:var(--muted);background:var(--accent-wash);border:1px solid var(--rule);
  border-radius:999px;padding:7px 15px;margin:0 0 20px;font-weight:500}
.freshness b{color:var(--ink);font-weight:600}
.freshness .fdot{width:6px;height:6px;border-radius:50%;background:var(--good);flex:none}

/* ---------- cards: hairline, flat, no competing shadows ---------- */
.card{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);
  padding:24px 26px;margin-bottom:18px;box-shadow:var(--lift)}
.card > p.muted,.card > p{max-width:80ch}
.card h2{font-size:1.24rem;padding-bottom:12px;border-bottom:1px solid var(--rule);margin-bottom:18px}
.card > .row:first-child{justify-content:space-between;align-items:baseline;
  border-bottom:1px solid var(--rule);padding-bottom:12px;margin-bottom:18px}
.card > .row:first-child h2{border-bottom:none;padding-bottom:0;margin-bottom:0}
h2{font-size:1.24rem;margin-bottom:12px}
h3{font-size:1rem;margin-bottom:8px;font-weight:600}
p{margin-bottom:10px}
p:last-child{margin-bottom:0}
.muted{color:var(--muted);font-size:.87rem}

/* ---------- hero: the one place the brand colour is spent ---------- */
.hero{background:var(--accent-deep);color:var(--on-deep);border:1px solid transparent;border-radius:var(--r-lg);
  padding:34px 36px 32px;margin-bottom:22px;box-shadow:none;position:relative;overflow:hidden}
.hero::after{content:"";position:absolute;inset:0 0 auto 0;height:3px;
  background:linear-gradient(90deg,var(--brass),transparent 62%)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .hero{border-color:rgba(255,255,255,.09)}}
:root[data-theme="dark"] .hero{border-color:rgba(255,255,255,.09)}
.hero .eyebrow{font-size:.76rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.1em;color:var(--brass-on-deep);margin-bottom:14px;display:flex;align-items:center;gap:12px}
.hero .eyebrow::after{content:"";height:1px;flex:1;max-width:140px;background:rgba(255,255,255,.18)}
.hero h1{font-family:var(--display);font-size:clamp(1.85rem,3.6vw,2.5rem);line-height:1.06;
  font-weight:700;letter-spacing:-.034em;color:#fff;margin-bottom:14px;max-width:24ch;border-bottom:none;padding-bottom:0}
.hero p{color:var(--on-deep);max-width:64ch;font-size:.97rem;margin-bottom:0}
.hero .price{font-size:.92rem;margin-top:18px;color:var(--on-deep-dim)}
.hero .price b{color:#fff;font-family:var(--display);font-size:1.08rem;font-weight:700;letter-spacing:-.018em}
.herostats{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:1px;margin-top:24px;
  background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.18);border-radius:var(--r);overflow:hidden}
.herostat{background:var(--brand);padding:13px 16px}
.herostat b{display:block;font-family:var(--display);font-size:1.45rem;font-weight:700;color:#fff;
  letter-spacing:-.022em;
  line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.herostat span{display:block;font-size:.78rem;color:var(--on-deep-dim);margin-top:7px}

/* ---------- section heads ---------- */
.grouphead{display:flex;align-items:baseline;gap:14px;margin:34px 0 16px;font-family:var(--display);
  font-size:1.16rem;font-weight:700;color:var(--ink);text-transform:none;letter-spacing:-.012em;
  border-bottom:2px solid var(--ink);padding-bottom:10px}
.grouphead::after{content:none}
.card > .grouphead:first-child{margin-top:0}

/* ---------- credential tiles: tiles + reactivity, as asked for ---------- */
.credgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(306px,1fr));gap:12px}
/* auto-fill lays down as many tracks as FIT, not as many as are needed, so a
   group with one credential (Manifest has exactly one) put a single tile in the
   first of three tracks and left two thirds of the row empty - it read as a
   missing pair rather than as a deliberate single. Counting the children in the
   selector is the honest fix; :has() can do it, so the markup does not have to
   carry a class saying how many of itself there are. */
.credgrid:has(> :first-child:last-child){grid-template-columns:minmax(0,1fr)}
.credgrid:has(> :nth-child(2):nth-last-child(1)){grid-template-columns:repeat(2,minmax(0,1fr))}
.credlink{position:relative;display:flex;align-items:center;gap:15px;padding:16px 18px;
  border:1px solid var(--rule);border-radius:var(--r);background:var(--surface);color:var(--body);
  text-decoration:none;box-shadow:var(--lift);overflow:hidden;
  transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease}
.credlink::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent);
  transform:scaleY(0);transform-origin:50% 50%;transition:transform .18s ease}
.credlink:hover{transform:translateY(-2px);box-shadow:var(--lift-hi);border-color:var(--rule)}
.credlink:hover::before{transform:scaleY(1)}
.credlink:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.credtile{width:46px;height:46px;flex:none;border-radius:var(--r-sm);background:var(--accent-wash);
  color:var(--accent-ink);display:flex;align-items:center;justify-content:center;
  font-weight:700;font-size:.9rem;letter-spacing:.02em}
.credbody{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}
.credname{font-family:var(--display);font-weight:600;font-size:1.02rem;color:var(--ink);letter-spacing:-.014em}
.credsub{font-size:.8rem;color:var(--muted)}
.credcount{font-size:.84rem;color:var(--ink);margin-top:6px;font-weight:500;
  display:flex;align-items:center;gap:7px;font-variant-numeric:tabular-nums}
.credcount::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--brass);flex:none}
.credsplit{font-size:.78rem;color:var(--muted);margin-top:2px}
.credchev{margin-left:auto;color:var(--muted);font-size:1.25rem;align-self:center;transition:color .16s,transform .16s}
.credlink:hover .credchev{color:var(--accent);transform:translateX(2px)}
.credlink.soon{opacity:.5;pointer-events:none;box-shadow:none}
.credlink.soon .credtile{background:var(--sunken);color:var(--muted)}
.credlink.mini{padding:13px 15px}
.credlink.mini .credtile{width:38px;height:38px;font-size:.76rem}
.credlink.mini .credname{font-size:.97rem}

/* ---------- state index --------------------------------------------------
   One row per state, both lines on the row: 25 rows where there were 50 tiles.
   A table would be the honest element here, but the generator emits divs and
   this is a CSS-led change; grid with matching column tracks on head and rows
   gives the same alignment without touching more markup than necessary.      */
.sitop{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px 18px;margin-bottom:14px}
.sitop .grouphead{margin:0;border:none;padding:0;flex:0 0 auto}
.sitools{display:flex;align-items:center;gap:12px;margin-left:auto;flex:1 1 260px;justify-content:flex-end}
.sisearch{flex:1 1 auto;max-width:280px;min-width:160px;font-size:.92rem;padding:8px 13px}
.sisearch::-webkit-search-cancel-button{cursor:pointer}
.sicount{font-size:.82rem;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
/* A full-width row per state left a dead zone between the name and the two
   controls, and stood 106px tall - so it read as a wide table row after all and
   gave back the compactness the index was for. Two columns of TILES instead:
   badge and name on top, the two lines as controls beneath. Same information,
   same one-row-per-state idea, half the height, and the shape the old site used. */
.siwrap{display:grid;grid-template-columns:repeat(auto-fill,minmax(424px,1fr));gap:12px;
  border:none;border-radius:0;overflow:visible}
.sihead{display:none}   /* column headers are a spreadsheet idiom; every value names itself now */
.sirow{display:grid;grid-template-columns:46px minmax(0,1fr) minmax(0,1fr);gap:8px 13px;
  align-items:center;padding:12px 15px;background:var(--surface);border:1px solid var(--rule);
  border-radius:var(--r);box-shadow:var(--lift);position:relative;overflow:hidden;
  transition:transform .16s ease,box-shadow .16s ease}
.siabbr{grid-column:1;grid-row:1/3}
.siname{grid-column:2/4;grid-row:1}
.sirow > .sicell:nth-child(3){grid-column:2;grid-row:2}
.sirow > .sicell:nth-child(4){grid-column:3;grid-row:2}
.sirow::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent);
  transform:scaleX(0);transform-origin:left;transition:transform .16s}
.sirow:hover{transform:translateY(-2px);box-shadow:var(--lift-hi)}
.sirow:hover::before{transform:scaleX(1)}
.sirow[hidden]{display:none}   /* .sirow sets display:grid, which would otherwise beat [hidden] */
.siabbr{width:46px;height:46px;display:flex;align-items:center;justify-content:center;
  background:var(--accent-wash);color:var(--accent-ink);border-radius:var(--r-sm);
  font-size:.86rem;font-weight:700;letter-spacing:.03em;align-self:center}
.siname{font-family:var(--display);font-size:1.02rem;font-weight:600;color:var(--ink);letter-spacing:-.014em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Each line is a control you can press, and it says which line it is - so the
   column headers are not needed and the row does not need to be read as a grid. */
.sicell{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 6px;text-decoration:none;
  color:var(--body);padding:7px 11px;border:1px solid var(--rule);border-radius:var(--r-sm);
  background:var(--surface);transition:background .13s,border-color .13s,box-shadow .13s}
.sicell::before{content:attr(data-line);flex:0 0 100%;font-size:.72rem;color:var(--muted);
  line-height:1.35;margin-bottom:1px}
.sicell b{font-weight:600;color:var(--accent-ink);font-variant-numeric:tabular-nums;font-size:.98rem;
  letter-spacing:-.012em}
.sicell .siq{color:var(--muted);font-size:.78rem}
.sicell .sigo{display:none}
.sicell:hover{background:var(--accent-wash);border-color:var(--accent);box-shadow:var(--lift)}
.sicell:hover::before{color:var(--accent-ink)}
.sicell.empty{display:flex;align-items:center;justify-content:center;color:var(--muted);opacity:.45;
  font-size:1rem;background:var(--sunken);border-style:dashed}
.sinone{padding:20px 16px;color:var(--muted);font-size:.9rem;margin:0}
.sinone[hidden]{display:none}
@media (max-width:720px){
  .siwrap{grid-template-columns:1fr}
  .sirow{grid-template-columns:44px minmax(0,1fr);grid-auto-rows:min-content;
    padding:13px 14px;gap:8px 12px}
  .siabbr{width:44px;height:44px;grid-column:1;grid-row:1}
  .siname{grid-column:2;grid-row:1}
  .sirow > .sicell:nth-child(3){grid-column:1/-1;grid-row:2}
  .sirow > .sicell:nth-child(4){grid-column:1/-1;grid-row:3}
  .sicell{margin:0;padding:9px 12px}
  .sicell.empty{display:none}
  .sitools{margin-left:0;justify-content:flex-start}
}

/* ---------- buttons ---------- */
button{font:inherit;font-family:var(--sans);font-weight:500;cursor:pointer;border-radius:var(--r-sm);
  border:1px solid var(--rule);background:var(--surface);padding:9px 17px;color:var(--ink);
  transition:background .14s,border-color .14s,color .14s,transform .14s,box-shadow .14s}
button:hover{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-ink)}
button:active{transform:translateY(1px)}
button.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:600;box-shadow:var(--lift)}
button.primary:hover{background:var(--accent-bright);border-color:var(--accent-bright);color:var(--on-accent);box-shadow:var(--lift-hi)}
button.danger{color:var(--bad);border-color:transparent;background:none}
button.danger:hover{background:var(--bad-wash);border-color:var(--bad-rule);color:var(--bad)}
button:disabled{opacity:.42;cursor:default;transform:none;box-shadow:none}
button:disabled:hover{background:var(--surface);border-color:var(--rule);color:var(--ink)}
.btn-buy{display:inline-flex;align-items:center;gap:9px;background:var(--buy);color:var(--on-buy);
  font-weight:700;padding:12px 22px;border-radius:var(--r-sm);text-decoration:none;box-shadow:var(--lift);
  transition:background .14s,box-shadow .14s,transform .14s}
.btn-buy:hover{background:var(--buy-hover);color:var(--on-buy);box-shadow:var(--lift-hi);transform:translateY(-1px)}
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.field{display:flex;flex-direction:column;gap:6px;font-size:.84rem;font-weight:500;color:var(--ink)}
.field > span:first-child{font-size:.78rem;color:var(--muted);font-weight:500}
select,input{font:inherit;font-family:var(--sans);padding:9px 12px;border:1px solid var(--rule);
  border-radius:var(--r-sm);background:var(--surface);color:var(--ink);transition:border-color .14s,box-shadow .14s}
select:focus,input:focus{outline:none;border-color:var(--accent);box-shadow:var(--ring)}

/* ---------- tables / pills ---------- */
table{border-collapse:collapse;width:100%;font-size:.89rem}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--rule-soft)}
th{color:var(--muted);font-weight:600;font-size:.78rem;border-bottom-color:var(--rule)}
td{font-variant-numeric:tabular-nums}
tr.histrow:hover td{background:var(--sunken)}
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:.8rem;
  background:var(--accent-wash);color:var(--accent-ink);font-weight:500}
.pill.good{background:var(--good-wash);color:var(--good)}
.pill.bad{background:var(--bad-wash);color:var(--bad)}

/* ---------- module tiles ---------- */
.modgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(272px,1fr));gap:13px}
.modcard{border:1px solid var(--rule);border-radius:var(--r);padding:17px 18px;background:var(--surface);
  box-shadow:var(--lift);display:flex;flex-direction:column;gap:11px;
  transition:transform .16s ease,box-shadow .16s ease}
.modcard:hover{transform:translateY(-2px);box-shadow:var(--lift-hi)}
.modcard h3{margin:0;font-size:1rem;line-height:1.28}
.modcard .meta{color:var(--muted);font-size:.8rem;margin:0}
.modchips{display:flex;flex-wrap:wrap;gap:6px}
.chip{font-size:.76rem;font-weight:500;color:var(--accent-ink);
  background:var(--accent-wash);border-radius:999px;padding:4px 11px;white-space:nowrap}
.chip.gold{color:var(--brass-ink);background:var(--brass-wash)}
.mprog{height:4px;border-radius:999px;background:var(--sunken);overflow:hidden}
.mprog > i{display:block;height:100%;background:var(--accent);transition:width .3s ease}
/* Prose, not data - it was reading as a machine field in mono. */
.mprog-label{font-family:var(--sans);font-size:.78rem;color:var(--muted);margin-top:0}

/* The module GROUP headings used to be reached with six !important
   declarations, because app.js wrote their typography as an inline style and an
   inline style beats a stylesheet. patch_app_shared.py removes that inline
   style and gives the h3 a class instead, so the rule can now win by being
   right rather than by shouting. The rule itself lives at the foot of the
   generated per-site files (make_site_css.py, SHARED_EXTRA) next to the note
   explaining the dependency, and covers the un-patched inline form too, so a
   site whose app.js has not been patched yet still renders correctly - just
   without the ability to override it later. */
#modGrid > p.muted{margin-top:6px}
.modcard button.primary{margin-top:auto;width:100%}
.modrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:4px}
.modrow button{flex:1;min-width:112px}
.note-soft{background:var(--brass-wash);color:var(--brass-ink);border-radius:var(--r-sm);
  padding:11px 15px;font-size:.86rem;font-weight:500;display:flex;align-items:center;gap:9px;
  border:1px solid transparent}

/* ---------- exam runner ---------- */
.testbar{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:16px}
.timer{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:600;font-size:1.02rem;
  padding:8px 16px;border-radius:var(--r-sm);background:var(--surface);border:1px solid var(--rule);
  color:var(--ink);box-shadow:var(--lift)}
.timer.low{color:var(--bad);border-color:var(--bad);background:var(--bad-wash)}
.qmeta{color:var(--muted);font-size:.82rem;margin-bottom:12px;
  display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.stem{font-family:var(--display);font-size:1.19rem;font-weight:500;color:var(--ink);
  margin-bottom:24px;white-space:pre-wrap;line-height:1.45;letter-spacing:-.011em;max-width:72ch}
.opt{display:flex;gap:14px;align-items:flex-start;border:1px solid var(--rule);border-radius:var(--r);
  padding:14px 16px;margin-bottom:9px;cursor:pointer;background:var(--surface);color:var(--body);
  font-size:.945rem;line-height:1.5;
  transition:border-color .13s,background .13s,box-shadow .13s,transform .13s}
.opt:hover{border-color:var(--accent);background:var(--accent-wash);color:var(--ink)}
.opt.sel{border-color:var(--accent);background:var(--accent-wash);color:var(--ink);box-shadow:inset 0 0 0 1px var(--accent)}
.opt .letter{font-family:var(--mono);font-weight:500;color:var(--muted);min-width:1.3em;padding-top:.08em}
.opt.sel .letter{color:var(--accent-ink);font-weight:600}
.opt.locked{cursor:default}
.opt.locked:hover{transform:none}
.opt.correct{border-color:var(--good);background:var(--good-wash);color:var(--ink);box-shadow:inset 0 0 0 1px var(--good)}
.opt.correct .letter{color:var(--good)}
.opt.wrong{border-color:var(--bad);background:var(--bad-wash);color:var(--ink);box-shadow:inset 0 0 0 1px var(--bad)}
.opt.wrong .letter{color:var(--bad)}
.palette{display:grid;grid-template-columns:repeat(auto-fill,minmax(40px,1fr));gap:6px}
.palette button{padding:9px 0;font-family:var(--mono);font-size:.78rem;font-variant-numeric:tabular-nums;position:relative}
.palette button.answered{background:var(--sunken);border-color:var(--rule);color:var(--ink);font-weight:600}
.palette button.right{background:var(--good-wash);border-color:var(--good-rule);color:var(--good)}
.palette button.wrong{background:var(--bad-wash);border-color:var(--bad-rule);color:var(--bad)}
.palette button.current{outline:2px solid var(--accent);outline-offset:1px}
.palette button.flagged::after{content:"";position:absolute;top:3px;right:3px;width:5px;height:5px;
  border-radius:50%;background:var(--flag)}
.navrow{display:flex;justify-content:space-between;gap:10px;margin-top:18px;flex-wrap:wrap}
.flagbtn.on{background:var(--flag-wash);border-color:var(--flag);color:var(--flag)}
.verdict{font-size:.86rem;font-weight:700;letter-spacing:.01em;margin-bottom:6px}
.verdict.good{color:var(--good)}
.verdict.bad{color:var(--bad)}

/* ---------- results ---------- */
.score-hero{text-align:center;padding:40px 20px}
.score-hero h2{border-bottom:none;padding-bottom:0}
.score-hero .big{font-family:var(--display);font-size:3.8rem;font-weight:700;letter-spacing:-.04em;
  line-height:1;color:var(--ink);font-variant-numeric:tabular-nums}
.score-hero .big.good{color:var(--good)}
.score-hero .big.bad{color:var(--bad)}
/* Mono caps suits a short data line and shouts on a sentence. Scope it to the
   two lines that ARE data; app.js also puts .muted on prose here. */
.score-hero #resMeta,.score-hero #resCount{font-size:.86rem;color:var(--muted)}
.score-hero #resPass{margin-top:16px;display:flex;flex-wrap:wrap;gap:10px 14px;
  align-items:center;justify-content:center}
.score-hero #resPass .muted{font-family:var(--sans);font-size:.87rem;text-transform:none;letter-spacing:0}
.rev{border:1px solid var(--rule);border-radius:var(--r);padding:18px;margin-bottom:14px;
  background:var(--surface);box-shadow:none}
.rev .ans{padding:10px 13px;border-radius:var(--r-sm);margin:6px 0;font-size:.92rem;display:flex;gap:11px;
  border:1px solid transparent}
.rev .ans .letter{font-family:var(--mono);font-weight:500;min-width:1.3em}
.rev .ans.correct{background:var(--good-wash);border-color:var(--good-rule);color:var(--ink)}
.rev .ans.wrong{background:var(--bad-wash);border-color:var(--bad-rule);color:var(--ink)}
.rev .ans.plain{border-color:var(--rule-soft)}
.rationale{background:var(--sunken);border-left:2px solid var(--accent);border-radius:0 var(--r-sm) var(--r-sm) 0;
  padding:13px 15px;font-size:.9rem;margin-top:11px;color:var(--body)}
.rationale:has(.verdict.good){border-left-color:var(--good)}
.rationale:has(.verdict.bad){border-left-color:var(--bad)}
.src{display:block;margin-top:9px;font-family:var(--mono);font-size:.72rem;color:var(--brass-ink);letter-spacing:.02em}
.tabs{display:flex;gap:0;margin-bottom:16px;flex-wrap:wrap;border:1px solid var(--rule);
  border-radius:var(--r-sm);overflow:hidden;width:fit-content}
.tabs button{border:none;border-radius:0;font-size:.84rem;font-weight:500;
  padding:10px 18px;color:var(--muted);background:var(--surface)}
.tabs button:hover{background:var(--sunken);color:var(--ink)}
.tabs button.on{background:var(--accent);color:var(--on-accent);font-weight:600}

/* ---------- scope pickers ---------- */
.scopechips,.scopechiprow{display:flex;flex-wrap:wrap;gap:7px;margin-top:5px}
.scopechip{font-size:.82rem;font-weight:500;padding:6px 14px;
  border:1px solid var(--rule);border-radius:999px;background:var(--surface);color:var(--muted);
  cursor:pointer;transition:.13s}
.scopechip:hover{border-color:var(--accent);color:var(--ink)}
.scopechip.on{border-color:var(--accent);color:var(--accent-ink);background:var(--accent-wash);font-weight:600}
.scopebar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;flex-basis:100%;margin-top:5px}
.scopesummary{font-size:.82rem;font-weight:500;
  color:var(--accent-ink);background:var(--accent-wash);border:1px solid var(--rule);border-radius:999px;padding:5px 13px}
.scopelink{font-family:var(--sans);font-size:.84rem;font-weight:500;color:var(--accent-ink);
  background:none;border:none;cursor:pointer;padding:4px}
.scopelink:hover{color:var(--accent);text-decoration:underline;background:none;border-color:transparent}
.scopepanel{flex-basis:100%;margin-top:10px;border:1px solid var(--rule);border-radius:var(--r);
  padding:14px 16px;background:var(--sunken)}
.scopegrouphead{display:flex;align-items:center;gap:9px;font-size:.78rem;
  font-weight:600;color:var(--muted);margin:14px 0 7px}
.scopegrouphead:first-of-type{margin-top:2px}
.scopegroupn{font-size:.72rem;font-weight:600;color:var(--accent-ink);
  background:var(--accent-wash);border-radius:999px;padding:1px 9px}
.scopebreakdown{margin-top:8px;font-size:.82rem;color:var(--muted)}
.scopebreakdown b{color:var(--accent-ink);font-weight:600}
/* This badge is how a candidate tells an official past-exam question from an
   authored one, which on Manifest is most of the reason to buy. It sits INSIDE
   the rationale block, whose background is already a pale wash (green when
   right, pink when wrong) - so a pale wash with no border vanished into it in
   light mode: contrast measured fine, because the TEXT was legible; the chip
   simply had no edges. It keeps its rule in both variants now. The weight does
   the rest of the work: the official badge is the one that reads as a stamp. */
.provbadge{display:inline-block;margin-top:10px;padding:3px 11px;border-radius:999px;
  font-size:.76rem;background:var(--surface);color:var(--muted);border:1px solid var(--rule)}
.provbadge.off{background:var(--accent-wash);color:var(--accent-ink);
  border:1px solid var(--accent-ink);font-weight:600}

/* ---------- SEO blocks: present, but clearly secondary to the exam ---------- */
.seo-block{background:none;border:none;border-top:1px solid var(--rule);border-radius:0;
  padding:30px 0 6px;margin-bottom:0}
.seo-block h2{margin-top:0;border-bottom:none;padding-bottom:0;margin-bottom:6px;font-size:1.18rem}
.seo-block > .muted{margin-bottom:20px}
.seo-faq{border-bottom:1px solid var(--rule-soft);padding:16px 0}
.seo-faq:last-child{border-bottom:none}
.seo-faq h3{font-size:.99rem;margin-bottom:6px}
.seo-faq p{margin:0;color:var(--muted);max-width:82ch}
.seo-q{border:1px solid var(--rule);border-radius:var(--r);padding:18px 20px;margin-bottom:14px;
  background:var(--surface);box-shadow:none}
.seo-stem{font-family:var(--display);font-size:1.04rem;font-weight:600;color:var(--ink);
  letter-spacing:-.012em;margin-bottom:13px;white-space:pre-wrap}
.seo-opts{list-style-position:inside;margin:0;padding:0}
.seo-opts li{padding:10px 13px;border:1px solid var(--rule-soft);border-radius:var(--r-sm);margin:7px 0;font-size:.92rem}
.seo-opts li::marker{font-family:var(--mono);font-weight:500;color:var(--muted)}
.seo-opts li.seo-correct{background:var(--good-wash);border-color:var(--good-rule);color:var(--ink);font-weight:500}
.seo-exp{background:var(--sunken);border-left:2px solid var(--accent);border-radius:0 var(--r-sm) var(--r-sm) 0;
  padding:12px 14px;font-size:.89rem;margin-top:13px}
.seo-more{margin-top:6px}
.seo-more > summary{cursor:pointer;font-size:.88rem;font-weight:600;
  color:var(--accent-ink);padding:12px 0 4px;list-style:none}
.seo-more > summary:hover{color:var(--accent)}
.seo-more > summary::-webkit-details-marker{display:none}
.seo-more > summary::before{content:"+ "}
.seo-more[open] > summary::before{content:"\2212 "}
.seo-more > .seo-q:first-of-type{margin-top:12px}

/* ---------- footer ---------- */
footer{margin-top:44px;border-top:1px solid var(--rule);padding-top:22px;font-size:.82rem;
  color:var(--muted);text-align:left;line-height:1.6}
footer a{color:var(--muted);text-decoration:underline}
footer a:hover{color:var(--accent-ink)}

/* A page someone is SITTING AN EXAM on does not need the landing-page
   masthead: title, note and stamp cost ~200px, so a laptop showed two
   options before scrolling. :has() degrades to no-rule where unsupported,
   which is exactly the old behaviour - there is no broken state. */
body:has(#view-test:not(.hidden)) header.app{padding:12px 20px;margin:14px 0 16px}
body:has(#view-test:not(.hidden)) header.app h1{font-size:1.06rem;margin-top:0}
body:has(#view-test:not(.hidden)) header.app .sub,
body:has(#view-test:not(.hidden)) .freshness{display:none}

.hidden{display:none}

/* ---------- narrow screens ---------- */
@media (max-width:640px){
  .wrap{padding-inline:16px}
  /* As a flex row each text node became its own wrapping column and the
     stamp tore into a three-column mess. As a block it is just a sentence. */
  .freshness{display:block}
  .freshness .fdot{display:inline-block;margin-right:7px;vertical-align:.12em}
  header.app{padding-top:20px;align-items:flex-start}
  .hero{padding:30px 22px 28px;border-radius:var(--r)}
  .card{padding:20px 18px;border-radius:var(--r)}
  .seo-block{padding-inline:0}
  .stem{font-size:1.1rem}
  .score-hero .big{font-size:3.1rem}
  .credgrid,.modgrid{grid-template-columns:1fr}
  .tabs{width:100%}
  .tabs button{flex:1}
}

/* ---------- print ---------- */
@media print{
  .noprint{display:none}
  body{background:#fff;color:#000}
  .wrap{max-width:none}
  header.app{background:none;border-bottom:2px solid #000}
  header.app h1,header.app .brand{color:#000}
  .hero{background:#fff;color:#000;border:1px solid #000}
  .hero h1,.hero p,.hero .price b,.herostat b{color:#000}
  .herostat{background:#fff}
  .card,.rev,.seo-q{box-shadow:none;border:1px solid #999}
}

/* ---------- module group headings -----------------------------------------
   app.js used to write these as an inline style, which meant the stylesheet
   could only reach them with !important - a rule that wins by shouting rather
   than by being right, and one that silently stops working the day someone
   edits the inline string. app.js now emits class="modgrouphead" and the
   geometry it genuinely needs (spanning the grid) lives here, where it can be
   seen. patch_app_shared.py makes that change; this rule assumes it. */
#modGrid > h3.modgrouphead,#modGrid > h3[style]{
  grid-column:1/-1;font-family:var(--display);font-size:.82rem;font-weight:600;
  letter-spacing:0;text-transform:none;color:var(--muted);margin:22px 0 2px}
#modGrid > h3.modgrouphead:first-child,#modGrid > h3[style]:first-child{margin-top:4px}

/* ---------- brand descriptor (23 Sep 2026) ----------------------------------
   <span class="brand-sub"> after the brand name says what the site is for
   (the exam it prepares for). It is real text, so search engines and screen
   readers get it. Always its own second line, aligned under the wordmark text:
   with a mark, .brand becomes a two-column grid (mark | text) and the sub sits
   in the text column, so on a phone the mark never wraps away from the name.
   Without :has() support it falls back to a wrapped flex row. Colour is inherited and
   muted by opacity only: no new colour, so no new contrast pair to measure
   beyond white-at-.82 on the brand surface, which clears AA on every site.
   Hidden in the compact test-view header, which exists to save height. */
.brand{flex-wrap:wrap;row-gap:1px}
.brand-sub{flex:1 0 100%;min-width:0;font-family:var(--sans);font-weight:500;
  font-size:.7em;letter-spacing:0;line-height:1.3;opacity:.82;white-space:normal}
.brand:has(> .brandmark):has(> .brand-sub){display:inline-grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:9px;row-gap:1px;align-items:center}
.brand:has(> .brandmark) > .brand-sub{grid-column:2}
body:has(#view-test:not(.hidden)) header.app .brand-sub{display:none}
@media print{.brand-sub{opacity:1}}
