:root{
  /* ---- surface ---- */
  --ink:#1a171f;            /* page floor, one step darker so raised bands read */
  --ink-2:#221e29;          /* raised band */
  --ink-3:#292430;          /* card on a raised band */
  --ink-deep:#141118;       /* recessed band */
  --gold:#ffce85;
  --gold-soft:#f6dcb6;
  --gold-dim:#c99a58;
  --navy:#163c68;
  --blue:#0050bd;
  --paper:#fdfcfb;
  --hair:rgba(255,255,255,.08);          /* hairline rules, decorative */
  --line:rgba(255,255,255,.11);
  --line-strong:rgba(255,255,255,.26);

  /* ---- text. Every value below is >= 4.5:1 on --ink; --muted-2 was 3.69:1. ---- */
  --text:#f7f2ec;
  --muted:rgba(246,240,232,.72);          /* 8.7:1  */
  --muted-2:rgba(246,240,232,.56);        /* 5.3:1  */

  /* ---- spacing: 4-unit base, named by role not by value ---- */
  --s-1:4px;   --s-2:8px;   --s-3:12px;  --s-4:16px;
  --s-5:20px;  --s-6:24px;  --s-8:32px;  --s-10:40px;
  --s-12:48px; --s-16:64px; --s-20:80px; --s-24:96px;
  --gutter:clamp(22px,5vw,64px);
  --measure:68ch;                          /* body prose */
  --measure-lede:52ch;                     /* section decks */

  /* Section rhythm. Deliberately two steps so sections alternate weight
     instead of every band getting the same air. */
  --band:clamp(88px,12vw,184px);
  --band-tight:clamp(60px,7vw,112px);
  /* How far the stat band rides up over the hero. The hero's bottom padding is
     derived from this, so the two can never drift apart and clip the microcopy:
     the rise was a vw clamp and the padding was a vh clamp, which meant a short
     wide window overlapped the SCHEV line. */
  --band-rise:clamp(38px,6vw,76px);
  --shell:1240px;

  /* ---- type roles. Light-on-dark: a touch more leading and tracking
         than the same face would take on white. ---- */
  --lh-display:1.03;
  --lh-heading:1.1;
  --lh-lede:1.55;
  --lh-body:1.72;
  --tr-display:-0.018em;
  --tr-heading:-0.014em;
  --tr-body:0.004em;
  --tr-meta:0.13em;

  --radius:18px;
  --ease:cubic-bezier(.22,1,.36,1);
  --dur:280ms;
  --serif:"Libre Caslon Display","Hoefler Text","Iowan Old Style",Georgia,serif;
}
@media (prefers-reduced-motion: reduce){ :root{ --dur:100ms; } }

/* ---- browser surfaces: the parts we did not draw still belong to the design ---- */
::selection{background:rgba(255,206,133,.26);color:#fff}
:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:4px}
html{scrollbar-color:rgba(255,206,133,.35) transparent;scrollbar-width:thin}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(255,206,133,.24);border-radius:99px;border:3px solid var(--ink)}
::-webkit-scrollbar-thumb:hover{background:rgba(255,206,133,.42)}
a{text-underline-offset:0.22em;text-decoration-thickness:from-font}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  /* Body face. Poppins is a geometric display sans: even circular bowls, a
     short x-height and tight apertures, which is handsome in a headline and
     tiring over a paragraph. Source Sans 3 is drawn for reading at text sizes,
     with a taller x-height and open apertures, and it is widely used without
     being one of the faces the detector flags as overused. Headlines stay
     Libre Caslon Display. */
  font-family:"Source Sans 3","Helvetica Neue",Helvetica,Arial,sans-serif;
  background:var(--ink); color:#fff;
  min-height:100dvh;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
body::before{ /* warm light source top-left, cool fall-off bottom-right */
  content:""; position:fixed; inset:0; pointer-events:none; z-index:0;
  background:
    radial-gradient(110vw 85vh at 6% -14%, rgba(255,206,133,.15), transparent 58%),
    radial-gradient(75vw 55vh at 112% 108%, rgba(22,60,104,.34), transparent 62%);
}
body::after{ /* film grain, keeps the flat dark from reading synthetic */
  content:""; position:fixed; inset:0; pointer-events:none; z-index:1; opacity:.5;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.05'/></svg>");
}
button{font:inherit;color:inherit}
a{color:inherit}

/* ---------- chrome ---------- */
#bar{position:fixed;top:0;left:0;right:0;height:3px;background:rgba(255,255,255,.08);z-index:60}
#bar>i{display:block;height:100%;width:100%;background:var(--gold);
  transform:scaleX(0);transform-origin:left center;transition:transform 420ms var(--ease)}

header.top{
  position:relative;z-index:20;
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  width:min(1220px,100%);margin:0 auto;
  /* enough clearance that the step kicker is not touching the mark, and no
     more: the shell below it starts at the top now, so this padding is the
     whole gap between the header and the step */
  padding:calc(18px + env(safe-area-inset-top)) clamp(20px,4vw,32px) clamp(10px,1.4vh,18px);
}
header.top img{height:36px;width:auto;display:block}
.callus{
  display:inline-flex;align-items:center;gap:9px;
  font-size:.82rem;letter-spacing:.01em;color:var(--muted);
  text-decoration:none;border:1px solid var(--line);border-radius:999px;
  padding:8px 14px;white-space:nowrap;transition:border-color 160ms,color 160ms;
}
.callus:hover{border-color:var(--gold);color:#fff}
.callus b{color:#fff;font-weight:500}
.callus .dot{width:6px;height:6px;border-radius:50%;background:var(--gold);flex:none}
@media (max-width:560px){ .callus span.lbl{display:none} }

/* ---------- shell ---------- */
.shell{
  position:relative;z-index:10;
  min-height:calc(100dvh - 74px);
  width:min(1220px,100%);margin:0 auto;
  display:grid;grid-template-columns:1fr;
  gap:clamp(28px,4vw,64px);
  padding:clamp(20px,3vw,40px) clamp(20px,4vw,32px) calc(32px + env(safe-area-inset-bottom));
  align-items:center;
}
@media (min-width:1000px){
  /* The text column is sized to the stage, not to `1fr`.
     As `1fr` it swallowed every spare pixel in the shell, but `.stage` inside it
     is capped at 600px and `justify-self:start`, so all of that slack piled up
     on ONE side: measured at 1920 the shell was perfectly centred (350px of gap
     each side) while the visible ink was 138px off centre, because the text
     began 138px inside the shell's content box and the reel ended flush with
     it. A centred container is not centred content.
     Sizing the track to the stage and centring the pair moves that slack to the
     outside, where it splits evenly. Holds at 1920 and 2560 alike. */
  .shell{
    grid-template-columns:minmax(0,600px) minmax(360px,420px);
    justify-content:center;
    align-items:center;
  }
}

.stage{max-width:600px;width:100%;justify-self:start}
.screen{will-change:transform,opacity}
.screen.out-fwd{animation:outFwd var(--dur) var(--ease) forwards}
.screen.in-fwd{animation:inFwd var(--dur) var(--ease)}
.screen.out-back{animation:outBack var(--dur) var(--ease) forwards}
.screen.in-back{animation:inBack var(--dur) var(--ease)}
@keyframes inFwd{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:none}}
@keyframes outFwd{from{opacity:1;transform:none}to{opacity:0;transform:translateY(-24px)}}
@keyframes inBack{from{opacity:0;transform:translateY(-24px)}to{opacity:1;transform:none}}
@keyframes outBack{from{opacity:1;transform:none}to{opacity:0;transform:translateY(24px)}}
@media (prefers-reduced-motion: reduce){
  @keyframes inFwd{from{opacity:0}to{opacity:1}}
  @keyframes outFwd{from{opacity:1}to{opacity:0}}
  @keyframes inBack{from{opacity:0}to{opacity:1}}
  @keyframes outBack{from{opacity:1}to{opacity:0}}
}

/* ---------- typography ---------- */
.kicker{
  display:flex;align-items:center;gap:11px;
  font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--gold-dim);font-weight:500;margin:0 0 20px;
}
.kicker .rule{height:1px;flex:1;max-width:64px;background:linear-gradient(90deg,var(--gold-dim),transparent)}
h1{
  font-family:var(--serif);
  font-size:clamp(2rem,4.4vw,3.3rem);line-height:1.04;letter-spacing:-.02em;
  font-weight:400;margin:0 0 18px;text-wrap:balance;
  color:#fbf7f2;
}
h1 em{font-style:normal;color:var(--gold)}
/* Leading tightened from 1.62 with the face change. Source Sans 3 has a taller
   x-height than Poppins, so the same numeric line-height reads looser: the
   glyphs fill more of the box and the gaps between lines grow. 1.44 sets the
   same optical rhythm the old face had at 1.62. */
.help{font-size:clamp(.95rem,1.4vw,1.04rem);line-height:1.44;color:var(--muted);margin:0 0 28px;max-width:44ch}
.help strong{color:#fff;font-weight:500}

/* ---------- options ---------- */
.opts{display:flex;flex-direction:column;gap:9px;margin:0 0 24px}
.opt{
  position:relative;overflow:hidden;
  display:flex;align-items:center;gap:15px;width:100%;text-align:left;
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.028));
  border:1px solid var(--line);
  border-radius:13px;padding:15px 18px;cursor:pointer;
  transition:border-color 180ms,background 180ms,transform 180ms var(--ease);
  min-height:58px;
}
/* Hover is a warm light sweeping in from the left edge, not a gold bar bolted
   to the side. The bar read as a selected state on an option that was only
   being pointed at, and it collided with the .hot state's own treatment. */
.opt::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(90deg,rgba(255,206,133,.14),rgba(255,206,133,.03) 42%,transparent 72%);
  opacity:0;transform:translateX(-8px);
  transition:opacity 240ms var(--ease),transform 340ms var(--ease);
}
.opt:hover{border-color:var(--line-strong);background:linear-gradient(180deg,rgba(255,255,255,.085),rgba(255,255,255,.04))}
.opt:hover::before{opacity:1;transform:none}
@media (prefers-reduced-motion: reduce){ .opt::before{transition:opacity 120ms linear;transform:none} }
.opt:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.opt .k{
  flex:none;width:27px;height:27px;border-radius:8px;border:1px solid var(--line);
  display:grid;place-items:center;font-size:.75rem;color:var(--muted-2);font-weight:500;
  transition:all 180ms;font-variant-numeric:tabular-nums;
}
.opt:hover .k{border-color:rgba(255,206,133,.55);color:var(--gold)}
.opt .t{display:flex;flex-direction:column;gap:3px;min-width:0}
.opt .t b{font-weight:400;font-size:1.03rem;letter-spacing:-.005em;color:#f7f2ec}
.opt .t small{font-size:.79rem;color:rgba(246,240,232,.56);font-weight:300;letter-spacing:.01em}
.opt.hot{background:var(--gold);border-color:var(--gold);color:var(--ink);transform:translateX(4px)}
.opt.hot::before{opacity:0}
.opt.hot .k{border-color:rgba(29,26,36,.32);color:var(--ink)}
.opt.hot .t b{color:var(--ink);font-weight:500}
.opt.hot .t small{color:rgba(29,26,36,.72)}

/* campus cards */
.opt.campus{align-items:flex-start;padding:17px}
.opt.campus .pin{flex:none;width:30px;height:30px;border-radius:50%;background:rgba(255,206,133,.14);display:grid;place-items:center}
.opt.campus .pin svg{width:15px;height:15px;fill:var(--gold)}
.opt.campus.hot .pin{background:rgba(37,33,44,.14)}
.opt.campus.hot .pin svg{fill:var(--ink)}
.opt.campus .t b{font-size:1.12rem}
.opt.campus .t small{font-size:.82rem;line-height:1.45;color:var(--muted)}
.opt.campus.hot .t small{color:rgba(37,33,44,.72)}

/* ---------- inputs ---------- */
.field{margin:0 0 24px}
.field.stacked{margin:0 0 18px}
.flabel{display:block;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted-2);margin:0 0 6px}
.field.stacked input{font-size:1.02rem;padding:6px 2px 10px}
.field input[type="date"]{color-scheme:dark;max-width:260px}
.field input,.field textarea{
  width:100%;background:transparent;border:0;border-bottom:2px solid var(--line-strong);
  color:#fff;font-family:inherit;font-size:clamp(1.15rem,2.6vw,1.5rem);font-weight:300;
  padding:8px 2px 12px;caret-color:var(--gold);border-radius:0;
}
.field textarea{font-size:1rem;line-height:1.6;resize:vertical;min-height:76px;border:1px solid var(--line);border-radius:12px;padding:12px 14px}
.field input:focus,.field textarea:focus{outline:none;border-bottom-color:var(--gold)}
.field textarea:focus{border-color:var(--gold)}
.field input::placeholder,.field textarea::placeholder{color:rgba(255,255,255,.26)}
.err{color:#ffb4a2;font-size:.85rem;margin-top:10px;min-height:1em}

/* ---------- buttons ---------- */
.row{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
/* The gold key. It is a struck piece of metal, not a coloured rectangle: a
   three-stop gradient with the light falling from above, a white rim picked out
   on the top edge, a dark seat on the bottom edge, and two shadows doing the
   lifting (a tight contact shadow plus a wide ambient one). Both shadows are
   neutral black. A gold-coloured shadow would bloom, which reads as slop
   on sight, see TRAP 10. */
/* The question CTA is one line. 2026-08-25: "Have a Question?" broke after
   "Have a" inside the gold button, because the label is a bare text node in an
   inline-flex row and wrapped like any other text. Scoped to the ask control
   (askWire sets aria-haspopup="dialog") rather than to every .btn, so a genuinely
   long label elsewhere can still wrap instead of overflowing its box. */
.btn[aria-haspopup="dialog"]{white-space:nowrap}

.btn{
  position:relative;isolation:isolate;
  display:inline-flex;align-items:center;gap:12px;
  background:linear-gradient(177deg,#ffe0b4 0%,var(--gold) 46%,#f2bd6d 100%);
  color:var(--ink);
  border:0;border-radius:13px;padding:14px 28px;font-weight:600;font-size:.98rem;
  cursor:pointer;letter-spacing:-.008em;
  min-height:50px;text-decoration:none;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.62),        /* top rim, the struck edge */
    inset 0 -1px 0 rgba(120,72,16,.28),          /* seat, stops it floating */
    0 1px 2px rgba(0,0,0,.34),                   /* contact */
    0 10px 24px -14px rgba(0,0,0,.78);           /* ambient */
  transition:transform 180ms var(--ease), box-shadow 220ms var(--ease);
}
/* The sheen is a separate layer so hover animates opacity only, never the
   gradient or a background-position. */
.btn::before{
  content:"";position:absolute;inset:0;border-radius:inherit;z-index:-1;
  background:linear-gradient(177deg,rgba(255,255,255,.55),rgba(255,255,255,0) 58%);
  opacity:0;transition:opacity 220ms var(--ease);
}
.btn:hover{
  transform:translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.7),
    inset 0 -1px 0 rgba(120,72,16,.3),
    0 2px 4px rgba(0,0,0,.32),
    0 18px 34px -16px rgba(0,0,0,.82);
}
.btn:hover::before{opacity:1}
/* Pressed. The key seats back down and the ambient shadow collapses, so the
   moment of commitment has a physical answer. */
.btn:active{
  transform:translateY(0) scale(.985);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.4),
    inset 0 2px 5px rgba(120,72,16,.34),
    0 1px 2px rgba(0,0,0,.3);
}
/* The global gold focus ring vanishes on a gold fill. Buttons get a dark inner
   ring with the gold ring outside it, which is visible on both variants. */
.btn:focus-visible{
  outline:2px solid var(--gold);outline-offset:4px;
  box-shadow:
    inset 0 0 0 2px rgba(26,23,31,.85),
    0 1px 2px rgba(0,0,0,.34),
    0 10px 24px -14px rgba(0,0,0,.78);
}
/* The arrow is a masked SVG on ::after rather than markup, so one rule gives
   every primary CTA on all three pages the same icon with no per-page edit and
   no unicode glyph. currentColor means it inherits the ink automatically. It
   travels on hover: transform only, and it is the movement that makes the
   button feel like it is going somewhere. */
.btn::after{
  content:"";width:17px;height:17px;flex:none;margin-left:-2px;
  background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h15'/%3E%3Cpath d='M13 6l6 6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h15'/%3E%3Cpath d='M13 6l6 6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
  transition:transform 260ms var(--ease);
}
.btn:hover::after{transform:translateX(4px)}
.btn:active::after{transform:translateX(1px)}
/* Press ripple. It starts where the finger or cursor actually landed, which is
   what separates a real press from a state change. One span, animating transform
   and opacity only, removed when it finishes. It sits at z-index -1 like the
   sheen, so it passes above the ground and under the label. */
.btn .rip{
  position:absolute;z-index:-1;pointer-events:none;
  width:var(--rs,220px);height:var(--rs,220px);
  left:var(--rx,50%);top:var(--ry,50%);border-radius:50%;
  background:radial-gradient(circle,rgba(255,255,255,.5),rgba(255,255,255,0) 68%);
  transform:translate(-50%,-50%) scale(0);
  animation:rip 560ms var(--ease) forwards;
}
.btn.ghosted .rip{background:radial-gradient(circle,rgba(255,255,255,.22),rgba(255,255,255,0) 68%)}
@keyframes rip{
  from{transform:translate(-50%,-50%) scale(0);opacity:.9}
  to  {transform:translate(-50%,-50%) scale(1);opacity:0}
}
/* An arrow means "onward". A phone link, a back step and a submit do not go
   onward, so they opt out. */
.btn.ghosted::after,.btn[href^="tel:"]::after,.btn.noarrow::after{content:none}
@media (prefers-reduced-motion: reduce){
  .btn,.btn::before,.btn::after{transition:none}
  .btn:hover{transform:none}
  .btn:hover::after{transform:none}
  .btn .rip{display:none}
}

/* Sticky CTA. Appears only while no other call to action is on screen, so it is
   a safety net rather than a permanent overlay competing with the real buttons.
   It is supplementary, never the only route to the funnel, and it ships hidden
   because a page with JS off still has a CTA in the hero and in the finale. */
.stickycta{
  position:fixed;z-index:60;left:0;right:0;bottom:0;
  padding:12px var(--gutter) calc(12px + env(safe-area-inset-bottom));
  display:flex;align-items:center;gap:var(--s-4);justify-content:center;
  background:linear-gradient(180deg,rgba(20,17,24,0),rgba(20,17,24,.92) 38%,rgba(20,17,24,.98));
  transform:translateY(110%);transition:transform 420ms var(--ease);
  pointer-events:none;
}
.stickycta.on{transform:translateY(0);pointer-events:auto}
/* Mobile only: the hero and finale CTAs are always in view on a wider
   window, so a second, floating one just duplicates them. */
@media (min-width:701px){ .stickycta{display:none} }
.stickycta .line{
  font-size:.86rem;color:var(--muted);margin:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.stickycta .btn{flex:none}
@media (max-width:700px){
  .stickycta{padding-inline:14px}
  .stickycta .line{display:none}
  .stickycta .btn{width:100%;justify-content:center}
}
@media (prefers-reduced-motion: reduce){
  .stickycta{transition:none}
}
.btn:disabled{opacity:.55;cursor:default;transform:none}
.btn:disabled::before{opacity:0}
/* "2 minutes" is metadata about the action, not part of the sentence. A hairline
   separates it so it stops being read as the tail of the label. */
.btn .ent{
  font-size:.72rem;font-weight:500;letter-spacing:.02em;
  color:rgba(26,23,31,.72);   /* .62 computed 4.35:1 on gold, under AA at this size */
  padding-left:12px;border-left:1px solid rgba(26,23,31,.22);
}
/* Hero CTA hover (#60, Siavash 2026-10-04): the gold key turns black and every
   piece inside it (label, hairline, "2 minutes", arrow) turns white. The sheen
   is held off so it cannot grey the black. */
.btn[data-cta="hero"]:hover{background:#000;color:#fff}
.btn[data-cta="hero"]:hover::before{opacity:0}
.btn[data-cta="hero"]:hover .ent{color:#fff;border-left-color:#fff}
.ghost{background:none;border:0;color:var(--muted);font-size:.86rem;cursor:pointer;padding:8px 0;text-align:left;text-decoration:underline;text-underline-offset:3px;text-decoration-color:rgba(255,255,255,.2)}
.ghost:hover{color:#fff}
.hintkeys{font-size:.76rem;color:var(--muted-2);margin-top:16px}
.hintkeys kbd{font-family:inherit;border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:.72rem}
@media (pointer:coarse){ .hintkeys{display:none} }

/* ---------- proof rail ---------- */
.rail{justify-self:center;width:100%;max-width:460px}
@media (max-width:999px){ .rail{max-width:620px;justify-self:start} .rail.hide-sm{display:none} }
.card{
  background:linear-gradient(180deg,rgba(255,255,255,.062),rgba(255,255,255,.028));
  border:1px solid var(--line);border-radius:var(--radius);
  padding:24px;
}
.shot{
  position:relative;border-radius:var(--radius);overflow:hidden;background:var(--ink-2);
  aspect-ratio:1/1;border:1px solid var(--line);
}
@media (max-width:999px){ .shot{aspect-ratio:16/9} }
.shot img{width:100%;height:100%;object-fit:cover;display:block;filter:saturate(.95) contrast(1.03)}
.shot::after{content:"";position:absolute;inset:0;box-shadow:inset 0 0 0 1px rgba(255,255,255,.06);border-radius:inherit;pointer-events:none}
.shot figcaption{
  position:absolute;left:14px;right:14px;bottom:14px;padding:11px 14px;
  background:rgba(21,18,26,.72);backdrop-filter:blur(14px) saturate(1.2);
  border:1px solid rgba(255,255,255,.1);border-radius:11px;
  font-size:.78rem;line-height:1.45;color:rgba(255,255,255,.9);letter-spacing:.01em;
}
.rev{display:flex;flex-direction:column;gap:14px;position:relative}
.rev::before{
  content:"\201C";position:absolute;top:-16px;right:2px;font-family:var(--serif);
  font-size:4.4rem;line-height:1;color:rgba(255,206,133,.16);pointer-events:none;
}
.rev .stars{color:var(--gold);letter-spacing:.16em;font-size:.85rem}
.rev p{margin:0;font-size:.98rem;line-height:1.66;letter-spacing:var(--tr-body);color:#f0e9df;font-weight:300}
.rev .who{display:flex;align-items:center;gap:11px;font-size:.81rem;color:var(--muted-2)}
.rev .who .av{width:32px;height:32px;border-radius:50%;background:rgba(255,206,133,.14);border:1px solid rgba(255,206,133,.28);color:var(--gold);display:grid;place-items:center;font-size:.78rem;font-weight:600;flex:none}
.rev .who b{color:#f4efe8;font-weight:500}
.src{display:inline-flex;align-items:center;gap:7px;font-size:.76rem;color:var(--muted-2);text-decoration:none;margin-top:2px}
.src:hover{color:var(--gold)}
.gmark{width:13px;height:13px;flex:none}

/* Debossed link. Reversed bevel: the light rim goes on the bottom edge and the
   shadow on the top, which is what makes a surface read as pressed into the
   page rather than sitting on it. The ground is darker than the section it
   sits in, so the recess has somewhere to go. */
.allrev{margin-top:var(--s-8);text-align:center}
.src.inset{
  padding:11px 20px;border-radius:999px;font-size:.82rem;
  background:rgba(0,0,0,.28);
  box-shadow:
    inset 0 2px 5px rgba(0,0,0,.6),
    inset 0 1px 0 rgba(0,0,0,.5),
    inset 0 -1px 0 rgba(255,255,255,.07),
    0 1px 0 rgba(255,255,255,.045);
  transition:color 240ms var(--ease),box-shadow 240ms var(--ease);
}
.src.inset:hover{
  box-shadow:
    inset 0 2px 6px rgba(0,0,0,.66),
    inset 0 1px 0 rgba(0,0,0,.55),
    inset 0 -1px 0 rgba(255,206,133,.16),
    0 1px 0 rgba(255,255,255,.05);
}

/* Hairline as an INSET SHADOW, not a border. 2026-08-25: a drawn border
   paints its own corner, and where `.hero .stats` zeroes two corners and
   keeps two, the square half of that border shows outside the rounded
   clip as a notch. An inset shadow follows border-radius exactly and
   cannot do that. Also what the borderless house rule asks for. */
.stats{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);
  border:0;box-shadow:inset 0 0 0 1px var(--line);
  border-radius:var(--radius);overflow:hidden}
.stats div{background:var(--ink-2);padding:20px 18px}
.stats b{display:block;font-family:var(--serif);font-size:1.9rem;font-weight:400;letter-spacing:-.02em;color:var(--gold);line-height:1}
.stats span{display:block;font-size:.74rem;color:var(--muted-2);margin-top:8px;line-height:1.4;letter-spacing:.01em}

.list{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:9px}
.list li{display:flex;gap:11px;font-size:.9rem;color:rgba(255,255,255,.84);line-height:1.5;font-weight:300}
.list li::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:var(--gold);margin-top:8px}

/* The founder quote. It used to be `.card` plus a gold left rule: a 1px grey
   rectangle with a stripe down one side, which is the cheapest possible way to
   say "card" and fights the hairline language the rest of this site uses. It
   also read as a UI panel rather than as somebody speaking.

   Three changes. The drawn rectangle is gone, replaced by a raised ground with
   a gold hairline along its top edge, the same treatment `.sec.raised` and
   `.ohcard` already use. The quote runs at the display size the landing page's
   own founder block uses, with the mark set as a real hanging quote rather than
   an italic paragraph, since Libre Caslon has no italic and faux-italic is what
   the browser was drawing. And the attribution sits under a short accent rule
   so it reads as an ascription rather than as two more lines of the quote. */
.quote{
  position:relative;border:0;border-radius:var(--radius);
  padding:var(--s-8) var(--s-6) var(--s-6);   /* --s-7 does not exist; see the scale at :root */
  background:
    radial-gradient(120% 100% at 0% 0%,rgba(255,206,133,.09),rgba(255,206,133,0) 60%),
    linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.015));
  box-shadow:
    inset 0 1px 0 rgba(255,206,133,.3),
    inset 0 0 0 1px rgba(255,255,255,.045),
    0 20px 46px -34px rgba(0,0,0,.9);
}
/* No decorative opening mark here. The quote string is built with real curly
   quotes around it in the page script, so a CSS one renders a second mark on
   top of the first: it read as a smudge behind the word "At". If the marks are
   ever dropped from the string, add it back, not before. */
.quote p{
  margin:0 0 var(--s-5);font-family:var(--serif);font-weight:400;font-style:normal;
  font-size:clamp(1.14rem,1.5vw,1.34rem);line-height:1.46;
  letter-spacing:var(--tr-heading);color:#fbf7f2;text-wrap:pretty;
}
.quote cite{
  display:block;font-style:normal;font-size:.82rem;color:var(--muted-2);
  padding-top:var(--s-4);position:relative;
}
/* Short accent rule over the attribution. scaleX from the left if anything ever
   animates it, and it is drawn at full length by default. */
.quote cite::before{
  content:"";position:absolute;left:0;top:0;width:34px;height:1px;
  background:linear-gradient(90deg,var(--gold),rgba(255,206,133,0));
}
.quote cite b{display:block;color:var(--text);font-weight:600;font-style:normal;margin-bottom:2px;font-size:.95rem}

.trust{display:flex;align-items:center;gap:10px;font-size:.84rem;color:var(--muted);margin-top:-6px;margin-bottom:22px;flex-wrap:wrap}
.trust .stars{color:var(--gold);letter-spacing:.1em}
.trust a{color:var(--muted);text-decoration:underline;text-underline-offset:3px}
.trust a:hover{color:var(--gold)}

/* ---------- comparison ---------- */
.cmp{display:flex;flex-direction:column;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
.cmp div{background:#292531;padding:15px 17px;display:flex;justify-content:space-between;align-items:baseline;gap:14px}
.cmp b{font-weight:500;font-size:.98rem}
.cmp span{font-size:.8rem;color:var(--muted);text-align:right;flex:none}

/* ---------- chips (tour) ---------- */
.chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:9px;margin:0 0 22px}
.chips.times{grid-template-columns:repeat(auto-fit,minmax(104px,1fr))}
.chip{
  border:1px solid var(--line);background:rgba(255,255,255,.035);border-radius:11px;
  padding:11px 14px;cursor:pointer;font-size:.92rem;transition:all 150ms;min-height:46px;
  display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:1px;
}
.chip small{font-size:.7rem;color:var(--muted-2)}
.chip:hover{border-color:var(--line-strong)}
.chip[aria-pressed="true"]{background:var(--gold);border-color:var(--gold);color:var(--ink)}
.chip[aria-pressed="true"] small{color:rgba(37,33,44,.68)}
.sublabel{font-size:.76rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-2);margin:0 0 10px}

/* ---------- summary ---------- */
.plan{border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;margin:0 0 22px;background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.02))}
.plan .hd{background:rgba(255,206,133,.09);padding:13px 20px;font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);border-bottom:1px solid var(--line)}
.plan dl{margin:0;padding:4px 20px 14px}
.plan .r{display:flex;justify-content:space-between;gap:18px;padding:12px 0;border-bottom:1px solid rgba(255,255,255,.06)}
.plan .r:last-child{border-bottom:0}
.plan dt{font-size:.82rem;color:var(--muted-2);flex:none;letter-spacing:.01em}
.plan dd{margin:0;font-size:.95rem;text-align:right;font-weight:500;letter-spacing:-.005em;color:#f7f2ec}
.plan .note{padding:14px 20px;background:rgba(22,60,104,.3);border-top:1px solid var(--line);font-size:.84rem;line-height:1.55;color:rgba(246,240,232,.84)}
.consent{font-size:.78rem;color:var(--muted-2);line-height:1.5;margin:0 0 16px;max-width:52ch}
/* A2P opt-in. The fine print sits directly under its checkbox, never behind a
   link or a disclosure toggle: a carrier reviewer has to be able to read the
   frequency, the rates line and STOP without clicking anything. */
/* Footer policy links. Present on every page, funnel included. */
.flegal{display:flex;flex-wrap:wrap;gap:6px 18px;font-size:.78rem;margin:0 0 10px}
.flegal a{color:var(--muted);text-decoration:underline;text-underline-offset:2px}
.flegal a:hover{color:var(--gold)}
footer.compact .flegal{margin:0;font-size:.74rem;gap:6px var(--s-4)}
/* The A2P disclosure text itself, site-wide. */
.fsms{font-size:.74rem;line-height:1.55;color:var(--muted-2);max-width:78ch;margin:0 0 12px}
.fsms a{color:var(--muted);text-decoration:underline;text-underline-offset:2px}
.fsms a:hover{color:var(--gold)}
/* Spans both columns and keeps the left edge of everything above it. Its own
   hairline separates the legally-required disclosure from the reference line
   without a second border weight or a box. */
footer.compact .fsms{
  grid-column:1 / -1;text-align:left;max-width:none;
  margin:0;padding-top:var(--s-4);border-top:1px solid var(--hair);
  font-size:.7rem;line-height:1.6;
}
/* The compact footer's own responsive rules live in ONE block next to the base
   rule, further down this file. They used to sit here, ~1800 lines ABOVE it at
   equal specificity, which made them dead code. See TRAP 93. */

.consent-block{margin:0 0 14px}
.consent-block .check{margin-bottom:7px}
.consent-fine{font-size:.72rem;color:var(--muted-2);line-height:1.5;margin:0;max-width:52ch}
.consent-fine a{color:var(--muted);text-decoration:underline;text-underline-offset:2px}
.consent-fine a:hover{color:var(--gold)}
.check{display:flex;align-items:flex-start;gap:11px;font-size:.87rem;color:rgba(246,240,232,.82);line-height:1.5;margin:0 0 16px;cursor:pointer;max-width:48ch}
.check input{appearance:none;flex:none;width:19px;height:19px;margin:1px 0 0;border:1px solid var(--line-strong);border-radius:5px;background:rgba(255,255,255,.04);cursor:pointer;transition:all 150ms}
.check input:checked{background:var(--gold);border-color:var(--gold)}
.check input:checked::after{content:"";display:block;width:5px;height:9px;margin:2px auto 0;border:solid var(--ink);border-width:0 2px 2px 0;transform:rotate(45deg)}
.check input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ---------- done ---------- */
.next{counter-reset:n;list-style:none;margin:22px 0 0;padding:0;display:flex;flex-direction:column;gap:14px}
.next li{counter-increment:n;display:flex;gap:14px;font-size:.94rem;line-height:1.55;color:rgba(255,255,255,.86);font-weight:300}
.next li::before{
  content:counter(n);flex:none;width:26px;height:26px;border-radius:50%;
  border:1px solid var(--gold);color:var(--gold);display:grid;place-items:center;
  font-size:.76rem;font-weight:600;
}
/* ============================================================================
   Footer. Four columns of real navigation and contact detail, then a base row
   carrying the accreditation line and the treatment disclaimer.
   ========================================================================= */
footer.legal{
  position:relative;z-index:10;border-top:1px solid var(--hair);
  width:min(var(--shell),100%);margin:0 auto;
  padding:var(--band-tight) var(--gutter) calc(32px + env(safe-area-inset-bottom));
  font-size:.86rem;color:var(--muted-2);line-height:1.6;
}
footer.legal .ftop{display:grid;gap:var(--s-10)}
/* five columns now that their own site links are here; the brand column keeps
   the extra width and the rest share what is left */
@media (min-width:760px){ footer.legal .ftop{grid-template-columns:1.5fr 1fr 1fr;gap:var(--s-8)} }
@media (min-width:1080px){ footer.legal .ftop{grid-template-columns:1.5fr .9fr .9fr 1.1fr 1.1fr} }
footer.legal h4{
  font-size:.72rem;letter-spacing:var(--tr-meta);text-transform:uppercase;
  color:var(--muted-2);font-weight:500;margin:0 0 var(--s-4);
}
footer.legal .fcol{display:flex;flex-direction:column;gap:var(--s-3);align-items:flex-start}
footer.legal a{color:var(--muted);text-decoration:none;transition:color 180ms}
footer.legal a:hover{color:var(--gold)}
footer.legal .fbrand img{height:34px;width:auto;display:block;margin-bottom:var(--s-4)}
footer.legal .fbrand p{margin:0 0 var(--s-5);max-width:30ch;color:var(--muted-2);font-size:.88rem}
footer.legal .campus{display:flex;flex-direction:column;gap:2px}
footer.legal .campus b{color:var(--text);font-weight:500;font-size:.92rem}
footer.legal .campus span{color:var(--muted-2);font-size:.84rem;line-height:1.5;max-width:26ch}
footer.legal .campus .src{margin-top:4px}
footer.legal .hours{margin:0;color:var(--muted-2);font-size:.84rem;max-width:26ch}
footer.legal .fcol .btn{margin-top:var(--s-2)}
/* The footer's own link colour was landing on the gold button. `footer.legal a`
   is (0,1,2) and `.btn{color:var(--ink)}` is (0,1,0), so every button in the
   footer rendered muted cream text on a gold ground: 1.6:1, effectively
   unreadable, on both index.html and thank-you.html. It is the only .btn on
   either page that was not carrying ink, checked by walking every one of them.
   Stated at the same specificity as the rule that broke it. */
footer.legal .btn,
footer.legal a.btn:hover{color:var(--ink)}
footer.legal .fbase{
  margin-top:var(--band-tight);padding-top:var(--s-6);border-top:1px solid var(--hair);
  display:flex;flex-wrap:wrap;gap:var(--s-2) var(--s-8);justify-content:space-between;
}
footer.legal .fbase p{margin:0;max-width:64ch;font-size:.8rem;color:var(--muted-2)}
footer.legal .fbase .fine{opacity:.8}
/* The builder credit. `.fbase` is a space-between flex row, so a fourth child
   would have pulled the copyright away from its own edge; the credit is
   `width:100%` and sits on its own line under the row instead, which also keeps
   it the quietest thing in the footer rather than a badge competing with the
   school's own copyright. The link is underlined because `footer.legal a` sets
   `text-decoration:none` and an unmarked link in 0.8rem muted text is not
   readable as a link at all. */
footer.legal .fbase .fcred{width:100%;opacity:.62;font-size:.76rem}
footer.legal .fbase .fcred a{text-decoration:underline;text-underline-offset:2px}

.demoflag{margin-top:14px;font-size:.76rem;color:var(--gold-dim);border:1px dashed rgba(255,206,133,.4);border-radius:10px;padding:9px 12px;max-width:44ch}

/* ambient video wash: their school film, blurred, behind everything */
/* Scoped to the top of the page. A full-document wash flattens every section into
   the same field; anchoring it to the hero lets the bands below carry their own tone. */
#amb{position:absolute;top:0;left:0;right:0;height:min(120vh,1100px);z-index:0;overflow:hidden;
  opacity:0;transition:opacity 1.4s ease;pointer-events:none}
body:not(.page) #amb{position:fixed;inset:-8%;height:auto}
#amb.on{opacity:1}
#amb video,#amb img{width:100%;height:100%;object-fit:cover}
/* Landing: the wash sits behind a whole scrolling page, so it stays blurred.
   Funnel: one focused question at a time, so the footage stays sharp under a
   flat 80% black scrim instead. */
body.page #amb video,body.page #amb img{filter:blur(56px) saturate(1.15) brightness(.85);transform:scale(1.15)}
body:not(.page) #amb video,body:not(.page) #amb img{filter:saturate(.9) contrast(1.02);transform:scale(1.02)}
#amb::after{content:"";position:absolute;inset:0;background:rgba(0,0,0,.93)}
body.page #amb::after{background:
  linear-gradient(100deg,rgba(26,23,31,.94) 0%,rgba(26,23,31,.88) 42%,rgba(26,23,31,.8) 100%),
  linear-gradient(to bottom,transparent 55%,var(--ink) 100%)}
@media (max-width:760px){ #amb::after{background:rgba(26,23,31,.92)} }

/* The funnel wash was sharp, not blurred, so on a phone the film read as busy
   even at 92 percent black: the noise was the focus, not the opacity. Blur it
   and take the scrim to 90 percent. (It was already darker than 85, so 85 would
   have shown MORE film, which is the opposite of what was wanted.) */
@media (max-width:900px){
  body:not(.page) #amb video,body:not(.page) #amb img{
    filter:saturate(.85) contrast(1.02) blur(16px);transform:scale(1.14);
  }
  body:not(.page) #amb::after{background:rgba(14,11,17,.90)}
}

/* THE HERO PATH: train -> certify -> hired. 2026-08-25.
   Borderless per the house rule; the steps are separated by an arrow and by
   space, not by boxes. The arrows are hidden from assistive tech and drop out
   entirely when the row stacks, where a right-pointing arrow between rows
   stacked vertically would point at nothing. */
/* FIVE EXPLICIT TRACKS, not a wrapping flex row. As flex-wrap it broke after
   step two on a 1440 hero column and left the arrow that belonged between two
   and three dangling at the end of the first line, pointing at nothing, with
   "Get hired" orphaned below. A grid with a fixed track count cannot wrap, so
   the three steps are either one row or, below the breakpoint, three rows with
   the arrows removed. */
/* Rhythm, 2026-08-29: the hero column is display:grid above 1180 and normal
   flow below it, so adjoining margins ADD in one and COLLAPSE in the other.
   Every gap here is therefore carried on ONE side only, which makes the two
   layouts agree by construction instead of by luck. Measured result at both:
   lede 24 path 20 rating 32 button. It was 26 / 4 / 48, which read as the pill
   stuck to the strip and a hole above the button. */
.path{
  display:grid;grid-template-columns:1fr auto 1fr auto 1fr;
  align-items:center;gap:12px;margin:var(--s-6) 0 var(--s-5);
}
.path .pstep{display:flex;align-items:center;gap:10px;min-width:0}
.path .pmk{ /* Law 16: the glyph paints bare in gold, no tinted tile */
  width:22px;height:22px;flex:none;display:grid;place-items:center;color:var(--gold);
}
.path .pmk svg{width:20px;height:20px}
.path .ptx{min-width:0}
/* Both lines stay on ONE line. A wrapped sub made its step taller than the
   other two, and align-items:center then dropped that step's title 8px below
   the others, which is what read as sloppy. Measured at 1496: a track offers
   151px of text width and the three subs need 137 / 112 / 134. The ellipsis is
   the failure mode if copy ever grows past that: one clipped word instead of a
   row that silently falls out of alignment again. */
.path .ptx b,.path .ptx span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.path .ptx b{
  display:block;font-size:.88rem;font-weight:600;color:var(--text);
  line-height:1.25;letter-spacing:-.005em;
}
.path .ptx span{
  display:block;font-size:.74rem;color:var(--muted-2);line-height:1.35;margin-top:2px;
}
.path .parr{flex:none;color:var(--gold-dim);opacity:.5;display:grid;place-items:center}
.path .parr svg{width:15px;height:15px}
/* One row is only honest while the sub-lines still fit. Below this the three
   steps stack, and a right-pointing arrow between vertically stacked rows would
   point at nothing, so it goes. */
@media (max-width:1240px){
  .path{grid-template-columns:1fr;gap:10px}
  .path .parr{display:none}
  .path .ptx b{font-size:.85rem}
}
@media (max-width:640px){
  .path{gap:9px}
  .path .pmk{width:20px;height:20px}
  .path .pmk svg{width:18px;height:18px}
  .path .ptx b{font-size:.82rem}
  .path .ptx span{font-size:.72rem}
}

/* intro: portrait reel of their school film */
.hero{display:grid;gap:14px;grid-template-columns:1fr}
.reel{
  position:relative;border-radius:var(--radius);overflow:hidden;border:1px solid var(--line);
  width:100%;height:min(52vh,400px);background:var(--ink-2);margin:0;
}
/* Taller on a phone, and centred. 2026-08-25 on Siavash's instruction: the reel
   was 46vh capped at 300px and framed at `50% 20%`, which put the top of the
   frame on foreheads and left the room below out of shot. */
@media (max-width:999px){
  .reel{height:min(60vh,440px)}
  .reel video{object-position:50% 50%}
}
.reel video{width:100%;height:100%;object-fit:cover;display:block;object-position:50% 20%}
.reel .veil{position:absolute;inset:0;background:linear-gradient(transparent 55%,rgba(18,15,23,.86))}
.reel figcaption{
  position:absolute;left:14px;right:14px;bottom:13px;display:flex;align-items:center;gap:9px;
  font-size:.76rem;color:rgba(255,255,255,.9);letter-spacing:.02em;
}
.reel .live{width:7px;height:7px;border-radius:50%;background:var(--gold);flex:none;animation:pulse 2.6s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
@media (prefers-reduced-motion: reduce){ .reel .live{animation:none} }
/* No box. It is a reassurance under the button, not a control, and a bordered
   pill next to a real button read as a second thing to press. It also sits
   tight under the CTA rather than a full step away from it. */
.badge{
  display:inline-flex;align-items:center;gap:8px;border:0;background:none;
  border-radius:0;padding:0;font-size:.76rem;color:var(--muted-2);letter-spacing:.014em;
}
.badge svg{width:13px;height:13px;fill:var(--gold-dim);flex:none}
.row:has(> .badge){margin-top:12px !important}

.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.hp{position:absolute;left:-9999px;opacity:0;height:0;width:0}
.spin{width:15px;height:15px;border:2px solid rgba(37,33,44,.3);border-top-color:var(--ink);border-radius:50%;animation:sp .7s linear infinite}
@keyframes sp{to{transform:rotate(360deg)}}

/* ============================================================================
   PAGES: landing (index.html) and confirmation (thank-you.html)
   The funnel styles above are shared; everything below only applies to the
   scrolling pages, which opt in with <body class="page">.
   ========================================================================= */
body.page{overflow-y:auto}
.wrap{width:min(var(--shell),100%);margin:0 auto;padding:0 var(--gutter)}
section{position:relative;z-index:10}
.sec{padding:var(--band) 0}
.sec.tight{padding:var(--band-tight) 0}
.sec + .sec{border-top:1px solid var(--hair)}

/* Section head. One rule: the heading owns the section, the deck supports it.
   No label above a heading anywhere on this site. */
.shead{max-width:var(--measure-lede);margin:0 0 var(--s-10)}
.shead.wide{max-width:none}
.shead h2{margin-bottom:var(--s-4)}
.shead .lede{margin:0}
.center .shead{margin-left:auto;margin-right:auto}

h2{
  font-family:var(--serif);font-weight:400;color:#fbf7f2;
  font-size:clamp(1.85rem,3.5vw,2.9rem);line-height:var(--lh-heading);
  letter-spacing:var(--tr-heading);margin:0 0 var(--s-4);text-wrap:balance;
}
h2 em{font-style:normal;color:var(--gold)}
h3{font-size:1.05rem;font-weight:500;line-height:1.38;letter-spacing:-.012em;margin:0 0 var(--s-2);color:var(--text)}
.lede{font-size:clamp(1.04rem,1.35vw,1.18rem);line-height:var(--lh-lede);letter-spacing:-.004em;
  color:var(--muted);margin:0 0 var(--s-2);max-width:var(--measure-lede);text-wrap:pretty}
p.body{font-size:1rem;line-height:var(--lh-body);letter-spacing:var(--tr-body);color:var(--muted);
  margin:0 0 var(--s-4);max-width:var(--measure);text-wrap:pretty}
p.body strong{color:var(--text);font-weight:500}
/* metadata role. Never used as a label above a heading, only for real metadata. */
.meta{font-size:.74rem;letter-spacing:var(--tr-meta);text-transform:uppercase;color:var(--muted-2);
  font-weight:500;margin:0;font-variant-numeric:tabular-nums}
.eyebrow{
  display:inline-flex;align-items:center;gap:10px;font-size:.7rem;letter-spacing:.2em;
  text-transform:uppercase;color:var(--gold-dim);font-weight:500;margin:0 0 18px;
}
.eyebrow::before{content:"";width:26px;height:1px;background:var(--gold-dim)}
.center{text-align:center}
.center .lede{margin-left:auto;margin-right:auto}
.center .eyebrow{justify-content:center}

/* ============================================================================
   Header. Transparent over the hero, solid once the page moves. Carries an
   announcement strip, scroll-spy on the section links, and a real mobile menu,
   which the previous version simply did not have below 900px.
   ========================================================================= */
/* No ground of its own. A filled band here reads as a second header stacked on
   the floating one, and its plum sat a shade off whatever section scrolled
   under the bar. The strip is now just text on the page floor. */
.topbar{position:relative;z-index:51;background:transparent;border-bottom:0}
/* The strip used to wrap, which put "2026," alone on a second line and made the
   whole bar look broken. It now steps down instead: three facts on one line,
   then two, then two deliberate stacked lines. It never wraps mid-phrase. */
.topbar .inner{
  display:flex;align-items:center;justify-content:center;gap:var(--s-5);
  min-height:44px;padding-block:12px;flex-wrap:nowrap;
  font-size:.8rem;letter-spacing:.012em;color:var(--muted-2);
}
.topbar .inner > span,.topbar .inner > a{white-space:nowrap}
.topbar b{color:var(--gold-soft);font-weight:500}
.topbar .sep{width:1px;height:12px;background:var(--line);flex:none}
.topbar a{color:var(--muted);text-decoration:none}
.topbar a:hover{color:var(--gold)}
/* drop the campus list before the line can run out of room */
@media (max-width:859px){ .topbar .sep,.topbar .hide-sm{display:none} }
/* two separate centred lines, not a ragged wrap */
@media (max-width:619px){
  .topbar .inner{
    flex-direction:column;gap:3px;min-height:0;
    padding-block:10px;font-size:.78rem;line-height:1.35;
  }
}

.nav{
  position:sticky;top:0;z-index:50;
  background:transparent;border-bottom:1px solid transparent;
  transition:background 360ms var(--ease),border-color 360ms var(--ease),backdrop-filter 360ms;
}
/* .nav.stuck carried a full-width ground and blur here, left over from the header
   that predated the glass rewrite. It beat .nav{background:transparent} on
   specificity, so on scroll the floating pill sat on a dark band instead of
   floating. Removed, and the blur with it: a full-width blur with no tint behind
   it is a visible band on its own. The scrolled state is now carried entirely by
   .nav.stuck .inner. Its border-bottom-color went too, inert since .nav below
   sets border-bottom:0. */
.nav .inner{display:flex;align-items:center;gap:clamp(12px,3vw,34px);height:72px;
  transition:opacity 300ms var(--ease),transform 300ms var(--ease)}
.nav img{height:34px;width:auto;display:block}
/* gap:0 and 11px of side padding, not 4px and 13px. Adding the "More" trigger
   pushed the row past its column: "What you learn" wrapped to two lines from
   1360px down and to THREE at 1100, where the design has always run its
   tightest (33px of clear ground between the last item and the mark). The
   padding still carries the hover pill, so the visual gap between labels is
   unchanged; what went is the 4px on top of it. nowrap is the guard: a bar
   link that wraps is a bug, not a smaller bar. */
.nav .links{display:flex;align-items:center;gap:0;margin-left:auto;font-size:.88rem}
/* Direct children only. The submenu below puts a second set of anchors inside
   this group, and they are a menu, not bar links: the gold underline and the
   scroll-spy current state belong to the five in-page anchors alone. */
.nav .links > a{
  position:relative;color:var(--muted);text-decoration:none;padding:9px 11px;border-radius:9px;
  white-space:nowrap;
  transition:color 200ms,background 200ms;
}
.nav .links > a::after{
  content:"";position:absolute;left:11px;right:11px;bottom:4px;height:1px;background:var(--gold);
  transform:scaleX(0);transform-origin:left;transition:transform 300ms var(--ease);
}
.nav .links > a:hover{color:#fff;background:rgba(255,255,255,.05)}
.nav .links > a:hover::after,.nav .links > a[aria-current="true"]::after{transform:scaleX(1)}
.nav .links > a[aria-current="true"]{color:var(--gold)}

/* ---------------------------------------------------------------------------
   Header menu: the site's pages where the bar's row does not fit.

   Since 2026-09-28 the bar's links ARE the site's pages (CONFIG.LINKS), so
   there is no "More" any longer. What is left of the submenu is the control
   the GHL block needs below 1280px, where the row is hidden and that build has
   no burger and no sheet: a bare menu glyph, the same one every sub-page bar
   carries, opening the same pages as a panel. Hidden everywhere else,
   index.html included, which has the sheet.

   Borderless and plateless (laws 1 and 16): the glyph sits on the bar, reacts
   by colour and a cross-fade to the close mark, and takes its 44px target from
   the box, not from a fill. The panel separates by fill, blur and a drop
   shadow.
   ------------------------------------------------------------------------ */
.navsub{position:relative;display:none;align-items:center}
.navsub .trig{
  display:grid;place-items:center;width:44px;height:44px;padding:0;flex:none;
  /* A button inherits neither font nor colour. Both are stated. */
  font:inherit;color:#fff;background:none;border:0;cursor:pointer;
  transition:color 200ms var(--ease),transform 200ms var(--ease);
}
.navsub .trig svg{grid-area:1 / 1;width:20px;height:20px;
  transition:opacity 200ms var(--ease),transform 280ms var(--ease)}
.navsub .trig .i-shut{opacity:0;transform:rotate(-45deg)}
.navsub.open .trig .i-open{opacity:0;transform:rotate(45deg)}
.navsub.open .trig .i-shut{opacity:1;transform:none}
.navsub .trig:hover,.navsub.open .trig{color:var(--gold)}
.navsub .trig:active{transform:scale(.92)}
.navsub .panel{
  /* Hung from the trigger's RIGHT edge, not centred on it. Centred, the panel
     ran under the mark, which sits on the bar's centre line and is the one
     thing on this page that must never have a card sliding beneath it. */
  position:absolute;top:calc(100% + 14px);right:0;left:auto;
  z-index:3;min-width:252px;max-width:min(92vw,300px);
  display:grid;gap:2px;padding:7px;border-radius:16px;
  /* Opaque, not the bar's own .20. A bar is glanced at and a menu is read, and
     what is behind this one on first paint is a 62px gold serif over a
     photograph. .985 was tried as glass and still read the headline through
     it: 4 levels of 255 under the panel with its own glyphs taken out, and on
     this ground that is a legible ghost (2026-09-28, 390 and 1024 in the GHL
     block). The sheen gradient stays; the blur went with the alpha, since a
     backdrop filter under an opaque fill paints nothing. */
  background:
    linear-gradient(178deg,rgba(255,255,255,.075),rgba(255,255,255,.018) 46%,rgba(255,255,255,0) 82%),
    var(--ink);
  box-shadow:0 26px 62px -26px rgba(0,0,0,.95);
  opacity:0;transform:translateY(-7px);pointer-events:none;
  transition:opacity 220ms var(--ease),transform 220ms var(--ease);
}
.navsub.open .panel{opacity:1;transform:none;pointer-events:auto}
.navsub .panel a{
  display:flex;align-items:center;min-height:44px;      /* touch floor */
  padding:9px 13px;border-radius:10px;
  color:var(--text);text-decoration:none;font-size:.9rem;line-height:1.3;
  transition:background 160ms,color 160ms;
}
.navsub .panel a:hover,.navsub .panel a:focus-visible{background:rgba(255,255,255,.07);color:#fff}
@media (prefers-reduced-motion: reduce){
  .navsub .panel{transition:none;transform:none}
  .navsub .trig svg{transition:none}
}
.nav .acts{display:flex;align-items:center;gap:14px;margin-left:auto}
.nav .links + .acts{margin-left:0}
.btn.sm{padding:10px 20px;font-size:.88rem;min-height:42px}
.tel{font-size:.86rem;color:var(--muted);text-decoration:none;white-space:nowrap}
.tel:hover{color:var(--gold)}
.tel b{color:#fff;font-weight:500}

/* ---------------------------------------------------------------------------
   THE SCROLLED RAIL.

   Bar at the top, rail after it. While the hero is on screen the horizontal
   bar is exactly what it was. Once the hero has gone the bar fades up and out
   AS ONE BLOCK and a 56px icon rail takes over at the left edge, vertically
   centred, carrying the SAME five in-page anchors, the same "More" set and the
   same apply action. Nothing is removed: the rail is a second state of the one
   navigation, and `navRail()` builds it from the bar's own anchors and
   CONFIG.LINKS, so the two states cannot name a different site.

   THE BAR (law 0): linear.app's shell, which carries a 56px rail through a
   whole document and still names every destination on hover. What it does that
   this header did not: navigation that survives the scroll without spending a
   band of the page, and a current-section state that is legible at icon size.

   1280px AND UP ONLY. Below that the bar's links are hidden and the burger
   and sheet (or the menu glyph, in the GHL block) own navigation. Below 1280
   there is no room for a rail either: the
   content shell is 1240 wide with a 64px gutter, so a 56px rail at the window
   edge lands ON the text column. The left offset below is derived from the
   shell rather than typed, and is clamped so it can never cross the gutter:
   at 1280 it sits at 12px with 16px of clear ground to the first character.

   Borderless: the rail, its hover labels and its panel separate by fill, blur
   and a drop shadow, the same treatment .navsub .panel carries. The groups
   inside it separate by spacing, never by a rule.
   ------------------------------------------------------------------------ */
.navrail{display:none}

@media (min-width:1280px){
  .navrail{
    position:fixed;z-index:52;
    /* 100% and not 100vw. The rail is position:fixed, so a percentage offset
       resolves against the initial containing block - and that block is the
       viewport WITHOUT its scrollbar, which is the same box .wrap is centred
       in. 100vw includes the scrollbar, so on a desktop with a classic one it
       pushed the rail ~7px closer to the text than this maths says. */
    left:clamp(12px, calc((100% - var(--shell)) / 2 - 14px), 40px);
    top:50%;
    display:grid;justify-items:stretch;gap:2px;
    /* minmax(0,1fr), not the implicit auto column. `.nmore` is the one rail
       item with no overflow:hidden of its own, so its automatic minimum size
       is its min-content - and its `.nlbl` is nowrap. That set the auto
       column's width, every `.nrk` stretched to match, and the rows measured
       69.4px inside a 53.8px rail: 21.4px of bleed past the glass, with the
       glyph 14px off the row's centre. Only /home has `.nmore`, which is why
       site/chrome.css's six rails never showed it. Measured 2026-09-20 at
       1440: 69.4 -> 42.2px, bleed -5.8, glyph centred to 0.5px. */
    grid-template-columns:minmax(0,1fr);
    width:56px;padding:8px 6px;border-radius:20px;
    /* WIDTH IS THE LABEL. A chip fired out of a 56px rail lands in the
       middle of the sentence the reader is on: measured at 1280, "Tuition"
       sat on top of the lede, because the rail is 16px from the text column
       at that width and there is no width where a 165px label clears it.
       The rail opens instead, as one surface, the way the bar named in the
       comment above does. Fixed, so nothing on the page reflows. */

    background:
      linear-gradient(178deg,rgba(255,255,255,.075),rgba(255,255,255,.018) 46%,rgba(255,255,255,0) 82%),
      var(--ink-2);
    backdrop-filter:blur(34px) saturate(1.7);
    -webkit-backdrop-filter:blur(34px) saturate(1.7);
    box-shadow:0 26px 62px -26px rgba(0,0,0,.95);
    opacity:0;visibility:hidden;
    /* Both offsets in ONE transform: the -50% centring and the entry slide
       cannot live in two properties, the second would drop the first. */
    transform:translate(-14px,-50%) scale(.96);
    transition:opacity 340ms var(--ease),transform 340ms var(--ease),
               visibility 340ms,width 300ms var(--ease);
  }
  .navrail.on{opacity:1;visibility:visible;transform:translate(0,-50%) scale(1)}
  .navrail.on:hover,.navrail.on:focus-within,.navrail.on:has(.nmore.open){width:196px}

  /* The mark. Their logo, so it stays a raster mark like the bar's; every
     other glyph in here is drawn from ICON_PATHS. Extra ground under it is
     what separates the brand from the links, in place of a rule. */
  .navrail .nbrand{
    display:grid;place-items:center;width:44px;height:44px;border-radius:13px;
    margin-bottom:6px;justify-self:start;
  }
  .navrail .nbrand img{width:26px;height:26px;display:block}

  .navrail .nrk{
    position:relative;display:flex;align-items:center;gap:12px;
    width:100%;height:44px;border-radius:13px;overflow:hidden;
    box-sizing:border-box;
    font:inherit;color:var(--muted);text-decoration:none;
    background:none;border:0;cursor:pointer;
    /* No fill at rest (Law 16, 2026-09-28). Collapsed, this row is a 44px box
       tight around one 19px glyph, so any resting fill is a plate. The glyph
       paints bare; the hover fill below only shows once the rail has opened
       to 196px and the row carries its label, where it is a menu row. */
    /* ONE padding declaration, and it is LAST. A `padding:0` control reset used
       to sit after `padding-left:12px` in this rule and silently beat it, so
       the 19px glyph sat flush against the left edge of a 44px row - 12.5px
       off centre in the collapsed 56px rail. Same defect, same rule, in
       site/chrome.css; both fixed 2026-09-20. */
    padding:0 0 0 12px;
    transition:color 200ms,background 200ms var(--ease);
  }
  .navrail .nrk svg{width:19px;height:19px;flex:none}
  .navrail .nbrand{justify-self:start;margin-left:0}
  /* star8 is the one filled path in the set. */
  .navrail .nrk svg.fill{fill:currentColor;stroke:none}
  .navrail .nrk:hover,.navrail .nrk:focus-visible{color:#fff;background:rgba(255,255,255,.115)}
  .navrail .nrk[aria-current="true"]{color:var(--gold)}

  /* The label is the whole reason a 56px rail can carry seven destinations.
     Opaque, never a tooltip outline, and it never reaches for the text column:
     it opens to the RIGHT of a rail that is already outside the gutter. */
  .navrail .nlbl{
    white-space:nowrap;font-size:.87rem;line-height:1.2;
    opacity:0;transition:opacity 200ms var(--ease);
  }
  .navrail.on:hover .nlbl,.navrail.on:focus-within .nlbl,
  .navrail.on:has(.nmore.open) .nlbl{opacity:1}

  /* More: the site's other pages, the same CONFIG.LINKS the bar submenu and
     the footer column read. */
  /* min-width:0, the second half of Law 9's remedy. The track above is
     minmax(0,1fr), and this is the flex container that holds the nowrap
     label; without it the item's own automatic minimum can still argue
     for min-content. The `.nrk` inside already clips with overflow:hidden. */
  .navrail .nmore{position:relative;display:flex;min-width:0}
  .navrail .nmore.open > .nrk{color:var(--gold)}
  .navrail .npanel{
    position:absolute;left:calc(100% + 12px);top:50%;translate:0 -50%;
    z-index:3;min-width:236px;max-width:300px;
    display:grid;gap:2px;padding:7px;border-radius:16px;
    /* vh IS right here, whatever --widths says about viewport units inside a
       panel. That gate is looking for a panel sized by its parent reading the
       window instead; this panel hangs off a fixed rail whose containing
       block IS the window, and the only thing it must not outgrow is the
       screen. It scrolls rather than clipping if it ever does. */
    max-height:min(70vh,430px);overflow-y:auto;
    background:
      linear-gradient(178deg,rgba(255,255,255,.075),rgba(255,255,255,.018) 46%,rgba(255,255,255,0) 82%),
      var(--ink-2);
    backdrop-filter:blur(34px) saturate(1.7);
    -webkit-backdrop-filter:blur(34px) saturate(1.7);
    box-shadow:0 26px 62px -26px rgba(0,0,0,.95);
    opacity:0;transform:translateX(-8px);pointer-events:none;
    transition:opacity 220ms var(--ease),transform 220ms var(--ease);
  }
  .navrail .nmore.open .npanel{opacity:1;transform:none;pointer-events:auto}
  .navrail .npanel a{
    display:flex;align-items:center;min-height:44px;padding:9px 13px;
    border-radius:10px;color:var(--text);text-decoration:none;
    font-size:.9rem;line-height:1.3;transition:background 160ms,color 160ms;
  }
  .navrail .npanel a:hover,.navrail .npanel a:focus-visible{background:rgba(255,255,255,.07);color:#fff}

  /* The one filled mark in the rail, the same rank the bar's apply button has.
     After the hover rule above on purpose: same specificity, later wins. */
  .navrail .nrk.ngo{
    margin-top:6px;border-radius:14px;
    background:var(--gold);color:#241d12;
    box-shadow:0 12px 26px -12px rgba(255,206,133,.75);
    transition:transform 220ms var(--ease),box-shadow 220ms var(--ease),background 200ms;
  }
  .navrail .nrk.ngo:hover,.navrail .nrk.ngo:focus-visible{
    background:var(--gold-soft);color:#241d12;
    transform:translateY(-2px);box-shadow:0 18px 34px -14px rgba(255,206,133,.85);
  }

  /* The bar goes as ONE block, never field by field. It keeps its 72px of
     sticky space and stops taking any input; the rail is the live control. */
  .nav.railed{pointer-events:none}
  .nav.railed .inner{opacity:0;transform:translateY(-10px);pointer-events:none}
}

@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .navrail,.navrail .npanel{background:var(--ink-2)}
}
@media (prefers-reduced-motion: reduce){
  .navrail{transition:opacity 200ms,visibility 200ms;transform:translate(0,-50%)}
  .navrail.on{transform:translate(0,-50%)}
  .navrail .nlbl,.navrail .npanel,.navrail .nrk.ngo{transition:opacity 160ms}
  .navrail .nlbl,.navrail .npanel{transform:none}
  .nav .inner{transition:opacity 200ms}
}


/* mobile menu */
/* Bare glyph, like the call button beside it. The drawn tile it used to sit in
   made a third container inside a bar that is already a container, next to a
   round badge and an outlined call button. The 44px target stays, it is just
   not painted. */
.burger{
  display:none;margin-left:auto;width:44px;height:44px;cursor:pointer;
  border:0;background:none;padding:0;
  align-items:center;justify-content:center;flex-direction:column;gap:5px;
  transition:opacity 200ms var(--ease);
}
/* The bars paint in currentColor so hover changes the glyph's colour on the
   button itself, never a background in a :hover rule (Law 16). */
.burger{color:#fff}
.burger:hover{color:var(--gold)}
.burger span{display:block;width:19px;height:1.5px;background:currentColor;border-radius:2px;
  transition:transform 300ms var(--ease),opacity 200ms,background 200ms var(--ease)}
.burger[aria-expanded="true"] span:nth-child(1){transform:translateY(6.5px) rotate(45deg)}
.burger[aria-expanded="true"] span:nth-child(2){opacity:0}
.burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-6.5px) rotate(-45deg)}

.sheet{
  position:fixed;inset:0;z-index:49;background:rgba(18,15,23,.97);
  backdrop-filter:blur(24px);
  /* --head is measured at runtime: the announcement strip wraps to two lines on
     narrow screens, so a fixed offset here overlaps the logo. */
  padding:calc(var(--head, 112px) + var(--s-6)) var(--gutter) calc(32px + env(safe-area-inset-bottom));
  display:flex;flex-direction:column;gap:var(--s-2);
  opacity:0;visibility:hidden;transform:translateY(-10px);
  transition:opacity 300ms var(--ease),transform 300ms var(--ease),visibility 300ms;
}
.sheet.open{opacity:1;visibility:visible;transform:none}
.sheet a.item{
  font-family:var(--serif);font-size:1.7rem;line-height:1.25;color:var(--text);text-decoration:none;
  padding:var(--s-4) 0;border-bottom:1px solid var(--hair);
  display:flex;align-items:center;justify-content:space-between;
}
.sheet a.item svg{width:19px;height:19px;color:var(--gold);opacity:.55}
/* The site's other pages, under the five in-page anchors. One step down in
   size and out of the display face: they are a second tier, and seven more
   1.7rem serif rows would run the sheet off the bottom of a phone. */
.sheet .grouplabel{
  margin:var(--s-6) 0 var(--s-1);font-size:.7rem;letter-spacing:.15em;
  text-transform:uppercase;color:var(--muted-2);
}
.sheet a.item.sub{font-family:inherit;font-size:1.02rem;font-weight:400;padding:var(--s-3) 0}
.sheet a.item.sub svg{width:16px;height:16px}
/* The sheet scrolls now: five display rows plus a label plus seven page rows
   plus the footer group is taller than a phone. Momentum on iOS, and the
   footer group stops being pinned to the bottom once the content is longer
   than the sheet. */
.sheet{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch}
.sheet .foot{margin-top:var(--s-8)}
.sheet .foot{margin-top:auto;display:flex;flex-direction:column;gap:var(--s-4);padding-top:var(--s-8)}
.sheet .btn{justify-content:center}
@media (max-width:1279px){
  .nav .links{display:none}
  /* The GHL landing block ships no burger and no sheet: both are
     position:fixed inset:0 over a host page we do not own, so build_ghl.py
     drops them. On that build the menu control is the ONLY route from the
     bar to the site's pages, so it has to survive this breakpoint. It stands
     after the call button, in the burger's slot, the order the sub-page bars
     use. The
     attribute is set in ghl/build/markup.html and nowhere else; index.html
     keeps the sheet, which carries the same links at this width. */
  .nav[data-sheetless] .links{display:flex;margin-left:-6px;order:1}
  .nav[data-sheetless] .links > a{display:none}
  .nav[data-sheetless] .navsub{display:flex}
  /* Anchored to the trigger it hangs off a phone at either edge: measured at
     390 in the block, right:0 put its left edge at -17. .navsub goes static so
     the panel's containing block becomes .nav .inner, which is positioned, and
     it spans the bar instead. The 20px clears the mark's 14px overhang below
     the bar, so the badge is never half under the panel. */
  .navsub{position:static}
  .navsub .panel{
    left:0;right:0;translate:none;min-width:0;max-width:none;
    top:calc(100% + 20px);
  }
  .nav .acts{display:none}
  .burger{display:flex}
  /* The mark used to be absolutely centred here, carried over from the 1100px
     grid where the links sit on one side of it and the actions on the other.
     Nothing flanks it below that width: the links and the acts group are both
     hidden, so a centred mark had a burger on its right and roughly 270px of
     empty glass on its left, which reads as a mark that drifted rather than one
     that was placed. It stands in flow at the left edge on this tier instead,
     and grows past the bar's own height so it breaks both edges of it. */
  .nav .inner{position:relative}
  .nav .inner > #brand{position:static;transform:none}
}
@media (min-width:1280px){ .sheet{display:none} }

/* hero */
/* padding-block only: .wrap owns the side gutters and a padding shorthand here
   would silently zero them, which pins the hero text to the screen edge at 390px. */
.hero-grid{display:grid;gap:clamp(34px,5vw,72px);align-items:center;
  padding-block:clamp(52px,8vw,104px) clamp(44px,6vw,76px)}
@media (min-width:1000px){ .hero-grid{grid-template-columns:minmax(0,1.05fr) minmax(330px,420px)} }
.h1{
  font-family:var(--serif);font-weight:400;color:#fbf7f2;
  font-size:clamp(2.35rem,5.4vw,4.25rem);line-height:var(--lh-display);
  letter-spacing:var(--tr-display);margin:0 0 var(--s-6);text-wrap:balance;
}
.h1 em{font-style:normal;color:var(--gold)}
.cta-row{display:flex;flex-wrap:wrap;gap:var(--s-3);align-items:center;margin:var(--s-8) 0 var(--s-5)}

/* Add-to-calendar menu. Anchored to its button, drops upward so it never runs
   off the bottom of the open-house card, and is opacity/transform only. Glass
   built like the ghosted button: sheen, light rim, real shadow, no blur. */
.cal-wrap{position:relative}
.cal-menu{
  position:absolute;bottom:calc(100% + 10px);left:0;min-width:248px;z-index:6;
  display:grid;padding:6px;border-radius:14px;
  background:
    linear-gradient(177deg,rgba(255,255,255,.07),rgba(255,255,255,.015) 60%),
    #241f2b;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.12),
    inset 0 0 0 1px rgba(255,255,255,.13),
    0 18px 44px -18px rgba(0,0,0,.75);
  opacity:0;transform:translateY(6px);pointer-events:none;
  transition:opacity 200ms var(--ease),transform 200ms var(--ease);
}
.cal-wrap.open .cal-menu{opacity:1;transform:none;pointer-events:auto}
.cal-menu a{
  /* Two columns now: the icon rail and the label stack. align-items:center
     rather than start, so the icon sits against the optical middle of the two
     lines instead of hanging off the top of the first one. */
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:0 11px;
  align-items:center;
  padding:10px 12px;border-radius:9px;text-decoration:none;
  transition:background 160ms;
}
.cal-menu .cal-t{display:grid;gap:1px;min-width:0}
.cal-menu .cal-ico{
  width:19px;height:19px;flex:none;
  /* Dimmer than the label it labels. At full text colour four icons in a
     column read as the loudest thing in the menu and the names stop leading. */
  color:var(--gold-dim);opacity:.85;
  transition:color 160ms,opacity 160ms;
}
.cal-menu a:hover .cal-ico,.cal-menu a:focus-visible .cal-ico{color:var(--gold);opacity:1}
/* Brand marks carry their own palettes, so the gold tint above must not touch
   them. Apple's is the one exception: it is a monochrome mark and takes the
   menu's light ink, which is what keeps it legible on this dark ground.
   Slightly larger than the drawn icon because a filled logo reads smaller than
   a stroked one at the same box. */
.cal-menu .cal-ico.brand{width:21px;height:21px;opacity:1;color:#f7f2ec}
.cal-menu a:hover .cal-ico.brand,.cal-menu a:focus-visible .cal-ico.brand{color:#fff}
.cal-menu a:hover,.cal-menu a:focus-visible{background:rgba(255,255,255,.07)}
.cal-menu b{font-size:.9rem;font-weight:500;color:#f7f2ec;letter-spacing:.002em}
.cal-menu span{font-size:.74rem;color:var(--muted-2)}
@media (prefers-reduced-motion: reduce){
  .cal-menu{transition:none;transform:none}
}
@media (max-width:560px){
  .cal-wrap{width:100%}
  .cal-wrap .btn{width:100%}
  .cal-menu{left:0;right:0;min-width:0}
}

/* The open-house calendar button is a SECOND action beside the gold CTA, not a
   peer of it. At full .btn size it took two lines ("Add to / calendar") and
   ended up taller and wider than the primary, so the card read as two equal
   asks when only one of them is the ask. Smaller, one line, always.

   Scoped to #ohCal on purpose: .btn.ghosted is used at full size elsewhere
   (the funnel's back buttons, the thank-you page) and must not shrink there. */
#ohCal .btn.ghosted{
  white-space:nowrap;                 /* never "Add to / calendar" again */
  /* Same 50px height and 13px radius as the gold primary beside it, so the two
     sit as one row of controls rather than a button and a smaller offcut.
     Hierarchy is carried by weight and colour instead: quieter ground, lighter
     type, tighter horizontal padding, no arrow. */
  padding:14px 20px;
  min-height:50px;
  font-size:.9rem;
  gap:9px;
}
#ohCal .btn.ghosted svg{width:16px;height:16px;flex:none}
@media (max-width:560px){
  /* Full width below 560 is the existing behaviour and still right: two stacked
     full-width buttons beat two half-width ones on a phone. Only the height and
     the type stay reduced, so the hierarchy survives the stack. */
  #ohCal .btn.ghosted{justify-content:center}
}
/* The secondary is a real object, not a wireframe. An outline with nothing in it
   sitting beside a filled key reads as "button and not-quite-button"; this gets
   the same construction as the glass header, a translucent ground with a light
   top rim, so the pair reads as two weights of one system. */
.btn.ghosted{
  background:
    linear-gradient(177deg,rgba(255,255,255,.07),rgba(255,255,255,.015) 60%),
    rgba(255,255,255,.028);
  color:#f7f2ec;border:0;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 0 0 1px rgba(255,255,255,.15),
    0 8px 20px -16px rgba(0,0,0,.8);
}
.btn.ghosted::before{
  background:linear-gradient(177deg,rgba(255,255,255,.1),rgba(255,255,255,0) 60%);
}
.btn.ghosted:hover{
  color:#fff;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.18),
    inset 0 0 0 1px rgba(255,206,133,.42),
    0 14px 28px -18px rgba(0,0,0,.85);
}
.btn.ghosted:active{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.08),
    inset 0 0 0 1px rgba(255,255,255,.14),
    inset 0 2px 6px rgba(0,0,0,.4);
}
.btn.ghosted:focus-visible{
  box-shadow:
    inset 0 0 0 2px rgba(26,23,31,.85),
    inset 0 0 0 3px rgba(255,255,255,.16);
}
.btn.ghosted .ent{color:rgba(247,242,236,.6);border-left-color:rgba(255,255,255,.2)}
/* Hero facts. Three cells on one row, split by hairlines, each with its own
   mark. This replaced a five-line paragraph that buried the start date in the
   middle of a sentence. Value first and large, label under it and quiet, so the
   row is scannable before it is readable. */
/* Each fact is its own glass chip now, not a shared rule across the row: a
   hairline divider looks correct until the row wraps, and then the first cell
   of the second row carries a leading rule against nothing, so a self-contained
   card is wrap-proof at every width in a way a shared top rule never was. */
.facts{
  /* Three across at every width. An auto-fit grid dropped to two columns in
     this hero column and left the third cell alone, which is why the orphan
     span below existed; with a fixed three there is no odd cell to rescue. */
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:10px;margin:var(--s-6) 0 0;max-width:100%;
}
.facts .fact{
  display:flex;align-items:flex-start;gap:10px;
  padding:var(--s-3) var(--s-4);border:0;border-radius:14px;
  /* The nav bar's glass recipe at full strength, not a scaled-down version of
     it. The weaker one was invisible where it mattered: these cards sit over
     the darkest part of the hero, and a .035 ground with a .10 rim over
     near-black has nothing to catch, so they read as three flat panels. The
     four things doing the work, in order of how much they matter here:
     the lit top edge (.16, up from .10), the sheen falling off it, a ground
     with enough body to separate from the plate behind, and a drop shadow deep
     enough to lift the card rather than tint the ground under it.
     Raising the ground costs contrast on the muted label: measured after, and
     it holds above the 4.5:1 floor. */
  background:
    linear-gradient(178deg,rgba(255,255,255,.115),rgba(255,255,255,.025) 52%,rgba(255,255,255,0) 88%),
    rgba(255,255,255,.055);
  backdrop-filter:blur(24px) saturate(1.7);
  -webkit-backdrop-filter:blur(24px) saturate(1.7);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 0 0 1px rgba(255,255,255,.07),
    0 14px 32px -20px rgba(0,0,0,.85);
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .facts .fact{background:rgba(30,26,36,.7)}
}
/* The orphan span that used to live here is gone: the grid is a fixed three
   columns now, so three facts fill one row exactly and there is no odd cell.
   The general no-orphan rule still applies to grids whose item count varies. */
.facts .fmk{
  flex:none;width:19px;height:19px;display:grid;place-items:center;
  color:var(--gold-dim);margin-top:2px;
}
.facts .fmk svg{width:17px;height:17px;flex:none}
.facts .ftx{display:grid;gap:1px;min-width:0}
/* A fact value is a label, not a sentence. It holds one line at every width;
   anything that would wrap belongs in a shorter config string instead. */
.facts .ftx b{
  font-size:.95rem;font-weight:600;color:var(--text);letter-spacing:-.005em;
  line-height:1.3;white-space:nowrap;
}
/* One line, always. A fact label is a caption on the value above it, not a
   sentence, and the three cards are a row: one of them wrapping made all three
   grow, so a single long string cost the whole band 18px of height. nowrap
   turns a silent wrap into a visible overflow, which is the failure mode you
   can actually catch: any label added here has to be short enough to fit the
   narrowest cell (124px of content at ~1024px, where the hero column is
   tightest), or it needs a shorter form in the t-short slot. */
.facts .ftx span{
  font-size:.78rem;color:var(--muted-2);letter-spacing:.012em;white-space:nowrap;
}

.microcopy{font-size:.78rem;line-height:1.58;color:var(--muted-2);margin:0;letter-spacing:.004em}
/* Eight-point star marking the qualifier line. Filled, so it needs fill and no
   stroke; the shared icon() helper draws stroked by default. Aligned to the
   first line rather than the block, and it never wraps away from the text. */
.microcopy .mstar{
  display:inline-flex;align-items:center;justify-content:center;
  width:9px;height:9px;flex:none;margin-right:5px;
  color:var(--gold-dim);vertical-align:baseline;position:relative;top:1px;
}
.microcopy .mstar svg{width:9px;height:9px;flex:none;fill:currentColor;stroke:none}

/* ============================================================================
   Stat band. Contained rather than edge-to-edge, one icon per figure, and the
   numbers count up once when the band first enters view.
   ========================================================================= */
.band{
  position:relative;z-index:11;
  /* rides up over the bottom of the banner rather than sitting below it */
  margin-top:calc(-1 * var(--band-rise));
  display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));
  gap:1px;background:var(--line);
  /* NO BORDER. A drawn border paints its own corner, and against
     border-radius + overflow:hidden the square half of it shows outside the
     clip as a notch at each bottom corner. That is the artifact. The same bug
     was fixed on `.stats` (see the note above it) and returned here. The band
     already separates from the page by fill and by its drop shadow, so the rim
     is not doing work either. */
  border:0;border-radius:var(--radius);overflow:hidden;
}
.band .cell{
  background:var(--ink-2);padding:var(--s-8) var(--s-6);
  display:flex;flex-direction:column;gap:var(--s-3);
  transition:background 320ms var(--ease);
}
/* The short label only exists for the narrow band; above that the full one runs. */
.band .cell .lbl-short{display:none}
.band .cell:hover{background:var(--ink-3)}
/* larger, and thinner-stroked to stay elegant at size: 1.5 stroke that reads
   right at 19px looks like a marker pen at 28 */
.band .ico{width:28px;height:28px;color:var(--gold-dim);stroke-width:1.15;
  transition:color 300ms,transform 320ms var(--ease)}
.band .cell:hover .ico{color:var(--gold);transform:translateY(-2px)}
.band .cell{gap:var(--s-4)}
.band b{
  display:block;font-family:var(--serif);font-size:clamp(1.75rem,2.7vw,2.3rem);font-weight:400;
  color:var(--gold);line-height:1;letter-spacing:-.02em;font-variant-numeric:tabular-nums;
}
.band span{display:block;font-size:.79rem;color:var(--muted-2);line-height:1.45;
  letter-spacing:.008em;max-width:22ch}

/* generic card grids */
.grid{display:grid;gap:var(--s-4)}
.g2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.g3{grid-template-columns:repeat(auto-fit,minmax(266px,1fr))}
.g4{grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
.pane{
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.025));
  border:1px solid var(--line);border-radius:var(--radius);padding:var(--s-8);
  display:flex;flex-direction:column;align-items:flex-start;
}
.pane.pad-lg{padding:clamp(28px,3.4vw,44px)}
.pane > :last-child{margin-bottom:0}
.pane p{font-size:.95rem;line-height:1.66;letter-spacing:var(--tr-body);color:var(--muted);margin:0}
.pane .tag{display:inline-block;font-size:.7rem;letter-spacing:var(--tr-meta);text-transform:uppercase;
  color:var(--gold-dim);margin:0 0 var(--s-3)}
.pane .price{font-family:var(--serif);font-size:1.6rem;color:var(--gold);line-height:1;margin:0 0 4px}

/* numbered steps */
.steps{counter-reset:s;display:grid;gap:var(--s-4)}
.steps .pane{position:relative;padding-top:var(--s-16)}
.steps .pane::before{
  counter-increment:s;content:counter(s);
  position:absolute;left:var(--s-8);top:var(--s-8);font-family:var(--serif);font-size:1.5rem;
  color:var(--gold);line-height:1;
  border-bottom:1px solid rgba(255,206,133,.35);padding-bottom:var(--s-2);min-width:26px;
}

/* feature list with dot */
.ticks{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-3)}
.ticks li{display:flex;gap:var(--s-3);font-size:.97rem;line-height:1.6;letter-spacing:var(--tr-body);
  color:rgba(246,240,232,.86)}
.ticks li svg{flex:none;width:17px;height:17px;margin-top:3px;fill:var(--gold)}

/* objection cards */
.obj{display:grid;gap:16px}
.obj .pane{padding-top:var(--s-6)}
.obj .pane::before{content:"";display:block;width:34px;height:1px;background:var(--gold);margin:0 0 var(--s-5)}
.obj .q{font-family:var(--serif);font-style:italic;font-size:1.12rem;color:#f7f2ec;margin:0 0 10px}

/* review grid */
.rgrid{display:grid;gap:var(--s-4);grid-template-columns:repeat(auto-fit,minmax(288px,1fr));align-items:start}
.rgrid .card{padding:26px}

/* FAQ */
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{
  list-style:none;cursor:pointer;padding:var(--s-6) 48px var(--s-6) 0;position:relative;
  font-size:1.06rem;font-weight:400;line-height:1.45;color:var(--text);letter-spacing:-.008em;
  transition:color 160ms;
}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{
  content:"";position:absolute;right:8px;top:50%;width:11px;height:11px;
  border-right:1.5px solid var(--gold);border-bottom:1.5px solid var(--gold);
  transform:translateY(-70%) rotate(45deg);transition:transform 220ms var(--ease);
}
.faq details[open] summary::after{transform:translateY(-25%) rotate(225deg)}
.faq summary:hover{color:var(--gold)}
.faq .a{padding:0 clamp(0px,4vw,72px) var(--s-6) 0;font-size:.98rem;line-height:var(--lh-body);
  letter-spacing:var(--tr-body);color:var(--muted);max-width:var(--measure)}
.faq .a p{margin:0 0 12px}
.faq .a p:last-child{margin:0}
.faq .a a{color:var(--gold)}

/* campuses */
.campus-card{display:flex;flex-direction:column;gap:14px}
.campus-card .shot{aspect-ratio:16/10}
.campus-card .adr{font-size:.92rem;line-height:1.6;color:var(--muted)}
/* #157: one campus since #149. A lone card in a two-up grid left half the band
   empty, so a single campus runs the full wrap: photo beside the address on
   desktop, stacked full width on a phone. */
#campusCards.solo{grid-template-columns:minmax(0,1fr)}
#campusCards.solo .campus-card{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);
  column-gap:var(--s-10);row-gap:var(--s-3);align-content:center}
#campusCards.solo .campus-card{grid-template-rows:1fr auto auto auto 1fr}
#campusCards.solo .campus-card .shot{grid-column:1;grid-row:1 / -1;margin:0}
#campusCards.solo .campus-card > :not(.shot){grid-column:2}
#campusCards.solo .campus-card h3{grid-row:2}
#campusCards.solo .campus-card .adr{grid-row:3}
#campusCards.solo .campus-card > p:last-child{grid-row:4}
@media (max-width:700px){
  #campusCards.solo .campus-card{grid-template-columns:minmax(0,1fr);grid-template-rows:none}
  #campusCards.solo .campus-card > :nth-child(n){grid-column:auto;grid-row:auto}
  #campusCards.solo h3{font-size:1.1rem}
  #campusCards.solo p{font-size:.9rem}
}

/* final CTA */
.finale{
  border:1px solid rgba(255,206,133,.28);border-radius:22px;
  background:linear-gradient(140deg,rgba(255,206,133,.1),rgba(22,60,104,.22));
  padding:clamp(40px,5.5vw,76px) clamp(28px,5vw,64px);text-align:center;
}

/* thank-you */
.tick-big{display:grid;place-items:center;margin:0 0 24px;color:var(--gold)}
.tick-big svg{width:30px;height:30px;fill:var(--gold)}
.two{display:grid;gap:clamp(26px,4vw,52px)}
@media (min-width:1000px){
  .two{grid-template-columns:minmax(0,1.05fr) minmax(320px,400px);align-items:start}
  /* The photo sticks while the skills list runs past it, so the column beside
     the list is never empty and the image stays in view for all seven rows.
     The offset clears the floating header rather than guessing: --head is the
     header's measured bottom edge, already published by the nav script. */
  #learn .two > #learnShot{position:sticky;top:calc(var(--head, 108px) + var(--s-6))}
  /* The deck ran 83ch inside this column, past the 65 to 75ch a single line of
     prose stays readable at. It is a deck, so it caps tighter than body copy. */
  #learn .two .body{max-width:58ch}
}
/* One column inside the narrower text column. The two-up version below is for
   when this list is not nested. Separated generously from the deck above it:
   heading and deck are one group, the list is another, and 24px read as the
   same interval as the gap inside the heading block. */
#learn .two .syllabus{grid-template-columns:minmax(0,1fr);margin-top:var(--s-10)}
/* The reviews section centres this pill inside its own full-width wrap; here it
   sits at the foot of a left-aligned text column, so it stays left-aligned too. */
.learn-allrev{margin-top:var(--s-8);text-align:left}
/* No orphan: an odd row count leaves the last skill alone across half the
   width. It takes the full width instead. */
.syllabus > *:last-child:nth-child(odd){grid-column:1 / -1}

/* social row is used in the footer and on the thank-you page */
.soc{display:flex;flex-wrap:wrap;gap:16px}
.soc a{color:var(--muted);text-decoration:none}
.soc a:hover{color:var(--gold)}

/* ============================================================================
   Typographic blocks that replace card grids where the items are not
   genuinely parallel offers. Cards stretch to equal height and leave dead
   space; hairline columns and definition rows do not.
   ========================================================================= */

/* three columns divided by hairlines, no box, no stretch */
.cols{
  display:grid;gap:0;
  border-top:1px solid var(--line);
}
.cols > div{
  padding:var(--s-8) 0 var(--s-2);
  border-bottom:1px solid var(--hair);
}
.cols > div:last-child{border-bottom:0}
@media (min-width:820px){
  .cols{grid-template-columns:repeat(3,1fr);gap:0 var(--s-10)}
  .cols > div{border-bottom:0;padding:var(--s-8) 0 0;position:relative}
  .cols > div + div::before{
    content:"";position:absolute;left:calc(var(--s-10) / -2);top:var(--s-8);bottom:var(--s-1);
    width:1px;background:var(--hair);
  }
}
.cols h3{margin:0 0 var(--s-4)}
.cols p{font-size:.95rem;line-height:1.66;letter-spacing:var(--tr-body);color:var(--muted);margin:0 0 var(--s-4)}
.cols > div > :last-child{margin-bottom:0}

/* definition rows: label left, explanation right, hairline between */
.dl{border-top:1px solid var(--line)}
.dl > div{
  display:grid;gap:var(--s-2) var(--s-10);
  padding:var(--s-6) 0;border-bottom:1px solid var(--hair);
}
.dl > div:last-child{border-bottom:0}
@media (min-width:760px){
  .dl > div{grid-template-columns:minmax(0,15rem) minmax(0,1fr);align-items:baseline}
}
.dl dt{font-size:1.05rem;font-weight:500;letter-spacing:-.012em;color:var(--text);margin:0}
.dl dd{margin:0;font-size:.97rem;line-height:1.66;letter-spacing:var(--tr-body);color:var(--muted);max-width:62ch}

/* inline social row */
.soc.inline{gap:var(--s-5)}
.soc.inline a{
  font-size:.92rem;color:var(--muted);text-decoration:underline;
  text-decoration-color:rgba(255,255,255,.22);
}
.soc.inline a:hover{color:var(--gold);text-decoration-color:var(--gold)}

/* ============================================================================
   Section surfaces. The page alternates three tones so the eye gets a rhythm
   instead of one continuous dark field: floor, raised, recessed.
   ========================================================================= */
.sec{position:relative;--band-bg:var(--ink)}
.sec.raised{--band-bg:var(--ink-2)}
.sec.deep{--band-bg:var(--ink-deep)}
.sec.raised{background:var(--ink-2)}
.sec.raised .pane{background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.02))}
.sec.deep{background:var(--ink-deep)}
.sec.raised + .sec, .sec.deep + .sec{border-top:0}
.sec.raised, .sec.deep{border-top:1px solid var(--hair);border-bottom:1px solid var(--hair)}

/* a hairline of gold light along the top edge of a raised band */
.sec.raised::before,.sec.deep::before{
  content:"";position:absolute;top:-1px;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(255,206,133,.28) 22%,rgba(255,206,133,.28) 78%,transparent);
}

/* full-bleed photography band, used as a breath between text sections */
.strip{margin:0;position:relative;z-index:10;height:clamp(280px,36vw,480px);overflow:hidden;
  border-top:1px solid var(--hair);border-bottom:1px solid var(--hair)}
.strip img{width:100%;height:100%;object-fit:cover;object-position:center 32%;
  filter:saturate(.9) contrast(1.04) brightness(.82)}
.strip::after{content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,rgba(26,23,31,.92) 0%,rgba(26,23,31,.55) 42%,rgba(26,23,31,.25) 100%)}
.strip figcaption{
  position:absolute;z-index:2;left:0;right:0;bottom:0;top:0;display:flex;align-items:center;
}
.strip .wrap{width:min(var(--shell),100%)}
/* Centred. A max-width block does not centre itself inside a centred container,
   so it needs the auto margins as well as the text alignment. */
.strip .line{
  font-family:var(--serif);font-size:clamp(1.8rem,4vw,3.15rem);line-height:1.14;
  letter-spacing:var(--tr-heading);color:#fbf7f2;
  max-width:19ch;margin-inline:auto;text-align:center;
  /* balance, so the browser evens the lines instead of pushing one orphan word
     onto the first line. It was breaking after "and a", stranding the article
     away from the noun it belongs to. */
  text-wrap:balance;
}
.strip .line em{font-style:normal;color:var(--gold);position:relative}
/* A vignette focused on the centre of the band, so the eye lands on the line
   rather than on whatever the photograph is doing at the edges. One radial
   layer, no filter running per frame. */
.strip figcaption::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(58% 72% at 50% 50%,rgba(0,0,0,.42),rgba(0,0,0,0) 72%);
}
/* A hairline under the accent phrase, drawn from the centre out. */
.strip .line em::after{
  content:"";position:absolute;left:8%;right:8%;bottom:-.16em;height:1px;
  background:linear-gradient(90deg,rgba(255,206,133,0),var(--gold),rgba(255,206,133,0));
}

/* hero gets a soft floor so it lands on the band below instead of stopping dead */
.hero-grid{position:relative;z-index:10}

/* ============================================================================
   Full-bleed hero. The school film is 406x720 portrait, so it fills a phone
   screen at close to native resolution and only needs help on desktop, where
   it is upscaled: a 1px blur there cleans the upscaling without reading blurry.
   ========================================================================= */
.hero-full{
  position:relative;isolation:isolate;overflow:hidden;
  /* The column is anchored to the bottom (align-items:flex-end), so the empty
     ground ABOVE the headline is whatever min-height leaves over, not the top
     padding. Measured 2026-08-23, the old clamp(580px,92svh,940px) left the
     headline 142px under the floating bar at 1440x900, 228px at 1728x1000 and
     231px on a 430x932 phone, against 38px on a short window: the same hero
     read completely differently depending on how tall the screen was. Pulled to
     82svh/800px, which puts that gap in a 38-138 band across the whole matrix
     and never lets a tall screen open a third of a screen of dead air. The
     bottom padding is untouched, so the band still rises into the same overlap. */
  min-height:clamp(560px,82svh,800px);
  display:flex;align-items:flex-end;
  padding-block:clamp(96px,14vh,150px) clamp(40px,7vh,84px);
}
.hero-full .bg{position:absolute;inset:0;z-index:-2;background:var(--ink-deep)}
.hero-full .bg > *{
  width:100%;height:100%;object-fit:cover;object-position:50% 28%;display:block;
  /* Blur was 3px here and 5px above 1000px. Eased back so the training footage
     is readable as footage. The film is 406x720, so above 1000px it is being
     upscaled and still needs some softening to hide the resample; that is what
     the second value is for, not legibility. The scrims below do the contrast
     work, so dropping blur does not put the headline at risk. */
  filter:saturate(.88) contrast(1.02) brightness(.9) blur(1.5px);transform:scale(1.04);
}
@media (min-width:1000px){
  .hero-full .bg > *{filter:saturate(.88) contrast(1.02) brightness(.88) blur(2.6px);transform:scale(1.05)}
}

/* Two stacked scrims. The vertical one guarantees legibility over any frame of
   the film; the horizontal one only exists on desktop, where the text column
   sits left of centre. Both land on --ink so the stat band below joins cleanly. */
.hero-full .scrim{
  position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(180deg,
      rgba(16,13,19,.90) 0%,
      rgba(16,13,19,.72) 26%,
      rgba(16,13,19,.88) 62%,
      rgba(26,23,31,.98) 88%,
      var(--ink) 100%);
}
@media (min-width:900px){
  .hero-full .scrim{
    background:
      linear-gradient(90deg,
        rgba(16,13,19,.96) 0%,
        rgba(16,13,19,.9) 34%,
        rgba(16,13,19,.66) 68%,
        rgba(16,13,19,.54) 100%),
      linear-gradient(180deg,
        rgba(16,13,19,.72) 0%,
        rgba(16,13,19,.42) 34%,
        rgba(16,13,19,.82) 74%,
        rgba(26,23,31,.97) 92%,
        var(--ink) 100%);
  }
}

.hero-full .hero-inner{position:relative;z-index:1;width:min(var(--shell),100%)}
.hero-full .col{max-width:min(100%,42rem)}
@media (min-width:900px){ .hero-full .col{max-width:36rem} }
@media (min-width:1240px){ .hero-full .col{max-width:41rem} }
/* The lede is two sentences now, each on its own line. The first reads better
   unwrapped than as a ragged two-line block, and widening the column (above) is
   what actually buys the room; nowrap alone would just clip it. Scoped to the
   width where it was measured to fit, and scoped to .l1 only: the pair on one
   nowrap line would run past the column and force the page to scroll sideways. */
.hero-full .lede .l1,.hero-full .lede .l2{display:block}
.hero-full .lede .l2{color:rgba(246,240,232,.72);margin-top:.3em}
/* q224 lede-inline: below 1160px (phones, tablets) the two sentences read as one block, back to back. Desktop keeps the two stacked lines. */
@media (max-width:1159px){ .hero-full .lede .l1,.hero-full .lede .l2{display:inline} .hero-full .lede .l2{margin-top:0} }
@media (min-width:1160px){ .hero-full .lede .l1{white-space:nowrap} }

/* ----------------------------------------------------------------------------
   Wide screens move the fact cells out of the text column and stack them down
   the hero's right side. Three chips in a row under the button pushed the
   microcopy a long way down the fold and left the whole right half of the hero
   holding nothing but the video. Stacked on the right they fill that side and
   the text column reads as one uninterrupted run: headline, deck, rating,
   button, microcopy.

   The move is CSS only. `.facts` stays where it is in the DOM, between the
   rating and the button, which is the order that makes sense when the layout
   collapses back to one column below 1180px and the reading order is the DOM
   order. Nothing about the narrow layouts changes.

   1180px is the floor because that is where the content box is wide enough for
   the text column at its full 41rem plus the gap plus the cells: 1062px
   available against 972px needed, measured, not assumed.
   ------------------------------------------------------------------------- */
@media (min-width:1180px){
  /* The hero's content box is given the floating bar's own width rule, verbatim,
     so the two agree at every width instead of by luck. They did not before: the
     bar ran 1208px and the hero content 1112px at 1440, which put the headline
     48px inside the bar's left edge and left the fact cells 169px short of its
     right edge. Two edges that nearly line up read worse than two that obviously
     do not, because the eye keeps checking. Now they are the same line.

     The expression is the bar's: shell less half a gutter, floored by the
     viewport less two gutters, which is what `.nav{padding-inline}` imposes on
     `.nav .inner` at widths where the shell is not the binding constraint. */
  .hero-full .hero-inner{
    width:min(calc(var(--shell) - var(--gutter) * .5),calc(100% - var(--gutter) * 2));
    padding-inline:0;
  }
  .hero-full .col{
    max-width:none;
    display:grid;
    /* The cells column takes the leftover rather than a fixed width, and the
       cells are pinned to its right edge, so they end on the bar's right edge
       whatever the leftover turns out to be. */
    grid-template-columns:minmax(0,41rem) minmax(0,1fr);
    column-gap:clamp(40px,4.4vw,72px);
    align-items:start;
  }
  /* Everything else stays in the text column. Stated on the universal child
     selector so a block added to the hero later joins the text run by default
     rather than silently landing in the cells column. */
  .hero-full .col > *{grid-column:1}
  /* The rating pill is an inline-flex element, so in normal flow it was as wide
     as its own content. A grid child stretches in the inline axis by default,
     which turned it into a 656px bar with the stars stranded at one end. Only
     this one needs opting out: the headline, the deck and the microcopy are
     blocks that were full-width already, and the button row is a flex container
     whose button is still content-sized inside it. */
  .hero-full .col > .hero-tag{justify-self:start}
  .hero-full .col > .facts{
    grid-column:2;grid-row:1 / -1;align-self:center;
    /* Pinned to the right edge of its column, and sized by its own content
       rather than to a number. `max-content` on the grid makes the column as
       wide as the widest of the three lines; the single 1fr column then makes
       all three cards exactly that width, so they are equal by construction
       instead of by a value someone picked. 300px left a third of every card
       empty. max-width keeps it inside the column if the copy ever grows. */
    justify-self:end;width:max-content;max-width:100%;
    /* one per row, not three across */
    grid-template-columns:minmax(0,1fr);
    gap:12px;margin:0;
  }
}

.hero-full .h1{margin-bottom:var(--s-5)}
.hero-full .lede{color:rgba(246,240,232,.86);max-width:44rem;margin-bottom:0}
/* The pill is inline-flex globally, which puts it on a LINE BOX in the hero's
   flow layout: the next block's margin then measures from the line box, not
   from the pill, and a stated 24 rendered as 10. Block-level and content-width
   here makes the box predictable, so the same numbers hold in the grid layout
   above 1180 and the flow layout below it. Visually identical. */
.hero-full .hero-tag{display:flex;width:max-content;max-width:100%;margin:0}
.hero-full .cta-row{margin:var(--s-8) 0 var(--s-2)}
.hero-full .microcopy{color:rgba(246,240,232,.66)}
/* #88: both campuses and each one's next start, under the fact cells. Each
   campus is one unbreakable unit; the line may wrap BETWEEN campuses only. */
.hero-full .starts{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 18px;
  margin:var(--s-4) 0 0;font-size:.95rem;line-height:1.35;color:rgba(246,240,232,.82)}
.hero-full .starts .sl{color:rgba(246,240,232,.6);letter-spacing:.06em;text-transform:uppercase;font-size:.78rem}
.hero-full .starts .sc{white-space:nowrap}
@media (max-width:600px){.hero-full .starts .sl{flex-basis:100%}.hero-full .starts .sc{flex-basis:100%}}
.hero-full .starts .sc b{color:#f6f0e8;font-weight:600}

/* a live marker so the moving background reads as their own footage, not stock.
   Runs horizontally above the fact row now, in flow rather than pinned to the
   edge, so it shows at every width instead of only above 1180px. */
.hero-tag{
  display:inline-flex;align-items:center;gap:10px;white-space:nowrap;
  font-size:.68rem;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(255,255,255,.7);
  background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.015));
  border:0;border-radius:999px;padding:6px 16px;
  /* Borderless: `inset 0 1px 0` is a lit top edge and `inset 0 0 0 1px` is a
     rim. Both are borders by another spelling and both are banned. The pill
     reads as a pill from its fill alone. */
  box-shadow:none;
  margin:0;
  max-width:100%;
}
.hero-tag .live{width:6px;height:6px;border-radius:50%;background:var(--gold);flex:none;
  animation:pulse 2.6s ease-in-out infinite}
@media (prefers-reduced-motion: reduce){ .hero-tag .live{animation:none} }
.hero-tag .gmark{width:13px;height:13px;flex:none}
/* Narrow phones: the full pill text plus the mark run past 360px at the base
   size. Smaller type and tighter tracking before anything is asked to wrap. */
@media (max-width:420px){
  .hero-tag{font-size:.6rem;letter-spacing:.1em;gap:7px;padding:6px 12px}
  .hero-tag .rstars{letter-spacing:.08em}
}

/* scroll cue, desktop only, disappears the moment the page moves */
.scroll-cue{
  position:absolute;z-index:1;left:50%;transform:translateX(-50%);bottom:clamp(18px,3vh,34px);
  width:1px;height:46px;background:linear-gradient(to bottom,transparent,rgba(255,206,133,.65));
  opacity:.9;transition:opacity 400ms ease;
}
@media (max-width:899px){ .scroll-cue{display:none} }

/* ============================================================================
   Hero entrance. The elements arrive one at a time rather than the block
   appearing at once.

   Load-time, not scroll-triggered: the hero is above the fold, so there is
   nothing to observe and an IntersectionObserver would only add a way to fail.
   Same contract as the reveal below it, and the same reason: the finished state
   is what the CSS ships. `.hero-armed` is added by script and only when motion
   is allowed and Save-Data is off, so no JS, reduced motion, a headless render
   or a saved-data connection all show the hero already assembled.

   The script also carries its own backstop timer, so `.hero-in` is added even
   if the ready signal never arrives. Nothing here can leave the hero hidden.

   Only opacity and transform animate, and the per-element stagger is a
   transition-delay rather than a keyframe, so there is one property to read
   when a delay looks wrong.
   ========================================================================= */
.hero-armed > *:not(.facts),
.hero-armed .fact{opacity:0;transform:translateY(14px)}
.hero-in > *:not(.facts),
.hero-in .fact{
  opacity:1;transform:none;
  transition:opacity 640ms var(--ease),transform 640ms var(--ease);
  transition-delay:var(--d,0ms);
}
@media (prefers-reduced-motion: reduce){
  .hero-armed > *:not(.facts),.hero-armed .fact{opacity:1;transform:none}
  .hero-in > *:not(.facts),.hero-in .fact{transition:none}
}

/* ============================================================================
   Reveal. Content is visible by default and only animates once JS has armed it,
   so a headless render, a hidden tab, or no JS still shows the finished state.
   ========================================================================= */
.rise-armed{opacity:0;transform:translateY(18px);
  transition:opacity 620ms var(--ease),transform 620ms var(--ease)}
.rise-in{opacity:1;transform:none}
@media (prefers-reduced-motion: reduce){
  .rise-armed{opacity:1;transform:none;transition:none}
}

/* ============================================================================
   Icons
   ========================================================================= */
.ico{width:22px;height:22px;flex:none;color:var(--gold)}
.ico-lg{width:26px;height:26px}
/* Law 16: the program glyph paints bare in gold at display size; the ring and
   tint it used to sit in are gone. Hover answers with a lift, never a fill. */
.ico-ring{
  flex:none;align-self:flex-start;display:grid;place-items:center;color:var(--gold);
  transition:transform 260ms var(--ease);
}
.ico-ring .ico-lg{width:34px;height:34px;stroke-width:1.4}

/* ============================================================================
   Flow: three steps as a path. The connector is drawn per step, from one ring
   to the next, so it never overshoots the last one or crosses a label.
   ========================================================================= */
.flow{display:grid;gap:var(--s-10)}
@media (min-width:860px){ .flow{grid-template-columns:repeat(3,1fr);gap:var(--s-8)} }
.flow .step{position:relative;display:flex;flex-direction:column;gap:var(--s-4);align-items:flex-start}
@media (min-width:860px){
  .flow .step:not(:last-child)::after{
    content:"";position:absolute;top:17px;left:50px;right:calc(var(--s-8) * -1);height:1px;
    background:linear-gradient(90deg,rgba(255,206,133,.32),rgba(255,206,133,.1));
  }
}
.flow .ico-ring{position:relative;z-index:1}
.flow h3{
  font-family:var(--serif);font-weight:400;font-size:clamp(1.3rem,2vw,1.6rem);
  letter-spacing:var(--tr-heading);line-height:1.16;margin:0;color:var(--text);
}
.flow p{font-size:.97rem;line-height:1.66;color:var(--muted);margin:0;max-width:34ch}
.flow .step:hover .ico-ring{transform:translateY(-3px)}

/* ============================================================================
   Offers: the three programs. Genuinely parallel, so cards are honest here,
   but they are not equal weight: the one most students take leads.
   ========================================================================= */
.offers{display:grid;gap:var(--s-4)}
@media (min-width:900px){ .offers{grid-template-columns:1.25fr 1fr 1fr;align-items:stretch} }
.offer{
  position:relative;overflow:hidden;display:flex;flex-direction:column;
  border:0;border-radius:var(--radius);
  padding:var(--s-8);text-decoration:none;color:inherit;
  /* No drawn border. The card is separated by light: a warm mass falling off
     from the top left, a hairline of light along the top edge as an inset
     shadow, and an ambient shadow seating it. A 1px rectangle around every card
     was the cheapest possible way to say "card" and it fought the hairline
     language the rest of the page uses. */
  background:
    radial-gradient(120% 90% at 0% 0%,rgba(255,206,133,.075),rgba(255,206,133,0) 58%),
    linear-gradient(180deg,rgba(255,255,255,.052),rgba(255,255,255,.018));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.09),
    0 22px 48px -34px rgba(0,0,0,.95);
  transition:transform 300ms var(--ease),box-shadow 300ms var(--ease),background 300ms var(--ease);
}
.offer::after{ /* a light sweep along the top edge on hover */
  content:"";position:absolute;top:0;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,var(--gold),transparent);
  transform:scaleX(0);transform-origin:left;transition:transform 520ms var(--ease);
}
.offer:hover{transform:translateY(-4px);
  box-shadow:
    inset 0 1px 0 rgba(255,206,133,.34),
    0 30px 60px -34px rgba(0,0,0,.98);
  background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.03))}
.offer:hover::after{transform:scaleX(1)}
.offer:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.offer .ico-ring{margin-bottom:var(--s-6)}
.offer:hover .ico-ring{transform:translateY(-3px)}
.offer h3{
  font-family:var(--serif);font-weight:400;font-size:clamp(1.35rem,2.1vw,1.75rem);
  line-height:1.14;letter-spacing:var(--tr-heading);margin:0 0 var(--s-3);color:var(--text);
}
.offer .blurb{font-size:.95rem;line-height:1.66;color:var(--muted);margin:0 0 var(--s-6)}
.offer .facts{
  margin:auto 0 0;padding-top:var(--s-5);border-top:1px solid var(--hair);
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);
}
.offer .facts .k{font-size:.78rem;letter-spacing:.06em;color:var(--muted-2);
  font-variant-numeric:tabular-nums}
.offer .go{display:inline-flex;align-items:center;gap:8px;font-size:.86rem;color:var(--gold);white-space:nowrap}
.offer .go svg{width:17px;height:17px;transition:transform 300ms var(--ease)}
.offer:hover .go svg{transform:translateX(5px)}
.offer .price{
  font-family:var(--serif);font-size:2rem;line-height:1;color:var(--gold);
  margin:0 0 var(--s-2);font-variant-numeric:tabular-nums;
}
.offer.lead{border-color:rgba(255,206,133,.3);background:
  linear-gradient(180deg,rgba(255,206,133,.075),rgba(255,255,255,.02))}
.offer .flag{
  align-self:flex-start;margin-bottom:var(--s-5);font-size:.7rem;letter-spacing:var(--tr-meta);
  text-transform:uppercase;color:var(--ink);background:var(--gold);
  border-radius:999px;padding:5px 12px;font-weight:600;
}

/* One program on offer (#programCards.solo): a single full-width row, so the
   band reads as one deliberate path, not a three-up grid with two gaps. */
.offers.solo .offer{background:
  radial-gradient(80% 140% at 0% 0%,rgba(255,206,133,.09),rgba(255,206,133,0) 60%),
  linear-gradient(180deg,rgba(255,255,255,.052),rgba(255,255,255,.018))}
@media (min-width:900px){
  .offers.solo{grid-template-columns:1fr}
  .offers.solo .offer{
    display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;
    column-gap:var(--s-8);padding:var(--s-8) var(--s-10);
  }
  .offers.solo .offer .ico-ring{margin:0}
  .offers.solo .offer .txt{max-width:60ch}
  .offers.solo .offer .blurb{margin:0}
  .offers.solo .offer .facts{
    margin:0;padding:0;border-top:0;
    flex-direction:column;align-items:flex-start;justify-content:center;gap:var(--s-3);
    align-self:stretch;
  }
  .offers.solo .offer:hover .ico-ring{transform:none}
}

/* ============================================================================
   Syllabus: the curriculum, as a two-column index with an icon per skill
   ========================================================================= */
.syllabus{
  display:grid;gap:0;margin-top:var(--s-12);
  border-top:1px solid var(--line);
}
@media (min-width:760px){
  .syllabus{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:var(--s-10)}
  /* seven items in two columns leaves an odd one out: let it run the full width */
  .syllabus .row:last-child:nth-child(odd){grid-column:1 / -1}
}
.syllabus .row{
  /* flex-wrap:nowrap is load-bearing. The generic `.row` in the funnel styles
     above sets flex-wrap:wrap, and this rule never overrode it, so in a column
     narrower than the longest label the text dropped below the numeral and the
     icon instead of sitting beside them. The label wraps its own text; the row
     itself must not wrap. */
  position:relative;display:flex;align-items:center;flex-wrap:nowrap;
  gap:var(--s-4);min-width:0;
  padding:var(--s-5) var(--s-4) var(--s-5) var(--s-4);
  border-bottom:1px solid var(--hair);
  transition:background 320ms var(--ease);
  isolation:isolate;
}
.syllabus .row::before{ /* a tone wash that wipes in from the left on hover */
  content:"";position:absolute;inset:0;z-index:-1;border-radius:10px;
  background:linear-gradient(90deg,rgba(255,206,133,.09),rgba(255,206,133,0));
  transform:scaleX(0);transform-origin:left;transition:transform 420ms var(--ease);
}
.syllabus .row:hover::before{transform:scaleX(1)}
.syllabus .num{
  font-family:var(--serif);font-size:.85rem;color:var(--muted-2);flex:none;width:1.9rem;
  font-variant-numeric:tabular-nums;transition:color 300ms;
}
.syllabus .ico-box{
  flex:none;width:38px;height:38px;border-radius:11px;display:grid;place-items:center;
  border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--gold);
  transition:border-color 300ms,background 300ms,transform 320ms var(--ease);
}
.syllabus .lbl{
  font-size:1rem;line-height:1.4;letter-spacing:var(--tr-body);color:var(--muted);
  transition:color 300ms;
  /* without this the label refuses to wrap and overruns its column */
  min-width:0;overflow-wrap:anywhere;
}
.syllabus .row:hover .num{color:var(--gold)}
.syllabus .row:hover .ico-box{border-color:rgba(255,206,133,.45);background:rgba(255,206,133,.1);transform:translateY(-2px)}
.syllabus .row:hover .lbl{color:var(--text)}

/* ============================================================================
   Ledger: the "what the school puts behind you" rows, made interactive
   ========================================================================= */
.ledger{border-top:1px solid var(--line)}
.ledger .row{
  position:relative;display:grid;gap:var(--s-2) var(--s-8);
  padding:var(--s-8) 0 var(--s-8) var(--s-4);border-bottom:1px solid var(--hair);
  /* transform, not padding: animating padding thrashes layout on every frame */
  transition:transform 340ms var(--ease);
}
@media (min-width:780px){
  /* The numeral track is a fixed measure, not `auto`. As `auto` it sized to the
     glyphs, and "01" is narrower than "06" in some faces, so the terms sat on a
     ragged left edge. Fixed, every term starts on the same vertical. */
  .ledger .row{grid-template-columns:4.6rem minmax(0,17rem) minmax(0,1fr);align-items:start}
}
/* No rule down the left edge. A coloured vertical border is the cheapest
   available way to say "this one", it is the same gesture as the side stripe
   this design system bans everywhere else, and on hover it read as a scrollbar
   appearing. Three things carry the hover instead, none of them a border:

   1. a warm wash that falls off from the left, so the emphasis still starts at
      the same edge the rule used to occupy, without drawing an edge;
   2. an accent rule that draws itself in under the term, left to right. Same
      idiom as the nav links and the open-house headline, so the page has one
      way of saying this rather than two;
   3. the numeral and the term take the accent colour.

   The row still does not move. Sliding it clipped the last column. */
.ledger .row:hover{
  background:linear-gradient(90deg,
    rgba(255,206,133,.075) 0%,
    rgba(255,206,133,.028) 22%,
    rgba(255,255,255,.012) 55%,
    rgba(255,255,255,0) 100%);
}
.ledger dt{position:relative;display:inline-block}
.ledger dt::after{
  content:"";position:absolute;left:0;right:0;bottom:-5px;height:1px;
  background:linear-gradient(90deg,var(--gold),rgba(255,206,133,0));
  transform:scaleX(0);transform-origin:left center;
  transition:transform 360ms var(--ease);
}
.ledger .row:hover dt::after{transform:scaleX(1)}
@media (prefers-reduced-motion: reduce){
  .ledger dt::after{transition:none}
}
.ledger .row:hover dt{color:var(--gold)}
.ledger .row:hover .idx{color:var(--gold)}
/* The index numerals carry the accent, which was the only place in this section
   the second brand colour appeared at all. */
/* A large ghosted figure rather than a small caption numeral. This is the whole
   change in weight: the section reads as an editorial index instead of a thin
   table, without becoming a card grid, which rule 3 rules out here because these
   six entries have very different text lengths and would stretch to equal height
   with dead space in the short ones.
   Ghosted, never glowing: a low-alpha fill, no coloured shadow. */
.ledger .idx{
  font-family:var(--serif);
  font-size:clamp(2rem,3.4vw,3rem);line-height:.9;
  color:rgba(255,206,133,.19);
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;
  padding-top:0;margin-top:-.12em;
  transition:color 300ms var(--ease);
}
.ledger .row:hover .idx{color:rgba(255,206,133,.5)}
.ledger dt{font-size:1.2rem;font-weight:500;letter-spacing:-.014em;color:var(--text);margin:0;
  transition:color 300ms}
.ledger dd{margin:0;font-size:.98rem;line-height:1.66;letter-spacing:var(--tr-body);
  color:var(--muted);max-width:60ch}

/* the photo band: soften and darken so the line always reads */
.strip img{filter:saturate(.78) contrast(1.02) brightness(.55) blur(6px);transform:scale(1.09)}
.strip::after{background:
  linear-gradient(90deg,rgba(20,17,24,.94) 0%,rgba(20,17,24,.78) 45%,rgba(20,17,24,.6) 100%),
  linear-gradient(180deg,rgba(20,17,24,.5),transparent 30%,transparent 70%,rgba(20,17,24,.5))}
.strip .line{text-shadow:0 2px 24px rgba(0,0,0,.5)}

/* ============================================================================
   Tour picker: calendar tiles rather than a row of pills
   ========================================================================= */
.daygrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:10px;margin:0 0 var(--s-6)}
.day{
  position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:14px 10px 12px;cursor:pointer;border-radius:14px;
  border:1px solid var(--line);background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.02));
  transition:border-color 220ms,background 220ms,transform 220ms var(--ease);
}
.day:hover{border-color:var(--line-strong);transform:translateY(-2px)}
.day .dow{font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted-2)}
.day .dd{font-family:var(--serif);font-size:1.65rem;line-height:1.05;color:var(--text);
  font-variant-numeric:tabular-nums}
.day .mon{font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted-2)}
.day[aria-pressed="true"]{background:var(--gold);border-color:var(--gold)}
.day[aria-pressed="true"] .dow,.day[aria-pressed="true"] .mon{color:rgba(29,26,36,.7)}
.day[aria-pressed="true"] .dd{color:var(--ink)}
.day:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* The one open-house chip mixed into an otherwise all-Sunday grid: a border
   in the brand colour and a small tag are enough to mark it as a different
   kind of date without a second grid or a separate flow. */
/* The tag sits IN the tile, not straddling its top edge.

   It used to be position:absolute, centred with translateX(-50%) and
   white-space:nowrap, which meant that the moment the label was wider than the
   tile it hung out over both borders: the rule ran straight through the words
   and the leading letters fell outside the tile entirely. A nowrap absolute
   label centred on a box narrower than itself always overflows; there is no
   width at which that arrangement is safe.
   In flow it cannot overflow, the tile grows for it if it ever needs two lines,
   and the gold border alone still carries "this date is different". */
.day.oh{border-color:rgba(255,206,133,.45);padding-top:10px}
.day.oh .ohtag{
  display:block;margin:0 0 3px;max-width:100%;
  font-size:.54rem;letter-spacing:.04em;line-height:1.25;text-transform:uppercase;
  color:var(--gold);white-space:normal;overflow-wrap:anywhere;
}
.day.oh[aria-pressed="true"] .ohtag{color:rgba(29,26,36,.7)}

.timegrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(102px,1fr));gap:10px;margin:0 0 var(--s-5)}
.slot{
  padding:12px 10px;border-radius:12px;cursor:pointer;font-size:.94rem;min-height:46px;
  border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text);
  font-variant-numeric:tabular-nums;
  transition:border-color 200ms,background 200ms,color 200ms,transform 200ms var(--ease);
}
.slot:hover{border-color:var(--line-strong);transform:translateY(-2px)}
.slot[aria-pressed="true"]{background:var(--gold);border-color:var(--gold);color:var(--ink);font-weight:500}
.slot:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

.chosen{
  font-size:.9rem;color:var(--gold);margin:0 0 var(--s-5);min-height:1.2em;
  opacity:0;transform:translateY(-4px);transition:opacity 300ms var(--ease),transform 300ms var(--ease);
}
.chosen.on{opacity:1;transform:none}

/* ============================================================================
   Review marquee: two rows drifting in opposite directions, edges faded out.
   Pauses on hover and on focus-within so a reader can actually finish a card,
   and collapses to a plain swipeable row when reduced motion is requested.
   ========================================================================= */
.marquee{position:relative;display:grid;gap:var(--s-4);overflow:hidden;
  margin-inline:calc(var(--gutter) * -1);padding-block:var(--s-1)}
@media (min-width:700px){
  .marquee{
    -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
    mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
  }
}
.marquee .lane{display:flex;gap:var(--s-4);width:max-content;will-change:transform;
  padding-block:6px} /* room for the hover lift, so a raised card is not clipped by the sibling lane */
.marquee .lane.ltr{animation:drift-l var(--dur-lane,72s) linear infinite}
.marquee .lane.rtl{animation:drift-r var(--dur-lane,86s) linear infinite}
@keyframes drift-l{from{transform:translate3d(0,0,0)}   to{transform:translate3d(-50%,0,0)}}
@keyframes drift-r{from{transform:translate3d(-50%,0,0)}to{transform:translate3d(0,0,0)}}
/* Pause is per lane: the row under the pointer holds still so it can be read,
   the other row keeps drifting. Keyboard focus pauses its own lane the same way. */
.marquee .lane:hover,.marquee .lane:focus-within{animation-play-state:paused}

.marquee .rv{
  flex:0 0 auto;width:clamp(268px,26vw,340px);
  display:flex;flex-direction:column;gap:11px;
  position:relative;border:0;border-radius:18px;padding:22px 22px 18px;
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.018) 62%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.07),
             inset 0 0 0 1px rgba(255,255,255,.05);
  transition:background 260ms,box-shadow 260ms,transform 260ms var(--ease);
}
/* An oversized serif quote mark, drawn once per card. Ornament, so hidden from
   assistive tech via the empty-alt idiom: it is a ::before, nothing to announce. */
.marquee .rv::before{
  content:"\201C";position:absolute;top:8px;right:16px;
  font-family:var(--serif);font-size:3.2rem;line-height:1;
  color:rgba(255,206,133,.14);pointer-events:none;
}
.marquee .rv:hover{transform:translateY(-4px);
  background:linear-gradient(180deg,rgba(255,255,255,.085),rgba(255,255,255,.03) 62%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.1),
             inset 0 0 0 1px rgba(255,255,255,.08),
             0 14px 34px -18px rgba(0,0,0,.55)}
.marquee .rv .stars{color:var(--gold);letter-spacing:.18em;font-size:.68rem}
.marquee .rv p{margin:0;font-size:.92rem;line-height:1.66;letter-spacing:var(--tr-body);color:#ece4d9}
.marquee .rv .who{display:flex;align-items:center;gap:10px;font-size:.74rem;color:var(--muted-2);
  margin-top:auto;padding-top:12px;border-top:1px solid rgba(255,255,255,.06)}
.marquee .rv .av{
  width:30px;height:30px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:linear-gradient(160deg,rgba(255,206,133,.2),rgba(255,206,133,.08));
  box-shadow:inset 0 0 0 1px rgba(255,206,133,.3);
  color:var(--gold);font-size:.7rem;font-weight:600;
  overflow:hidden;
}
.marquee .rv .av img{width:100%;height:100%;object-fit:cover;display:block;border-radius:50%}
.marquee .rv .who b{color:#f1eae0;font-weight:500}
.marquee .rv .g{width:13px;height:13px;flex:none;margin-left:auto;opacity:.85}

@media (prefers-reduced-motion: reduce){
  .marquee{overflow-x:auto;scroll-snap-type:x mandatory;padding-inline:var(--gutter);
    scrollbar-width:none}
  .marquee::-webkit-scrollbar{display:none}
  /* .lane alone loses to .lane.ltr on specificity, so the ltr row kept
     drifting under reduced motion. Name both variants. */
  .marquee .lane.ltr,.marquee .lane.rtl{animation:none}
  .marquee .lane.rtl{display:none}
  .marquee .rv{scroll-snap-align:start}
}
@media (max-width:560px){
  .marquee .rv{width:clamp(240px,78vw,290px);padding:17px}
  .marquee .rv p{font-size:.9rem}
}

/* aggregate line above the marquee */
.rating-head{display:flex;align-items:center;gap:var(--s-4);flex-wrap:wrap;margin:0 0 var(--s-8)}
.rating-head .big{font-family:var(--serif);font-size:2.6rem;line-height:1;color:var(--gold);font-variant-numeric:tabular-nums}
.rating-head .meta2{font-size:.9rem;color:var(--muted);line-height:1.5}
.rating-head .meta2 .s{display:block;color:var(--gold);letter-spacing:.14em;font-size:.82rem}
.rating-head a{color:var(--muted);text-underline-offset:.24em}
.rating-head a:hover{color:var(--gold)}

/* ============================================================================
   Versus: the before/after that replaced three stacked paragraphs. Same claims,
   scannable in one pass instead of read line by line.
   ========================================================================= */
.versus{display:grid;gap:var(--s-4);margin:0 0 var(--s-8)}
@media (min-width:760px){
  .versus{grid-template-columns:1fr 1fr;align-items:stretch}
}
.versus .side{
  border:1px solid var(--line);border-radius:var(--radius);padding:var(--s-8);
  background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.015));
}
.versus .side.win{
  border-color:rgba(255,206,133,.3);
  background:linear-gradient(180deg,rgba(255,206,133,.075),rgba(255,255,255,.02));
}
.versus .tagline{
  margin:0 0 var(--s-5);font-size:.72rem;letter-spacing:var(--tr-meta);text-transform:uppercase;
  color:var(--muted-2);
}
.versus .win .tagline{color:var(--gold)}
.pts{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-4)}
.pts li{display:flex;gap:var(--s-3);align-items:flex-start;font-size:1rem;line-height:1.5;
  letter-spacing:var(--tr-body);color:var(--muted)}
.versus .win .pts li{color:var(--text)}
.pts .mk{flex:none;width:20px;height:20px;display:grid;place-items:center;margin-top:1px}
.pts .mk svg{width:16px;height:16px}
.versus .side:not(.win) .pts .mk{color:rgba(246,240,232,.42)}
.versus .win .pts .mk{color:var(--gold)}
.versus .side:not(.win) li span:last-child{text-decoration:line-through;
  text-decoration-color:rgba(255,255,255,.26);text-decoration-thickness:1px;
  text-underline-offset:0}

/* ============================================================================
   Confirmation hero. A receipt should feel like an arrival, not a dead end.
   ========================================================================= */
/* The confirmation headline is the first thing on the page and it sat behind a
   full `--band` of air, up to 184px, on top of the header's own height: a void
   between the bar and the one sentence the visitor came back to read. A band is
   the rhythm BETWEEN sections; it is not an opening. Scoped to the section that
   actually contains the confirmation, so no other `.sec` on any page moves. */
.sec:has(.confirm){padding-top:var(--s-12)}
@media (min-width:1080px){ .sec:has(.confirm){padding-top:var(--s-16)} }
.confirm{position:relative}
.confirm .glow{
  position:absolute;left:-14%;top:-38%;width:58%;aspect-ratio:1;pointer-events:none;z-index:-1;
  background:radial-gradient(circle,rgba(255,206,133,.16),transparent 66%);
}
.tick-big{ /* Law 16: the tick draws itself bare, no disc and no ring behind it */
  position:relative;display:grid;place-items:center;margin:0 0 var(--s-6);
}
.tick-big svg{width:34px;height:34px;fill:none;stroke:var(--gold);stroke-width:2.2;
  stroke-linecap:round;stroke-linejoin:round}
.tick-big svg path{stroke-dasharray:32;stroke-dashoffset:32;animation:draw 620ms var(--ease) 180ms forwards}
@keyframes draw{to{stroke-dashoffset:0}}
@media (prefers-reduced-motion: reduce){
  .tick-big svg path{animation:none;stroke-dashoffset:0}
}

/* three-stage status strip */
.status{display:grid;gap:var(--s-3);margin:var(--s-8) 0 0;padding:0;list-style:none}
@media (min-width:620px){ .status{grid-template-columns:repeat(3,1fr);gap:var(--s-2)} }
.status li{
  position:relative;display:flex;align-items:center;gap:10px;
  padding:13px 15px;border-radius:12px;border:1px solid var(--line);
  background:rgba(255,255,255,.03);font-size:.84rem;color:var(--muted-2);line-height:1.35;
}
.status li .dot{
  width:9px;height:9px;border-radius:50%;flex:none;background:var(--line-strong);
}
.status li.done{border-color:rgba(255,206,133,.36);background:rgba(255,206,133,.08);color:var(--text)}
.status li.done .dot{background:var(--gold)}
.status li.now .dot{background:var(--gold);animation:pulse 2.2s ease-in-out infinite}
.status li.now{color:var(--muted)}
@media (prefers-reduced-motion: reduce){ .status li.now .dot{animation:none} }

/* plan card: an icon per row and a confirmed badge in the header */
.plan .hd{display:flex;align-items:center;justify-content:space-between;gap:var(--s-4)}
.plan .hd .stamp{
  display:inline-flex;align-items:center;gap:7px;text-transform:none;letter-spacing:0;
  font-size:.72rem;color:var(--gold);
}
.plan .hd .stamp svg{width:13px;height:13px}
.plan .r .k{display:flex;align-items:center;gap:10px}
.plan .r .k svg{width:16px;height:16px;color:var(--gold-dim);flex:none}

/* ============================================================================
   Confirmation hero, sizing pass. This page is a receipt, not a sales hero:
   the headline steps down from the landing scale and the mark sits beside it,
   optically aligned to the first line rather than stacked above it.
   ========================================================================= */
.headline-row{display:flex;align-items:flex-start;gap:var(--s-4);margin:0 0 var(--s-5)}
.headline-row .h1{
  margin:0;
  font-size:clamp(1.95rem,3.6vw,3rem);
  line-height:1.06;
}
.confirm .tick-big{
  flex:none;margin:0;
  /* nudge onto the cap height of the first line instead of the line box top */
  margin-top:clamp(2px,0.5vw,7px);
}
.confirm .tick-big svg{width:30px;height:30px;stroke-width:2.4}
/* Same measured problem as the funnel steps: the 46ch cap resolved to 432px
   inside a 660px column, so the lede stopped well short of the stage chips and
   the plan card below it and the whole block read as ragged. The column is
   already the measure here. */
.confirm .lede{margin:0 0 var(--s-2);max-width:none}
.confirm .status{margin-top:var(--s-6)}
.confirm .glow{top:-30%;width:46%}
@media (max-width:560px){
  .headline-row{gap:var(--s-3)}
  .confirm .tick-big svg{width:26px;height:26px}
}

/* social icons in the footer. Bare glyphs (Law 16 + Law 1, 2026-09-30; the
   bordered 40px tiles came off, matching site/chrome.css). Each link is a 44px
   hit area with nothing painted in it, and the row is pulled left by the pad
   so the first glyph still starts on the column edge. */
.soc.icons{gap:0;margin-left:-12px}
.soc.icons a{
  width:44px;height:44px;border-radius:11px;display:inline-grid;place-items:center;
  color:var(--muted);
  transition:color 180ms;
}
.soc.icons a svg{width:20px;height:20px;flex:none;transition:transform 220ms var(--ease)}
.soc.icons a:hover{color:var(--gold)}
.soc.icons a:hover svg{transform:translateY(-2px)}
@media (prefers-reduced-motion: reduce){ .soc.icons a svg{transition:none} }

/* ============================================================================
   Preloader. Self-removing: `.gone` is applied by JS on load and by a CSS
   animation at 2.6s regardless, so it can never strand the page behind it.
   ========================================================================= */
#pre{
  position:fixed;inset:0;z-index:200;
  background:radial-gradient(120vw 90vh at 50% 38%, #221d29 0%, var(--ink-deep) 62%);
  display:grid;place-items:center;
  animation:pre-fail-safe 0s linear 2.6s forwards;
}
#pre.gone{opacity:0;visibility:hidden;transform:scale(1.015);
  transition:opacity 620ms var(--ease),transform 800ms var(--ease),visibility 620ms}
@keyframes pre-fail-safe{to{opacity:0;visibility:hidden}}

#pre .mark{
  position:relative;display:flex;flex-direction:column;align-items:center;
  gap:var(--s-8);padding:0 24px;
}
/* a slow warm bloom behind the mark, the same light source the page uses */
#pre .mark::before{
  content:"";position:absolute;left:50%;top:34%;transform:translate(-50%,-50%);
  width:320px;height:320px;border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(255,206,133,.13),transparent 64%);
  animation:pre-bloom 2.6s ease-in-out infinite;
}
@keyframes pre-bloom{0%,100%{opacity:.55;transform:translate(-50%,-50%) scale(.94)}
  50%{opacity:1;transform:translate(-50%,-50%) scale(1.06)}}

/* ---- the gyroscope -------------------------------------------------------
   Three hoops on genuinely different 3D axes, not three flat circles faking
   depth. Each keyframe writes the full transform list in the same order, so the
   engine interpolates the rotateZ term and holds the tilt.
   ------------------------------------------------------------------------ */
#pre .gyro{
  position:relative;width:clamp(148px,34vw,196px);aspect-ratio:1;
  display:grid;place-items:center;
  perspective:560px;transform-style:preserve-3d;
  opacity:0;animation:pre-in 760ms var(--ease) 60ms forwards;
}
#pre .ring{
  position:absolute;inset:0;border-radius:50%;border:1px solid rgba(255,206,133,.34);
  transform-style:preserve-3d;backface-visibility:visible;
}
#pre .r1{animation:gyro-a 5.4s linear infinite}
#pre .r2{inset:11%;border-color:rgba(255,206,133,.20);animation:gyro-b 7.8s linear infinite}
#pre .r3{inset:22%;border-style:dashed;border-color:rgba(246,240,232,.13);
  animation:gyro-c 13s linear infinite}
@keyframes gyro-a{from{transform:rotateZ(0deg) rotateX(72deg)}
                    to{transform:rotateZ(360deg) rotateX(72deg)}}
@keyframes gyro-b{from{transform:rotateZ(360deg) rotateY(70deg)}
                    to{transform:rotateZ(0deg) rotateY(70deg)}}
@keyframes gyro-c{from{transform:rotateX(58deg) rotateY(0deg)}
                    to{transform:rotateX(58deg) rotateY(360deg)}}

/* ---- the tooth traces itself in, under a scan line ---------------------- */
#pre .glyph{position:relative;width:52%;height:52%;overflow:visible}
#pre .reticle{stroke:rgba(255,206,133,.42);opacity:0;
  animation:pre-in 600ms var(--ease) 520ms forwards}
#pre .tooth{stroke:var(--text);stroke-dasharray:210;stroke-dashoffset:210;
  animation:pre-trace 1.5s cubic-bezier(.5,.05,.2,1) 180ms forwards}
#pre .pulp{stroke:var(--gold);opacity:0;
  animation:pre-in 700ms var(--ease) 900ms forwards}
@keyframes pre-trace{to{stroke-dashoffset:0}}

#pre .scan{stroke:var(--gold);opacity:.85;
  animation:pre-scan 2.3s cubic-bezier(.5,0,.5,1) 700ms infinite}
@keyframes pre-scan{
  0%{transform:translateY(-13px);opacity:0}
  12%{opacity:.9}
  88%{opacity:.9}
  100%{transform:translateY(13px);opacity:0}
}

@keyframes pre-in{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}

/* a hairline that fills rather than a shuttle that slides: it reads as loading,
   not as an indeterminate spinner pretending to be progress */
#pre .bar{
  position:relative;width:clamp(120px,42vw,168px);height:1px;
  background:rgba(255,255,255,.12);overflow:hidden;
  opacity:0;animation:pre-in 700ms var(--ease) 340ms forwards;
}
#pre .bar i{
  display:block;height:100%;width:100%;background:var(--gold);
  transform:scaleX(0);transform-origin:left;
  animation:pre-fill 1.5s cubic-bezier(.35,.9,.3,1) 380ms forwards;
}
@keyframes pre-fill{
  0%{transform:scaleX(0)}
  62%{transform:scaleX(.72)}
  100%{transform:scaleX(1)}
}

/* Nothing spins, nothing traces, and the mark is complete on the first frame. */
@media (prefers-reduced-motion: reduce){
  #pre .mark::before{animation:none;opacity:.8}
  #pre .gyro,#pre .reticle,#pre .pulp,#pre .bar{animation:none;opacity:1}
  #pre .r1{transform:rotateZ(28deg) rotateX(72deg)}
  #pre .r2{transform:rotateZ(300deg) rotateY(70deg)}
  #pre .r3{transform:rotateX(58deg) rotateY(40deg)}
  #pre .ring,#pre .scan{animation:none}
  #pre .scan{opacity:0}
  #pre .tooth{animation:none;stroke-dashoffset:0}
  #pre .bar i{animation:none;transform:scaleX(1)}
  #pre.gone{transition:opacity 120ms linear;transform:none}
}

/* ============================================================================
   Funnel fit. One question per screen only works if the screen actually holds
   it, so the funnel gets its own tighter scale and two height breakpoints. The
   tour step is the tallest and drives these numbers.
   ========================================================================= */
/* Start, not centre. Centring meant every step began at a different height
   depending on how much content it had, so the heading jumped between
   screens and a short step like the summary opened a 140px hole under the
   mark. Aligning to start also can only ever reduce the height a step
   needs, so it cannot threaten the no-scroll rule. */
body:not(.page) .shell{align-content:start;padding-block:clamp(4px,1vh,12px)}
body:not(.page) .shell{align-items:start}
body:not(.page) h1{font-size:clamp(1.8rem,3.6vw,2.95rem);margin-bottom:var(--s-3)}
body:not(.page) .help{font-size:clamp(1rem,1.4vw,1.14rem);line-height:1.45;margin-bottom:var(--s-5)}
body:not(.page) .kicker{margin-bottom:var(--s-3)}
body:not(.page) .opts{gap:8px;margin-bottom:var(--s-4)}
body:not(.page) .opt{min-height:52px;padding:12px 16px}
body:not(.page) .field{margin-bottom:var(--s-4)}
body:not(.page) .sublabel{margin-bottom:8px}
body:not(.page) .daygrid,body:not(.page) .timegrid{gap:8px;margin-bottom:var(--s-4)}
body:not(.page) .hintkeys{margin-top:var(--s-3)}
body:not(.page) footer.legal{padding-block:var(--s-5)}

/* short viewports: laptops at 1440x760, phones in landscape, browser chrome open */
@media (max-height:820px){
  body:not(.page) h1{font-size:clamp(1.6rem,2.9vw,2.25rem)}
  body:not(.page) .help{margin-bottom:var(--s-4);font-size:1rem}
  body:not(.page) .opt{min-height:48px;padding:10px 15px}
  body:not(.page) .opt .t small{display:none}
  body:not(.page) .day{padding:10px 8px}
  body:not(.page) .day .dd{font-size:1.35rem}
  body:not(.page) .slot{padding:9px 8px;min-height:40px;font-size:.88rem}
  /* The tour step is the tallest in the funnel and the only one that still
     overflows a 760px-tall window AFTER the trims above.

     HONEST NUMBERS: these four rules took it from 45px to 44px. They are kept
     because tighter grids are right at this height, not because they fixed it.
     **The tour step still overflows: 44px at 1440x760, 36px at 1920x1000, 35px
     at 390x844.** That is PRE-EXISTING and not the footer — measured by
     restoring the previous footer at runtime on the same step, where the same
     overflow was 243px. Design-system rule 5 is therefore still false for this
     one step and the map says so. Whatever fixes it is in the tour step's own
     content, not in spacing. */
  body:not(.page) .daygrid{gap:8px;margin-bottom:var(--s-4)}
  body:not(.page) .timegrid{gap:8px;margin-bottom:var(--s-3)}
  body:not(.page) .day.oh{padding-top:8px}
  body:not(.page) .day.oh .ohtag{font-size:.5rem;margin-bottom:2px}
  body:not(.page) .reel{height:min(58vh,430px)}
  body:not(.page) .stats div{padding:14px 14px}
  body:not(.page) .shot{aspect-ratio:16/10}
  /* The footer hide is NOT here. A short viewport is not the same case as a
     narrow one, and it needs the `.compact` class to survive the GHL rewrite.
     Both live in the compact-footer block further down. */
}
@media (max-height:660px){
  body:not(.page) .rail{display:none}
  body:not(.page) .shell{grid-template-columns:1fr}
  body:not(.page) .hintkeys{display:none}
  body:not(.page) h1{font-size:clamp(1.4rem,2.4vw,1.85rem)}
  body:not(.page) .help{display:none}
  body:not(.page) .daygrid{grid-template-columns:repeat(auto-fit,minmax(70px,1fr))}
  body:not(.page) .timegrid{grid-template-columns:repeat(auto-fit,minmax(88px,1fr))}
}

/* The summary carries the most content of any step, so it gets its own
   compression before the generic height rules kick in. */
body:not(.page) .plan{margin-bottom:var(--s-4)}
body:not(.page) .plan .hd{padding:10px 18px;font-size:.66rem}
body:not(.page) .plan dl{padding:2px 18px 10px}
body:not(.page) .plan .r{padding:9px 0}
body:not(.page) .plan dt{font-size:.8rem}
body:not(.page) .plan dd{font-size:.9rem}
body:not(.page) .plan .note{padding:11px 18px;font-size:.8rem;line-height:1.5}
body:not(.page) .field textarea{min-height:56px;font-size:.94rem}
body:not(.page) .check{margin-bottom:var(--s-3);font-size:.84rem}
body:not(.page) .consent{margin-bottom:var(--s-3);font-size:.75rem}
/* In the funnel the step column IS the measure (544px on desktop), so the
   ch-based caps tuned for the landing page's larger type fight it: at .72rem a
   52ch cap resolves to ~298px, barely half the plan card, and every text block
   wrapped early and read as ragged against a full-width card. Measured, not
   guessed: card 544, help 350, check 321, consent 310, fine 298. Let them fill
   the column so every left AND right edge lines up with the card. */
body:not(.page) .help,
body:not(.page) .check,
body:not(.page) .consent,
body:not(.page) .consent-fine{max-width:none}

@media (max-height:900px){
  body:not(.page) #stage .plan .r{padding:7px 0}
  body:not(.page) .field textarea{min-height:46px}
}
@media (max-height:820px){
  body:not(.page) .plan .note{display:none}   /* marketing line, the recap above covers it */
  body:not(.page) .field textarea{min-height:40px}
  /* The contact consent is a disclosure and never gets hidden to save space,
     only tightened. The certify checkbox says something different. */
  body:not(.page) .consent{font-size:.72rem;line-height:1.4;margin-bottom:var(--s-2)}
}

/* five optional fields on one screen: two up on wide, stacked on narrow */
@media (min-width:620px){
  body:not(.page) .fieldgrid{display:grid;grid-template-columns:1fr 1fr;column-gap:var(--s-6)}
}

/* At 660px and under the summary is still the one screen that will not fit:
   the plan recap collapses to a compact two-column list. */
@media (max-height:660px){
  body:not(.page) .plan dl{padding:2px 14px 8px}
  body:not(.page) .plan .r{padding:5px 0}
  body:not(.page) .plan dt{font-size:.75rem}
  body:not(.page) .plan dd{font-size:.84rem}
  body:not(.page) .plan .hd{padding:8px 14px}
  body:not(.page) .field textarea{display:none}   /* optional note, offered again by text */
  body:not(.page) .check{font-size:.8rem;margin-bottom:var(--s-2)}
  body:not(.page) .btn{min-height:44px;padding:11px 22px;font-size:.92rem}
}

/* ============================================================================
   Funnel on a phone. The rail is supporting content, not the question, and on
   a narrow screen it stacks underneath and pushes every step past the fold.
   ========================================================================= */
@media (max-width:900px){
  body:not(.page) .rail{display:none}
  body:not(.page) .shell{grid-template-columns:1fr;min-height:100dvh;
    padding-bottom:calc(20px + env(safe-area-inset-bottom))}
  /* The footer STAYS on a phone: it carries the number and the policy links on
     the same page that collects the phone number. It stacks to one column
     instead (see the compact-footer block), which is what the dead rule this
     replaces was always trying to do. */
  body:not(.page) .stage{max-width:none}
  /* Vertically centred on a phone. This is deliberately against the usual rule
     for multi-step forms, which is to align to the top so the heading lands at
     the same height on every step. Centring is what was asked for and it closes
     the dead space under the short steps; the cost is that the heading now sits
     at a different height on a short step than on a long one. Steps taller than
     the viewport fall back to top alignment on their own, because align-content
     centre does not clip an overflowing grid. */
  body:not(.page) .shell{align-content:center;align-items:center}
}
@media (max-width:640px){
  body:not(.page) h1{font-size:clamp(1.5rem,7vw,1.95rem)}
  body:not(.page) .help{font-size:.92rem;margin-bottom:var(--s-4)}
  body:not(.page) .opt{min-height:52px}
  /* the two longest steps, matched to the height rules above */
  body:not(.page) .daygrid{grid-template-columns:repeat(3,1fr);gap:7px}
  body:not(.page) .day{padding:9px 6px}
  body:not(.page) .day .dd{font-size:1.3rem}
  body:not(.page) .timegrid{grid-template-columns:repeat(4,1fr);gap:6px}
  body:not(.page) .slot{padding:8px 4px;min-height:40px;font-size:.78rem;text-align:center;
    justify-content:center}
  body:not(.page) .sublabel{margin-bottom:6px;font-size:.7rem}
  body:not(.page) .chosen{font-size:.82rem;margin-bottom:var(--s-3)}
  body:not(.page) .plan .r{padding:7px 0}
  body:not(.page) .plan dt{font-size:.78rem}
  body:not(.page) .plan dd{font-size:.86rem}
  body:not(.page) .plan .note{display:none}
  body:not(.page) .field textarea{min-height:44px}
  body:not(.page) .fieldgrid .field.stacked{margin-bottom:var(--s-3)}
}

/* The funnel shell asked for a full 100dvh on top of the header, which pushed
   every step 58px past the fold. Let the body own the viewport and give the
   shell whatever is left. */
body:not(.page){display:flex;flex-direction:column;min-height:100dvh}
body:not(.page) .shell{flex:1 1 auto;min-height:0}
@media (max-width:900px){
  body:not(.page) .shell{min-height:0}
}

/* A max-width block does not centre itself. Anything inside a centred container
   needs the auto margins explicitly, or it sits flush left while its text reads
   centred, which is exactly how the finale lede drifted off-axis. */
.finale .lede,
.finale .microcopy,
.finale p.body,
.center .lede,
.center .microcopy,
.center p.body{margin-inline:auto}
.finale .microcopy{max-width:52ch}

/* ============================================================================
   Objections. Was a horizontal rail of equal-height cards, which clipped the
   longest answer and made four peer items feel like a carousel. Now a plain
   two-column read: hairline rules, no boxes, each answer as long as it needs.
   ========================================================================= */
.objgrid{display:grid;gap:0}
@media (min-width:860px){
  .objgrid{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:var(--s-16)}
}
.objgrid .qa{
  padding:var(--s-8) 0;border-top:1px solid var(--line);min-width:0;
}
@media (min-width:860px){
  /* the first two sit on the same top rule */
  .objgrid .qa:nth-child(-n+2){border-top:1px solid var(--line)}
}
.objgrid .q{
  position:relative;margin:0 0 var(--s-4);padding-left:var(--s-6);
  font-family:var(--serif);font-size:1.22rem;line-height:1.34;color:var(--text);
}
.objgrid .q::before{
  content:"";position:absolute;left:0;top:.55em;width:14px;height:1px;background:var(--gold);
}
.objgrid .a{
  margin:0;padding-left:var(--s-6);
  font-size:.97rem;line-height:1.7;letter-spacing:var(--tr-body);color:var(--muted);
  max-width:56ch;
}

/* ============================================================================
   Tuition: a price table, not a second tick list. The two columns previously
   ran the same component and read as one repeated thought.
   ========================================================================= */
.prices{margin:var(--s-8) 0 0;border-top:1px solid var(--line)}
.prices .row{
  display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-6);
  padding:var(--s-5) 0;border-bottom:1px solid var(--hair);min-width:0;
}
.prices dt{margin:0;min-width:0}
.prices dt b{display:block;font-size:1rem;font-weight:400;color:var(--text);letter-spacing:-.005em}
.prices dt small{display:block;font-size:.8rem;color:var(--muted-2);margin-top:3px;line-height:1.45}
.prices dd{
  margin:0;flex:none;font-family:var(--serif);font-size:1.28rem;line-height:1.1;color:var(--gold);
  font-variant-numeric:tabular-nums;text-align:right;max-width:11ch;
}

/* funnel footer: two sides and a base, never the full site footer.

   It was a wrapping flex row of four loose items, which put the three short ones
   across the width and dropped the long A2P disclosure onto a line of its own,
   centred, under a left-aligned column. Mixed alignment inside one component
   reads as an accident, because it is one.

   A grid instead: the accreditation line owns the left, the ways to reach ADATS
   own the right, and the disclosure spans the full width underneath its own
   hairline. Nothing is centred, so every left edge in the footer lines up.
   Spacing steps are from the real scale, which SKIPS 7, 9 and 11 (TRAP 66).

   MOBILE-FIRST, and that is load-bearing rather than a style preference. The
   two-column form is the exception and it now lives in a min-width query
   directly below, so no later rule and no rewritten selector can restore two
   columns at a width that cannot hold them. It was written the other way round
   -- two columns in the base, one column in a `max-width:700px` override placed
   ~1800 lines EARLIER in this file -- and at equal specificity the base won at
   every width. The `auto` track then took its max-content width, the `1fr`
   track was left 1.45px, and the accreditation line rendered one letter per
   line, 2758px tall, on the live phone layout. TRAP 93. */
footer.legal.compact{
  display:grid;grid-template-columns:minmax(0,1fr);
  align-items:baseline;column-gap:var(--s-8);row-gap:var(--s-3);
  padding-block:var(--s-5);border-top:1px solid var(--hair);font-size:.78rem;
}
/* Two columns only once there is width for both. */
@media (min-width:701px){
  footer.legal.compact{grid-template-columns:minmax(0,1fr) auto;row-gap:var(--s-4)}
  footer.legal.compact .fmeta{justify-content:flex-end}
}
/* Short AND wide is the no-scroll case the hide was written for: a laptop at
   1440x760. A phone is narrow, scrolls anyway, and keeps its footer.

   `.compact` is on this selector on purpose. The GHL build rewrites
   `body:not(.page)` to `#adats-fn`, which SPENDS the `:not(.page)` class, so a
   `body:not(.page) footer.legal` hide ships as `#adats-fn footer.legal` (1,1,1)
   and loses to `#adats-fn footer.legal.compact` (1,2,1). That is why the live
   funnel showed a footer the repo believed it had hidden. Matching the class
   count makes the rule survive the rewrite; sitting AFTER the base rule makes
   the resulting tie resolve the right way. TRAP 94. */
@media (min-width:901px) and (max-height:820px){
  body:not(.page) footer.legal.compact{display:none}
}
/* `:not(.fsms)` is load-bearing. The disclosure IS a <p>, so a bare
   `footer.legal.compact p{max-width:62ch}` beat `footer.compact .fsms
   {max-width:none}` on specificity (0,2,2 against 0,2,1) and held the
   full-width disclosure at 345px, six lines deep. That pushed the compact
   footer to 204px and broke the no-scroll invariant on the tour step at three
   viewports. Measure the element you think you styled. */
footer.legal.compact p:not(.fsms){margin:0;max-width:62ch}
/* The phone and the policy links are one group: both are ways to reach ADATS,
   and splitting them left a hole in the middle of the row.
   Stacked, it keeps its own left edge rather than jumping to centre, so the
   whole footer still reads as one block. It takes the right edge only in the
   two-column form above.
   A stray closing delimiter used to sit after "row." above. It closed this
   comment early and left the next three lines as raw text in the stylesheet, so
   the selector list below started with "Stacked, it keeps ..." and ended with a
   loose delimiter in front of `footer.legal.compact .fmeta`. ONE invalid
   selector in a comma list drops the WHOLE rule, so this block was dead:
   measured on the live step, `.fmeta` computed display:block and gap:normal,
   and the only rules the browser had for it were the two for its `> a`
   children. Nothing errors, nothing warns, and the rule is right there in the
   file. Never write a closing comment delimiter inside a comment — doing that
   is what the build's own guard caught while this note was being written. */
footer.legal.compact .fmeta{
  display:flex;align-items:baseline;justify-content:flex-start;
  gap:var(--s-2) var(--s-5);flex-wrap:wrap;
}
footer.legal.compact .fmeta > a{color:var(--muted);font-variant-numeric:tabular-nums}
footer.legal.compact .fmeta > a:hover{color:var(--gold)}

/* The summary at 900px tall is the one step the compact footer still pushes over. */
@media (max-height:960px){
  body:not(.page) #stage .plan .r{padding:6px 0}
  body:not(.page) .plan .note{font-size:.78rem;padding:9px 18px}
  body:not(.page) .field textarea{min-height:42px}
}

/* ============================================================================
   App section. The store badges say "coming soon" rather than mimicking the
   real App Store and Google Play marks, because there is nothing to download
   yet and a badge that looks like the real one is a claim ADATS cannot back.
   ========================================================================= */
.stores{margin-top:var(--s-8)}
.stores-note{margin:0 0 var(--s-4);font-size:.82rem;color:var(--muted-2);letter-spacing:.01em}
.badges{display:flex;flex-wrap:wrap;gap:var(--s-3)}
/* Scoped to .badges. Unscoped, this rule sat after the funnel's own .badge and
   silently restyled the accreditation chip on apply.html, which is a different
   component that happens to share the class name. */
.badges .badge{
  display:inline-flex;align-items:center;gap:11px;
  border:1px solid var(--line);border-radius:12px;padding:9px 16px 9px 14px;
  background:rgba(255,255,255,.03);color:var(--muted);
}
.badges .badge svg{width:21px;height:21px;flex:none;color:var(--muted)}
.badges .badge u{display:block;text-decoration:none;font-size:.66rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted-2);line-height:1.3}
.badges .badge b{display:block;font-size:.95rem;font-weight:500;color:var(--text);line-height:1.25}

.appframe{display:flex;justify-content:center}
.appframe img{width:min(300px,72%);height:auto;display:block;border-radius:32px;
  box-shadow:0 40px 80px -34px rgba(0,0,0,.85)}
@media (max-width:899px){ .appframe img{width:min(260px,62%)} }

/* ============================================================================
   Floating header. The bar detaches from the top edge, rounds off and rides on
   a heavy blur. It tightens on scroll rather than changing colour abruptly.
   ========================================================================= */
.nav{
  position:sticky;top:clamp(14px,2vw,24px);z-index:50;
  background:transparent;border-bottom:0;
  padding-inline:var(--gutter);
  margin-top:clamp(14px,2vw,22px);      /* clear the announcement strip */
  transition:top 420ms var(--ease);
}
.nav .inner{
  position:relative;
  width:min(calc(var(--shell) - var(--gutter) * 0.5),100%);
  margin:0 auto;height:auto;min-height:68px;
  padding:12px 14px 12px 26px;          /* room to breathe inside the bar */
  border-radius:18px;                    /* softened, not a pill */
  border:0;
  /* Real glass: a top-lit sheen over a translucent ground, a 1px rim picked out
     in white rather than a drawn border, and the drop shadow doing the lifting.
     A flat rgba fill under a blur reads as a grey box, not as glass. */
  /* Thinner ground, heavier blur. Dropping the fill alone turns glass into a
     window; the blur is what keeps whatever is behind it from competing with
     the links, so the two move together. */
  background:
    linear-gradient(178deg,rgba(255,255,255,.095),rgba(255,255,255,.026) 42%,rgba(255,255,255,0) 78%),
    rgba(23,19,28,.20);
  backdrop-filter:blur(34px) saturate(1.9);
  -webkit-backdrop-filter:blur(34px) saturate(1.9);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.11),
    inset 0 0 0 1px rgba(255,255,255,.05),
    0 20px 50px -28px rgba(0,0,0,.92);
  transition:background 420ms var(--ease), box-shadow 420ms var(--ease),
             min-height 420ms var(--ease), padding 420ms var(--ease);
}
.nav.stuck .inner{
  /* Scrolled, real content runs under the bar, so this keeps more ground than
     the floating state does. Still thinner than it was. */
  background:
    linear-gradient(178deg,rgba(255,255,255,.07),rgba(255,255,255,.016) 42%,rgba(255,255,255,0) 78%),
    rgba(19,16,23,.40);
  min-height:62px;padding-block:10px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.08),
    inset 0 0 0 1px rgba(255,255,255,.045),
    0 24px 56px -28px rgba(0,0,0,.95);
}
/* Safari and Firefox without backdrop-filter would show the sheen over nothing,
   so those get an opaque ground instead of a see-through bar. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nav .inner{background:rgba(23,19,28,.95)}
  .nav.stuck .inner{background:rgba(19,16,23,.97)}
}
@media (prefers-reduced-motion: reduce){
  .nav,.nav .inner{transition:none}
}

/* ----------------------------------------------------------------------------
   Wide screens put the mark on the centre line of the bar, links to its left
   and the actions to its right. Placement is explicit per child so the
   thank-you page, which has no link group, still centres the logo.
   ------------------------------------------------------------------------- */
/* Plain mark, the default for every header that has no announcement strip
   above it (the funnel pages' header.top, and thank-you.html's nav, which
   sits too close to the viewport top for anything to break through). A
   little top margin so it is not flush against the edge. */
#brand{margin-top:6px}
#brand img{height:30px;width:auto}
/* Funnel pages only: the mark carries the whole header on its own here, no
   strip or bar to share the weight with, so it runs much bigger. */
header.top #brand img{height:56px}

/* The round badge is only for the page that actually has a seam to break
   through: the announcement strip's bottom edge meeting the floating bar's
   top edge. Anywhere else it has nothing to poke into and just floats,
   clipped by the viewport. Runs at every width on this page, scaled down on
   narrow bars, since the strip is there at every width too. */
body:has(.topbar) #brand{
  position:relative;display:grid;place-items:center;border-radius:50%;
  width:52px;height:52px;margin-top:-18px;margin-bottom:-12px;
  /* Close to the bar's own near-black glass rather than a visibly separate
     tone, so the badge reads as part of the header, not a sticker on it. */
  background:
    radial-gradient(120% 120% at 30% 20%,rgba(255,206,133,.1),rgba(255,206,133,0) 64%),
    #2d2735;
  box-shadow:
    inset 0 1px 0 rgba(255,206,133,.24),
    inset 0 0 0 1px rgba(255,255,255,.05),
    0 10px 22px -16px rgba(0,0,0,.7);
}
body:has(.topbar) #brand img{height:28px;width:auto}

/* THE WIDE BAR, 1280px and up. Since 2026-09-28 it carries the site's eight
   pages, and they do not fit a column beside a centred mark: that column is
   about 520px at 1440 and the eight bar words measure 566px. So the mark
   anchors LEFT, as it already did below this width and as every sub-page bar
   does, the pages run beside it, and the actions keep the right edge. At 1280
   the row leaves 57px of air before the actions; 1240 left 21px, which is why
   the tier starts here. The round plate stays: the seam it breaks, the strip
   meeting the bar's top edge, is there at the left edge as much as the middle. */
@media (min-width:1280px){
  .nav .inner{
    display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;
    padding-inline:26px;
  }
  /* the row is explicit on every child, so auto-placement can never push one
     onto a second row */
  .nav .inner > #brand{grid-area:1 / 1;justify-self:start}
  .nav .inner > .links{grid-area:1 / 2;justify-self:start;margin-left:0}
  .nav .inner > .acts{grid-area:1 / 3;justify-self:end;margin-left:0}
  body:has(.topbar) #brand{width:80px;height:80px;margin-top:-34px;margin-bottom:-26px}
  body:has(.topbar) #brand img{height:40px;width:auto}
}

/* the hero now starts under a floating bar, so it needs the height back */
.hero-full{margin-top:calc(-1 * (64px + clamp(10px,1.6vw,20px)))}
/* Top padding clears the floating bar and no more. It was clamp(150px,20vh,220px),
   which on a phone opened roughly a third of a screen of empty ground between the
   bar and the headline, and on a tall desktop window pushed the whole hero down.
   The floor is now the bar height plus a real gap rather than a guessed 150px.
   The bottom padding clears the band's rise plus a gap, at every size. */
.hero-full{padding-block:clamp(122px,15vh,182px) calc(var(--band-rise) + clamp(46px,6vh,78px))}

@media (max-width:1279px){
  /* One source for the narrow bar's own geometry. The badge that breaks out of
     it derives its diameter and its overhang from these three, so the bar and
     the thing overlapping it can never be tuned apart. */
  .nav .inner{
    --nav-h:48px;--nav-pad:6px;--brand-d:72px;
    /* The bar's real inner height is not --nav-h minus padding: min-height is
       48px but the 44px controls plus 12px of padding push it to 56px, so the
       controls are what set it. --ctrl is that control size, and everything the
       badge does is measured against it. Deriving the overhang from --nav-h
       instead put the badge 4px proud where the design wanted 8px, and the
       error only showed up in a render. */
    --ctrl:44px;
    --nav-box:var(--ctrl);
    /* half the excess: pull this off the top and bottom and the badge lays out
       as --nav-box tall, so the bar never grows to contain it */
    --brand-out:calc((var(--brand-d) - var(--nav-box)) / 2);
    /* Right padding is 2px, not 8px. The controls used to be drawn tiles whose
       own edge was the thing being inset from the bar; they are bare glyphs
       now, so what needs the inset is the glyph, and a 19px mark centred in a
       44px target already carries 12.5px of its own. 2px puts the burger's
       bars about 15px in from the bar's edge, which matches the badge's 14px
       on the other side. */
    padding:var(--nav-pad) 2px var(--nav-pad) 14px;min-height:var(--nav-h);
  }
  /* The stuck state shrinks the wide bar from 68px to 62px. This one is already
     at 48px, and letting it shrink further would move the bar's centre line out
     from under a badge whose overhang was computed against --nav-h. It keeps
     the colour and shadow change and drops the size change. */
  .nav.stuck .inner{min-height:var(--nav-h);padding-block:var(--nav-pad)}
}

/* ============================================================================
   Weekends against years. A 104-unit waffle was the wrong form: the twelve that
   matter were outnumbered nine to one by grey noise and the wrap point carried
   no meaning. Two rules at true relative length say the same thing in one look.
   ========================================================================= */
.compare{margin:var(--s-10) 0 var(--s-12);display:grid;gap:var(--s-6)}
.cmp-row{display:grid;gap:var(--s-3)}
@media (min-width:700px){
  .cmp-row{grid-template-columns:minmax(0,17rem) minmax(0,1fr);align-items:center;gap:var(--s-8)}
}
.cmp-row .lbl{display:flex;align-items:baseline;gap:14px;min-width:0}
.cmp-row .n{
  font-family:var(--serif);font-size:clamp(2.2rem,4vw,3.2rem);line-height:.95;
  letter-spacing:-.02em;font-variant-numeric:tabular-nums;
}
.cmp-row .u{font-size:.86rem;color:var(--muted-2);line-height:1.35}
/* Tall enough to hold the gap label inside it. Both rows carry the same height
   so the two bars stay directly comparable, which is the whole job of the
   figure; only one of them has a label to hold. */
.cmp-row .track{
  position:relative;min-height:38px;border-radius:5px;overflow:hidden;
  display:flex;align-items:center;
}
.cmp-row .fill{
  position:absolute;inset:0 auto 0 0;border-radius:3px;
  transform-origin:left;transform:scaleX(var(--w));
}
/* ADATS */
.cmp-row.ours .n{color:var(--gold)}
.cmp-row.ours .track{background:rgba(255,255,255,.05)}
.cmp-row.ours .fill{width:100%;background:var(--gold)}
/* the usual route */
.cmp-row.theirs .n{color:var(--muted)}
.cmp-row.theirs .track{background:rgba(255,255,255,.05)}
/* hatched, not solid. A solid lighter bar that is nine times longer reads as
   "more, therefore better", which is the opposite of the point. */
.cmp-row.theirs .fill{
  width:100%;
  background:
    repeating-linear-gradient(-45deg,
      rgba(246,240,232,.20) 0 1.5px, transparent 1.5px 7px),
    rgba(246,240,232,.06);
}
.cmp-note{margin:0;font-size:.84rem;color:var(--muted-2);letter-spacing:.005em}

/* The gap, named inside the bar it belongs to. This replaces a bracket and a
   caption that floated under the chart: two more elements explaining a third,
   when the trough itself was the thing that needed explaining. The label sits
   in the 88.5% of the ADATS track the gold does not fill, which is exactly the
   92 weeks it names, so the sentence and the space it describes are the same
   object. `left` is stated as the same 12/104 the fill is scaled by. */
.cmp-row .rest{
  position:absolute;left:calc(100% * 12 / 104);right:0;
  display:flex;align-items:center;gap:.4ch;
  padding-inline:var(--s-4) var(--s-3);
  font-size:.82rem;line-height:1.3;color:var(--gold-dim);letter-spacing:.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  pointer-events:none;
}
.cmp-row .rest b{color:var(--gold);font-weight:600}
/* Under about 620px the sentence cannot fit the 88.5% it is describing without
   ellipsing away the part that matters, so it drops below the bar and keeps its
   alignment to where the gold ends. Same fact, same anchor, two lines allowed. */
@media (max-width:619px){
  .cmp-row.ours{position:relative;padding-bottom:var(--s-6)}
  .cmp-row .rest{
    position:absolute;top:100%;left:calc(100% * 12 / 104);right:0;
    padding:6px 0 0;white-space:normal;overflow:visible;
  }
  .cmp-row .track{overflow:visible}
}

/* ============================================================================
   Matchup. Two side-by-side lists made the reader pair the items themselves;
   one axis per row does the pairing for them. The ADATS column carries a
   continuous ground so it reads as one answer, not four separate cells.
   ========================================================================= */
.matchup{margin:var(--s-10) 0 var(--s-10)}
.mu-head{display:none}
.mu-row{
  display:grid;gap:var(--s-2);
  padding:var(--s-5) 0;border-top:1px solid var(--hair);
}
.mu-k{
  font-size:.7rem;letter-spacing:var(--tr-meta);text-transform:uppercase;
  color:var(--muted-2);
}
.mu-a,.mu-b{display:flex;gap:var(--s-3);align-items:flex-start;
  font-size:.98rem;line-height:1.5;letter-spacing:var(--tr-body)}
.mu-a{color:var(--muted-2)}
.mu-b{color:var(--text)}
.mu-a .mk,.mu-b .mk{flex:none;width:18px;height:18px;display:grid;place-items:center;margin-top:2px}
.mu-a .mk svg,.mu-b .mk svg{width:15px;height:15px}
.mu-a .mk{color:rgba(246,240,232,.34)}
.mu-b .mk{color:var(--gold)}

@media (min-width:820px){
  .matchup{display:grid}
  .mu-head,.mu-row{
    display:grid;
    grid-template-columns:minmax(0,8.5rem) minmax(0,1fr) minmax(0,1fr);
    gap:0 var(--s-6);
    /* stretch, not centre. Every cell has to be full row height or the hairline
       each one carries lands at a different y and the rules step up and down
       across the table. Content is centred inside the cell instead. */
    align-items:stretch;
  }
  /* No gap under the head. It used to carry --s-3 of bottom padding, which cut
     the highlighted column in two: a rounded plate, a 12px hole, then the rows.
     The plate now sits directly on the first row and the column is continuous. */
  .mu-head{padding:0}
  .mu-head span{
    font-size:.7rem;letter-spacing:var(--tr-meta);text-transform:uppercase;color:var(--muted-2);
    display:flex;align-items:flex-end;
  }
  .mu-head .win{color:var(--gold)}
  .mu-head span:nth-child(2),.mu-head span:nth-child(3),
  .mu-row .mu-k,.mu-row .mu-a,.mu-row .mu-b{padding-inline:var(--s-5)}
  .mu-row .mu-k{padding-inline-start:0}
  .mu-row{padding:0;border-top:0}
  .mu-k,.mu-a,.mu-b{padding-block:var(--s-5);display:flex;align-items:center}

  /* The rule belongs to the left side only. Run across the whole row it cut
     through the highlighted column and read as a table gridline drawn on top of
     a panel. The panel divides itself with a much fainter line of its own. */
  .mu-k,.mu-a{border-top:1px solid var(--hair)}
  .mu-row + .mu-row .mu-b{border-top:1px solid rgba(255,255,255,.055)}

  /* One continuous column: the head plate closes the top, the last row the
     bottom, and every cell between them shares the same ground. */
  .mu-head .win,.mu-row .mu-b{background:rgba(255,206,133,.055)}
  .mu-head .win{padding-block:var(--s-4);border-radius:12px 12px 0 0}
  .mu-row:last-child .mu-b{border-radius:0 0 12px 12px}
  .mu-k{color:var(--muted-2)}
}


/* the bars grow once, when the block arrives, from a state that is already
   readable if the animation never runs */
@media (prefers-reduced-motion: no-preference){
  .compare.armed .fill{transition:transform 900ms var(--ease)}
  .compare.armed:not(.shown) .fill{transform:scaleX(0)}
}

/* ============================================================================
   Funnel intro composition. The two columns were drifting apart on a wide
   screen and reading as two unrelated blocks. Tighter gap, a capped text
   column, and the stat grid overlapping the reel so the rail reads as one
   composed unit instead of two stacked boxes.
   ========================================================================= */
@media (min-width:1000px){
  /* The pair is centred as a pair, and the text column is sized to the stage.
     It used to be `1fr` with the stage `justify-self:end` inside it, which put
     every spare pixel of the shell between the viewport edge and the first
     word: measured at 1920 the shell was perfectly centred (350px each side)
     while the visible ink sat 138px right of centre, and the same 136-142px
     offset held at 1440 and 2560. A centred container is not centred content.
     Sizing the track to the stage and centring the tracks moves that slack
     outside the pair, where it splits evenly. */
  body:not(.page) .shell{
    grid-template-columns:minmax(0,544px) minmax(330px,392px);
    gap:clamp(24px,2.6vw,44px);
    justify-content:center;
  }
  body:not(.page) .stage{max-width:544px;justify-self:start;margin-right:0}
  body:not(.page) .rail{justify-self:start}
}
.hero .reel{border-bottom-left-radius:0;border-bottom-right-radius:0}
.hero .stats{
  margin-top:-1px;                    /* seam, not a gap */
  border-top-left-radius:0;border-top-right-radius:0;
}
@media (min-width:1000px){
  .hero{gap:0}
  .hero .stats{
    width:calc(100% - 28px);margin-inline:auto;
    border-radius:0 0 var(--radius) var(--radius);
    box-shadow:0 24px 40px -30px rgba(0,0,0,.9);
  }
}

/* the band overlaps the hero, so it needs its own ground to sit on */
.band{
  background:var(--line);
  box-shadow:0 30px 60px -34px rgba(0,0,0,.95);
}
.band .cell{background:rgba(31,27,38,.94);backdrop-filter:blur(14px)}

/* key facts in running copy carry the accent, so a scanner catches the date
   and the length without reading the sentence */
.lede .hl,.help .hl{color:var(--gold);font-weight:500;white-space:nowrap}
.lede strong,.help strong{color:var(--text);font-weight:500}

/* ============================================================================
   Count-led heading. Libre Caslon has no italic and background-clip on text is
   banned, so the emphasis is carried by scale and placement instead: the number
   is set larger, in gold, and the sentence resolves under it.
   ========================================================================= */
.count-h{line-height:1.02}
.count-h .big{
  font-style:normal;color:var(--gold);
  font-size:1.42em;line-height:.92;
  letter-spacing:-.03em;
  /* sits on the same baseline as the words that follow it */
  display:inline-block;transform:translateY(.06em);margin-right:.06em;
}
@media (max-width:560px){ .count-h .big{font-size:1.3em} }
/* Two adjacent full-band sections stack to ~176px of dead air at the seam on
   a phone. This one specifically felt like a hole between the last "how it
   works" step and the heading above it, so it drops to the tight band. */
@media (max-width:560px){ #how{padding-top:var(--band-tight)} }

/* the syllabus never strands a trailing item: at an odd count the last row runs
   the full width and keeps the same rhythm rather than leaving a dead cell */
@media (min-width:760px){
  .syllabus .row:last-child:nth-child(odd){
    grid-column:1 / -1;
    background:linear-gradient(90deg,rgba(255,255,255,.03),transparent 62%);
  }
}

/* the comparison label: unit on top, the qualifier beneath it */
.cmp-row .u{display:flex;flex-direction:column;gap:1px}
.cmp-row .u b{font-weight:500;color:var(--text);font-size:.95rem;letter-spacing:-.005em}
.cmp-row .u span{font-size:.8rem;color:var(--muted-2);line-height:1.4}
@media (min-width:700px){ .cmp-row .lbl{align-items:center} }

/* band captions: one line, set as labels rather than sentences */
.band span{
  font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--muted-2);max-width:none;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;
}

/* the rating rail */
.hero-tag .rstars{color:var(--gold);letter-spacing:.18em;font-size:.72rem}
.hero-tag .rtxt{color:rgba(255,255,255,.62)}
.hero-tag{text-decoration:none;transition:color 200ms}
.hero-tag:hover .rtxt{color:#fff}

/* the eyebrow-free microcopy sits quieter under the buttons */
.hero-full .microcopy{font-size:.78rem;letter-spacing:.01em;max-width:52ch;
  color:rgba(246,240,232,.58)}

/* ============================================================================
   ADVANCED LAYER, 2026-08-11
   WebGL hero field, split-word headings, and the sticky three-step rail.
   Every rule here degrades to the page as it was: the canvas is decoration,
   the word masks contain visible words, and the rail is CSS sticky, so the
   section reads correctly with no JavaScript motion at all.
   ========================================================================= */

/* ---- the shader field sits between the film and the scrim ---------------- */
.glfield{
  position:absolute;inset:0;width:100%;height:100%;
  z-index:0;                 /* over the scrim, under .hero-inner */
  display:block;pointer-events:none;
}
@media (max-width:620px){ .glfield{opacity:.7} }

/* ---- headings that rise word by word ------------------------------------ */
/* The mask clips the word on its way up. Padding plus an equal negative margin
   gives descenders somewhere to live without moving the baseline. */
.wmask{display:inline-block;overflow:hidden;vertical-align:bottom;
  padding-bottom:.12em;margin-bottom:-.12em}
.w{display:inline-block;will-change:transform}

/* an emphasised phrase carries a gold rule that draws in behind it */
/* the padding is the rule's clearance: at bottom:0 with no room, the underline
   runs straight through the descenders of "marketing copy" */
.ruled{position:relative;display:inline-block;padding-bottom:.16em}
.ruled .drawn{
  position:absolute;left:0;right:0;bottom:0;height:2px;border-radius:2px;
  background:linear-gradient(90deg,rgba(255,206,133,.22),var(--gold) 42%,rgba(255,206,133,.28));
  transform-origin:left center;
}

/* ---- section ledes: a step quieter than the heading they follow ---------- */
.shead .lede{font-size:clamp(.98rem,1.1vw,1.08rem);line-height:1.52;max-width:58ch}

/* ============================================================================
   The three-step rail. Left column holds the count, the progress rule and the
   step's mark, and sticks. Right column is the three steps in normal flow.
   ========================================================================= */
.tell{display:grid;gap:var(--s-8)}

/* On mobile there is no rail to look at, so the count lives on each step. On
   desktop it moves into the rail and the per-step number is dropped, because
   two copies of "01" a hundred pixels apart is just noise. */
.tell-count{display:none;align-items:baseline;gap:8px;margin:0;font-family:var(--serif);
  line-height:1;color:var(--muted-2)}
.tell-count b{font-size:clamp(2.4rem,4vw,3.4rem);font-weight:400;color:var(--gold);
  font-variant-numeric:tabular-nums}
.tell-count i{font-style:normal;font-size:1rem;letter-spacing:.04em}

.tell-bar{display:none}
.tell-art{display:none}

.tell-steps{display:grid;gap:var(--s-8)}
.tell-steps{min-width:0}
.tstep{display:flex;flex-direction:column;gap:var(--s-3);align-items:flex-start;
  padding-top:var(--s-6);border-top:1px solid var(--hair)}
.tstep-n{font-size:.72rem;letter-spacing:var(--tr-meta);text-transform:uppercase;
  color:var(--gold-dim);font-variant-numeric:tabular-nums}
.tstep h3{font-size:clamp(1.2rem,1.9vw,1.5rem);line-height:1.2;letter-spacing:var(--tr-heading);
  margin:0;color:var(--text)}
.tstep p{font-size:.97rem;line-height:1.66;color:var(--muted);margin:0;max-width:46ch}

@media (min-width:1000px){
  .tell{grid-template-columns:minmax(0,258px) minmax(0,1fr);gap:clamp(40px,5vw,88px);
    align-items:start}

  .tell-rail{
    position:sticky;align-self:start;
    /* Parked at the vertical middle so the count sits beside the step being
       read, not 300px above it. `max()` keeps it clear of the floating bar on
       a short window. --head is the measured bottom of that bar. */
    top:max(calc(var(--head, 118px) + 28px), calc(50vh - 132px));
    display:grid;grid-template-columns:auto 1fr;gap:var(--s-6);align-items:start;
  }
  /* the rule runs the height of the rail; the inner fill is what scrubs */
  .tell-bar{
    display:block;grid-row:1 / span 2;width:2px;height:clamp(140px,17vh,208px);
    background:rgba(255,255,255,.08);border-radius:2px;overflow:hidden;
  }
  .tell-bar i{display:block;width:100%;height:100%;background:var(--gold);
    transform:scaleY(0);transform-origin:top center;border-radius:2px}
  .tell-count{display:flex;grid-column:2}
  .tstep-n{display:none}
  /* each step already reserves its own height; a gap on top of that opens a
     hole between them that reads as a mistake */
  .tell-steps{gap:0}

  .tell-art{display:block;grid-column:2;position:relative;width:96px;height:96px;margin-top:var(--s-6)}
  /* Law 16: the rail's glyph paints bare on the band in the site's one-stroke
     language, at display size so it still anchors the column without the
     glass tile it used to sit on. The cross-fade between steps is kept. */
  .tell-art .plate{
    position:absolute;inset:0;display:grid;place-items:center;
    opacity:0;transform:scale(.92);color:var(--gold);
  }
  .tell-art .plate .ico{width:56px;height:56px;stroke-width:1.1}
  .tell-art .plate.on{opacity:1;transform:none}
  @media (prefers-reduced-motion: no-preference){
    .tell-art .plate{transition:opacity 420ms var(--ease),transform 420ms var(--ease)}
  }

  /* the steps are tall enough that exactly one owns the middle of the screen */
  /* tall enough that exactly one step owns the middle of the screen, capped
     so a 1400px-tall monitor does not turn three short steps into three
     screens of empty plum */
  .tstep{min-height:clamp(300px,40vh,460px);justify-content:center;padding-top:0;border-top:0}
  .tstep h3{font-size:clamp(1.5rem,2.4vw,2rem)}
  .tstep p{font-size:1.02rem;max-width:42ch}
  /* The step being read is the lit one. Opacity only, never a layout property.
     `.lit` is added by motion.js only after its observer is actually watching,
     so a page with no JS, a blocked script or reduced motion never dims a step
     that nothing is going to light back up. */
  .tell.lit .tstep{opacity:.32;transition:opacity 450ms var(--ease)}
  .tell.lit .tstep.on{opacity:1}
  @media (prefers-reduced-motion: reduce){
    .tell.lit .tstep{opacity:1;transition:none}
  }
}

/* ============================================================================
   How-it-works: a photo per step, and light that moves.
   ========================================================================= */

/* Two slow warm masses drifting behind the section. No `filter: blur()`: a
   radial-gradient is already soft, and blurring a 44vw box every frame is the
   expensive way to get the same pixels. Only `transform` animates.
   No `overflow:hidden` on the section either, because that would make it a
   scroll container and silently kill the sticky rail inside it. */
/* `overflow-x: clip`, not `overflow: hidden`. Hidden would make this a scroll
   container and kill the sticky rail inside it; clip trims the light without
   creating one. The blobs still sit inside the bounds as a second guard. */
.drift{position:relative;overflow-x:clip}
.drift::before,.drift::after{
  content:"";position:absolute;pointer-events:none;z-index:0;border-radius:50%;
}
.drift > *{position:relative;z-index:1}
.drift::before{
  width:min(46vw,720px);aspect-ratio:1;left:0;top:2vh;
  background:radial-gradient(circle,rgba(255,206,133,.09),transparent 64%);
  animation:drift-a 28s ease-in-out infinite alternate;
}
.drift::after{
  width:min(40vw,620px);aspect-ratio:1;right:0;bottom:2vh;
  background:radial-gradient(circle,rgba(163,142,214,.075),transparent 64%);
  animation:drift-b 37s ease-in-out infinite alternate;
}
@keyframes drift-a{to{transform:translate3d(4vw,6vh,0) scale(1.1)}}
@keyframes drift-b{to{transform:translate3d(-3vw,-5vh,0) scale(1.08)}}
@media (prefers-reduced-motion: reduce){
  .drift::before,.drift::after{animation:none}
}

/* the third column: their own photograph of whichever step you are reading */
.tell-shots{display:none}
@media (min-width:1180px){
  .tell{
    grid-template-columns:minmax(0,178px) minmax(0,1fr) minmax(0,clamp(250px,22vw,340px));
    gap:clamp(28px,3.4vw,64px);
  }
  .tell-shots{
    display:block;position:sticky;align-self:start;
    top:max(calc(var(--head, 118px) + 28px), calc(50vh - 196px));
    aspect-ratio:4 / 5;border-radius:var(--radius);overflow:hidden;
    border:1px solid var(--line);background:var(--ink-3);
  }
  /* NOT `.shot`: that class is already the photoCard component, which carries an
     aspect-ratio and collapsed this frame to a square. */
  .tell-shots .tshot{position:absolute;inset:0;opacity:0;transform:scale(1.05)}
  .tell-shots .tshot.on{opacity:1;transform:none}
  .tell-shots img{width:100%;height:100%;object-fit:cover;display:block}
  /* a quiet scrim so the photo never competes with the step it illustrates */
  .tell-shots::after{
    content:"";position:absolute;inset:0;pointer-events:none;z-index:2;
    background:linear-gradient(180deg,rgba(20,17,24,.10),rgba(20,17,24,.52));
  }
  @media (prefers-reduced-motion: no-preference){
    .tell-shots .tshot{transition:opacity 620ms var(--ease),transform 900ms var(--ease)}
  }
}

/* ============================================================================
   Hero on a phone. The deck was set at the desktop ratio and ran five lines,
   and two pill buttons at content width left a ragged right edge.
   ========================================================================= */
@media (max-width:560px){
  .hero-full .lede{font-size:.98rem;line-height:1.5}
  /* Every CTA pair stacks full-width on a phone, not only the hero's. A
     content-width pill leaves a ragged right edge and a smaller tap target. */
  .cta-row{display:grid;grid-auto-flow:row;gap:10px;width:100%}
  .cta-row .btn{width:100%;justify-content:center}
  .btn .ent{padding-left:10px}

  /* Stat band: all four across, no dividers. The auto-fit minmax collapsed it
     to one column, which turned four short facts into four full-width slabs.
     The grid lines and the 1px gap that drew them are dropped here: at this
     size they cut an 86px cell into something that reads as a table. Labels
     swap to their one-word short forms so nothing wraps. */
  /* The rise has to go with the fill. The band normally rides up over the
     bottom of the hero, which works while it is an opaque plate; once it is
     transparent the hero's own edge shows through behind it as a hard
     rectangle. Transparent means it sits in its own air instead. */
  .band{
    grid-template-columns:repeat(4,minmax(0,1fr));
    gap:0;background:transparent;border:0;box-shadow:none;
    margin-top:0;padding-block:var(--s-2);
  }
  /* The hero's bottom padding reserved room for the band's rise (above) to
     overlap into. With the rise cancelled at this width, that room was dead
     air between the microcopy and the band. Drop just the rise's share. */
  .hero-full{padding-bottom:clamp(46px,6vh,78px)}
  .band .cell{
    background:transparent;backdrop-filter:none;
    padding:var(--s-5) 2px;gap:10px;align-items:center;text-align:center;
  }
  /* The mark sits quieter than the figure it introduces, and the figure and its
     label are set tight to each other so each cell reads as one object with
     air between the cells rather than four evenly spaced lines. */
  .band .cell .ico{width:15px;height:15px;opacity:.72}
  .band .cell b{font-size:1.46rem;line-height:1;margin-bottom:-2px}
  .band .cell .lbl-full{display:none}
  .band .cell .lbl-short{
    display:block;font-size:.56rem;letter-spacing:.14em;white-space:nowrap;
    color:var(--muted-2);
  }

  /* The comparison ran nearly a full screen per side on a phone. Tighter
     display size, tighter rhythm, and the number sits on the label line
     instead of above it. */
  /* Headings go up as the decks come down, so the gap between them widens and
     the hierarchy is readable at a glance instead of two similar greys. */
  .shead h2,.sec h2,.oh-body h2{font-size:clamp(1.62rem,7.2vw,2.05rem);line-height:1.14}
  .hero-full .h1{font-size:clamp(2.05rem,9.4vw,2.7rem)}

  /* Every deck on a phone, not just the open house one. A clamp tuned for a
     desktop column leaves a five and six line paragraph on a 390px screen at
     full weight, which reads as body copy competing with the heading above it.
     One step down, one weight lighter, more leading, quieter colour. */
  .lede,.shead .lede,.oh-body .lede{
    font-size:.9rem;font-weight:300;line-height:1.7;
    letter-spacing:.006em;color:var(--muted-2);
  }

  .compare{margin:var(--s-8) 0 var(--s-10);gap:var(--s-5)}   /* --s-9 does not exist */
  .cmp-row{gap:var(--s-2)}
  .cmp-row .n{font-size:2.1rem}
  .cmp-row .lbl{gap:10px}
  .cmp-row .track{height:22px}
  .cmp-note{font-size:.8rem}
}

/* ============================================================================
   The narrow bar. Below the burger breakpoint the links row and the actions
   group are both hidden, so the bar is a two-anchor composition: the lockup on
   the left, the actions on the right, and real air between them. It used to
   centre the mark absolutely, which is the 1100px grid's idiom and only works
   while something flanks the mark on both sides.

   Three things change on this tier:
   1. the mark anchors left and stands in flow, not absolutely positioned;
   2. the wordmark appears beside it, so the bar names the school instead of
      showing an unlabelled tooth glyph;
   3. the round plate comes off. It exists to break the seam where the
      announcement strip meets the bar's top edge, which is a centred-mark
      trick. Against the left edge, next to lettering, it reads as a sticker on
      the bar rather than part of it.
   ========================================================================= */
@media (max-width:1279px){
  .nav .inner{justify-content:flex-start}
  .nav .inner > #brand{margin-right:auto}
  /* The badge now runs taller than the bar it sits in and breaks both edges of
     it. Negative block margins keep its layout height inside the 36px content
     box, so the bar does not grow to contain it: 64px of badge with -16px top
     and bottom lays out as 32px and stands 8px proud of the bar's outer edge,
     top and bottom. It clears the announcement strip above by 6px at 390px.
     Both numbers are derived from the bar, not guessed: --nav-h is the bar's
     own min-height on this tier and the poke is one token. */
  body:has(.topbar) #brand{
    width:var(--brand-d);height:var(--brand-d);
    margin-top:calc(-1 * var(--brand-out));margin-bottom:calc(-1 * var(--brand-out));
  }
  body:has(.topbar) #brand img{height:34px}
  /* Scoped to .nav so it outranks the .callbtn{display:none} default, which is
     written later in this file and would otherwise win on source order. */
  .nav .callbtn{display:grid}
  /* Icon + label sit on one line once the control carries words. */
  .nav .callbtn[role="button"]{display:inline-flex;align-items:center;gap:0}
}

/* The narrow-bar call button. The one action the bar carries once the actions
   group is hidden, and deliberately not a second "Apply": the hero, the finale
   and the sticky bottom bar already own that ask, and a phone call is the
   cheaper thing to offer someone who is not ready to fill in a form. Icon only,
   at the 44px minimum target, with the number on the title and the action in
   the aria-label. */
/* Both controls are bare glyphs, not boxes. Two outlined tiles beside a round
   badge put three competing containers in a 346px bar; the glass bar is
   already the container and the icons only need to sit in it. The 44px box
   stays for the tap target, it just stops being drawn. Colour carries hover
   and press, since there is no longer a ground to tint. */
.callbtn{
  display:none;place-items:center;width:44px;height:44px;flex:none;
  border:0;background:none;color:#fff;text-decoration:none;
  transition:color 200ms var(--ease),transform 200ms var(--ease);
}
.callbtn svg{width:20px;height:20px;flex:none}
.callbtn:hover{color:var(--gold)}
.callbtn:active{transform:scale(.92)}
/* One pair, so they read as a pair. The 44px targets still do not overlap;
   the negative margin only takes back some of the padding either side of two
   glyphs that no longer have edges of their own to keep apart. */
.callbtn + .burger{margin-left:-6px}


/* The open-house calendar button. Its label wrapped to two lines, which made it
   taller than the gold button beside it and broke the row's baseline. The label
   is two words and should never wrap; the icon keeps its own size, since an SVG
   with no intrinsic size stretches to fill a flex parent. */
#ohCard .cta-row #ohIcs{white-space:nowrap}
#ohCard .cta-row #ohIcs svg{width:18px;height:18px;flex:none}
/* the hero deck is long; at 400 it competed with the headline for weight */
.hero-full .lede{font-weight:300}

footer.legal .credit a{color:var(--gold-dim);text-decoration:none;border-bottom:1px solid rgba(255,206,133,.32)}
footer.legal .credit a:hover{color:var(--gold);border-bottom-color:var(--gold)}

/* ============================================================================
   The registration-fee gate. Stated before the submit button because it is a
   condition of the application, not a footnote about it.
   ========================================================================= */
/* Kept deliberately tight: the summary is the tallest step in the funnel and
   rule 5 says every step fits its viewport without scrolling. */
.feegate{
  margin:var(--s-4) 0 var(--s-3);padding:var(--s-4) var(--s-4) calc(var(--s-4) - 2px);
  border-radius:12px;
  border:1px solid rgba(255,206,133,.24);background:rgba(255,206,133,.05);
  display:grid;gap:8px;
}
.feegate p{margin:0;font-size:.83rem;line-height:1.5;color:var(--muted)}
.feegate .fg-head{
  display:flex;align-items:center;gap:9px;
  font-size:.72rem;letter-spacing:var(--tr-meta);text-transform:uppercase;color:var(--gold);
}
.feegate .fg-head svg{width:15px;height:15px;flex:none}
.feegate .fg-here{color:var(--muted-2);font-size:.79rem}

/* ============================================================================
   The one documented exception to "every funnel step fits its viewport".

   The summary is a review screen, not a question. It carries the plan recap,
   an optional note, the attestation checkbox, the registration-fee condition
   and the contact consent. On a 640px-tall window that does not fit, and every
   candidate for removal is a disclosure rather than decoration. So below 780px
   of height this step scrolls, and the submit button is reachable by scrolling
   instead of a required notice being deleted to avoid it.
   ========================================================================= */
@media (max-height:780px){
  body:not(.page)[data-step="summary"],
  body:not(.page)[data-step="summary"] .shell{
    height:auto;min-height:0;overflow:visible;align-content:start;
  }
  body:not(.page)[data-step="summary"] .shell{padding-bottom:clamp(20px,4vh,40px)}
}

/* ============================================================================
   Open house. The lowest-commitment ask on the page, and the one their social
   traffic was actually promised. Given a date block so it reads as an event
   rather than another content card.
   ========================================================================= */
.topbar .oh{color:var(--gold-soft)}
.topbar .oh:hover{color:var(--gold)}
.topbar .oh b{color:#fff}

/* No drawn border. The card is separated by light instead: a warm ground that
   falls off downward, a single hairline of gold light along the top edge done
   as an inset shadow, and a deep ambient shadow seating it on the page. A 1px
   gold rectangle around the whole thing was the cheapest possible way to say
   "card" and it fought the hairline language used everywhere else. */
.ohcard{
  position:relative;
  display:grid;gap:var(--s-6);align-items:start;
  padding:clamp(24px,3vw,40px);border-radius:var(--radius);border:0;
  background:
    radial-gradient(120% 100% at 0% 0%,rgba(255,206,133,.11),rgba(255,206,133,0) 62%),
    linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.012));
  box-shadow:
    inset 0 1px 0 rgba(255,206,133,.34),
    0 26px 60px -40px rgba(0,0,0,.95);
  /* Not clipped. The drift mass below is sized and faded so it never reaches
     the corner radius, so it needs no clip. */
  overflow:visible;
}
/* One warm mass drifting behind the card. A single composited layer moving on
   transform, no blur filter and no repaint: it costs one element and stays on
   the GPU. Nothing inside the card is sticky, so clipping it is safe. */
.ohcard::before{
  content:"";position:absolute;z-index:0;
  width:52%;aspect-ratio:1;left:2%;top:-14%;
  border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(255,206,133,.15),rgba(255,206,133,0) 62%);
  animation:ohdrift 26s ease-in-out infinite alternate;
  will-change:transform;
}
.ohcard > *{position:relative;z-index:1}
@keyframes ohdrift{
  from{transform:translate3d(0,0,0) scale(1)}
  to  {transform:translate3d(46%,22%,0) scale(1.18)}
}
@media (prefers-reduced-motion: reduce){
  .ohcard::before{animation:none}
}
@media (min-width:760px){ .ohcard{grid-template-columns:auto minmax(0,1fr);gap:clamp(26px,3.4vw,52px)} }

/* The photo is off below 1000px. Between 760 and 999 the card is only wide
   enough for the plate and the body, and a third column there would squeeze the
   facts list into wrapping. */
.oh-shot{display:none}
@media (min-width:1000px){
  /* Two columns and two rows. The plate keeps the top-left corner it already
     breaks out of; the photo takes the ground under it, which was 132px wide
     and empty for the card's whole height; the body spans both rows on the
     right. The plate's row is only as tall as what is left of it after its
     negative margin (135px of plate less a 104px pull is 31px of layout), so
     the photo gets nearly the full card height without being told a number. */
  .ohcard{
    grid-template-columns:minmax(268px,340px) minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);
  }
  /* The photo takes the whole left column, both rows, and the plate sits on top
     of its top-left corner. They used to be two stacked objects with 49px of
     empty ground between them, which read as a plate floating above a picture
     rather than as one thing. Now the plate breaks two edges at once, the
     card's and the photo's, which is the same move it already makes against the
     card alone. The plate is opaque, so nothing of the photo shows through it. */
  .ohcard > .oh-shot{grid-area:1 / 1 / span 2 / span 1}
  .ohcard > .oh-body{grid-area:1 / 2 / span 2 / span 1}
  .oh-shot{
    display:block;margin:0;border-radius:14px;overflow:hidden;
    /* The image is absolutely placed inside, so the figure has no intrinsic
       height of its own and stretches to whatever the body column leaves. A
       flowed img would have contributed its own 340px instead and driven the
       card taller than its text needed. The floor stops it collapsing if the
       body ever gets short. */
    position:relative;min-height:210px;
    /* .ohcard sets align-items:start, so this has to opt back in explicitly or
       it sits at its 210px floor in a 301px row. */
    align-self:stretch;
    /* The card is not clipped (the plate breaks its edge), so this clips itself.
       The rim is the same hairline idiom as every other raised surface here, not
       a drawn border. */
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.07),0 18px 40px -28px rgba(0,0,0,.9);
  }
  .oh-shot img{
    position:absolute;inset:0;
    width:100%;height:100%;display:block;object-fit:cover;object-position:50% 38%;
    /* Warmed and settled a touch so a bright clinical photo does not outshout
       the card it sits in. */
    filter:saturate(.94) contrast(1.02) brightness(.94);
  }
}

.oh-body h2{font-size:clamp(1.72rem,3vw,2.6rem);line-height:var(--lh-heading);
  letter-spacing:var(--tr-heading);margin:0 0 var(--s-4);text-wrap:balance}
/* The accent phrase carries a rule that grows from the left on entry. Animated
   with scaleX, never width, and it is visible at full length if the script
   never arms it. */
.ohh em{font-style:normal;color:var(--gold);position:relative;display:inline-block}
.ohh em::after{
  content:"";position:absolute;left:0;right:0;bottom:-.12em;height:1px;
  background:linear-gradient(90deg,var(--gold),rgba(255,206,133,0));
  transform-origin:left center;
}
/* One step down from the default deck. At full size it competed with the
   heading above it; light grey at a generous leading reads as a supporting line
   rather than a second headline. Weight 400, not 300: at this size and colour
   the light cut washed out and the line did not read (#113). */
.oh-body .lede{
  margin:0 0 var(--s-5);max-width:54ch;
  font-size:clamp(.88rem,1.05vw,.96rem);font-weight:400;
  line-height:1.72;letter-spacing:.006em;color:var(--muted-2);
}
.oh-facts{list-style:none;margin:0 0 var(--s-6);padding:0;display:grid;gap:var(--s-3)}
.oh-facts li{display:flex;gap:var(--s-3);align-items:flex-start;font-size:.96rem;
  line-height:1.5;color:var(--muted)}
.oh-facts .mk{flex:none;width:19px;height:19px;display:grid;place-items:center;
  color:var(--gold);margin-top:1px}
.oh-facts .mk svg{width:16px;height:16px}
.oh-facts .t-short{display:none}

/* ---- one row per campus, each with its own date or "Soon" -----------------
   Borderless: the row is separated from its neighbour by space and by the
   weight step between the city and its address, never by a rule or a rim. */
.oh-facts li.oh-camp{align-items:flex-start}
.oh-camp-b{display:grid;gap:1px;min-width:0}
/* The city is the label and the date is the value, so the city carries the
   weight and the date carries the colour. Reversing that made the row read as
   a date with a place attached, which is backwards on a page where the reader
   already knows the school and is deciding which door to walk through. */
.oh-city{font-weight:600;color:var(--text);font-size:.95rem;letter-spacing:.005em}
.oh-date{color:var(--gold)}
.oh-addr{font-size:.84rem;color:var(--muted);opacity:.82}

/* "Soon" is a real answer, not a disabled state, so it keeps the accent rather
   than greying out. One step down to --gold-dim is enough to say it carries
   less information than a date without making it look broken. --gold-dim is
   4.6:1 on the card ground, so it clears the body-text floor on its own. */
.oh-camp.is-soon .oh-date{color:var(--gold-dim)}

/* the second proof source sits under the primary one, quieter */
.rating-head .alsofb{
  margin:6px 0 0;font-size:.82rem;color:var(--muted-2);letter-spacing:.005em;flex-basis:100%;
}
.rating-head .alsofb a{color:var(--muted);text-decoration:none;border-bottom:1px solid var(--hair)}
.rating-head .alsofb a:hover{color:var(--gold);border-bottom-color:rgba(255,206,133,.5)}

/* ============================================================================
   Phone pass, 2026-08-11. One block so the narrow-width decisions are in one
   place instead of spread through the file. Everything here is layout and
   type sizing; no facts, no colours invented outside the tokens.
   ========================================================================= */
@media (max-width:700px){

  /* The qualifier line is fine print. Smaller, tighter, and it should read as
     one quiet block rather than two loose lines of body copy. */
  .microcopy{font-size:.74rem;line-height:1.42;letter-spacing:.008em}
  .microcopy .mstar{width:9px;height:9px;margin-right:5px}
  .microcopy .mstar svg{width:9px;height:9px}

  /* Open house facts: one line each. The short strings come from config, so
     the full date and street address are still what the footer and the tour
     confirmation use. */
  .oh-facts li{font-size:.86rem;align-items:center}
  /* The reassurance row is one line and centres beside its icon; a campus row
     is three deep and has to stay top-aligned or its icon floats to the middle
     of the address. */
  .oh-facts li.oh-camp{align-items:flex-start}
  .oh-city{font-size:.9rem}
  .oh-addr{font-size:.78rem}
  .oh-facts .t-full{display:none}
  .oh-facts .t-short{display:block;white-space:nowrap}

  /* The app section leads with the phone. The screenshot is the argument; the
     list explains it afterwards. */
  #app .two{display:flex;flex-direction:column}
  #app .two > #appShot{order:-1;margin-bottom:var(--s-6)}
  #app .ticks li{font-size:.88rem}
  #app .ticks .t-full{display:none}
  #app .ticks .t-short{display:block;white-space:nowrap}
  /* Store badges sit under the phone, centred, since they are a status note
     rather than an action. */
  .stores{justify-content:center;flex-wrap:nowrap}

  /* Campus cards two per row. One per row turned two locations into two full
     screens of scrolling for what is a single comparison. */
  #campusCards{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-3)}
  /* The element is `pane campus-card`, NEVER `.card`. `#campusCards .card`
     matched nothing, so `.pane`'s 32px padding survived on both sides of a
     167px card and left a 101px column: a 101x63 photo, the city name on two
     lines, the address on four, and the arrow orphaned onto its own line.
     Measure the element you think you styled. */
  #campusCards .campus-card{padding:var(--s-4);gap:var(--s-3);align-items:stretch}
  #campusCards h3{font-size:.98rem}
  #campusCards p{font-size:.8rem;line-height:1.5}
  /* The caption plate is fixed padding plus a 1.45 line-height, so at this
     width it stood 114px over an 83px photo and painted 66px ABOVE the top of
     its own figure -- the slab of text sitting over the image in the phone
     render. It is also redundant here: both cards carry the same generic
     string and the campus name is the next line down. The `alt` on the img is
     identical, so nothing is lost to a screen reader. */
  #campusCards .shot figcaption{display:none}
  /* With the caption gone the photo can carry its own weight again. */
  #campusCards .shot{aspect-ratio:4/3}
  /* Keeps "Maps ->" together so the arrow never wraps alone. On one line the
     link's own box is 18px, well under the 44px tap floor, so the floor is a
     min-height rather than padding: padding is measured through whichever face
     draws tallest, min-height is not. */
  #campusCards .src{font-size:.75rem;min-height:44px;align-items:center;margin-top:0}

  /* FAQ. The questions were near-white at body size against near-black, which
     is the highest contrast pairing on the page and the hardest to scan down a
     list of sixteen. Softer text, more room per row, and the chevron quieter
     until the row is actually being used. */
  .faq summary{font-size:.94rem;line-height:1.45;color:var(--muted);padding-block:var(--s-4)}
  .faq details[open] summary{color:var(--text)}
  .faq summary::after{opacity:.5}
  .faq details[open] summary::after{opacity:1}
  .faq .a{font-size:.9rem;line-height:1.72;color:var(--muted-2)}
}

@media (max-width:700px){
  /* The finale CTA goes full width like every other one, and its qualifier
     line drops to fine print with the same star as the hero's. */
  .finale .cta-row{display:grid;width:100%}
  .finale .cta-row .btn{width:100%;justify-content:center}
  .finale .microcopy{font-size:.74rem;line-height:1.42;text-align:center}
}

/* Hero facts default: the long label runs, the short one is held back. */
.facts .ftx .t-short{display:none}

@media (max-width:700px){
  /* Three across on one row. The first-cell full-width span is cancelled here:
     with three columns there is no odd cell left over, so the orphan rule has
     nothing to fix and the row is even by construction. The mark moves above
     the value so each cell is a narrow stack rather than an icon plus a
     two-line block that cannot fit 113px. */
  .facts{grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;max-width:100%}
  .facts .fact,
  .facts .fact:first-child:nth-last-child(odd){
    grid-column:auto;flex-direction:column;align-items:center;text-align:center;
    gap:5px;padding:var(--s-3) 4px;justify-content:flex-start;
  }
  .facts .fmk,
  .facts .fact:first-child:nth-last-child(odd) .fmk{width:15px;height:15px;margin-top:0;opacity:.75}
  .facts .fmk svg,
  .facts .fact:first-child:nth-last-child(odd) .fmk svg{width:15px;height:15px}
  .facts .ftx{justify-items:center}
  .facts .ftx b,
  .facts .fact:first-child:nth-last-child(odd) .ftx b{font-size:.84rem;white-space:nowrap}
  .facts .ftx .t-full{display:none}
  .facts .ftx .t-short{display:block;font-size:.66rem;white-space:nowrap}

  /* The qualifier line shares the button's box, so the two read as one block
     rather than as text that happens to sit under a button. */
  .hero-full .microcopy{max-width:100%;width:100%}
}

/* The announcement strip swaps to a short form where the full sentence cannot
   fit. It is nowrap by design, so this is an overflow fix, not a preference. */
.topbar .t-short{display:none}
@media (max-width:700px){
  .topbar .t-full{display:none}
  .topbar .t-short{display:inline}
}

/* ============================================================================
   Funnel rail on a phone, 2026-08-11. It used to be display:none below 900px,
   so the proof beside each question, the school film included, existed only on
   a desktop. It now renders under the question, compact.

   TRADE-OFF, stated because it breaks a rule this project set itself: with the
   rail present, a question step no longer always fits the viewport without
   scrolling. The question, its options and the submit button are still above
   the fold; the proof card is what you scroll to. Steps that opt out with
   .hide-sm stay hidden.

   The film follows automatically. ambient() only borrows the shared <video>
   when no .reel is actually rendered, and the reel is rendered again here, so
   the reel takes the film back and the background falls to the blurred poster.
   ========================================================================= */
@media (max-width:900px){
  body:not(.page) .rail{display:block;max-width:100%;margin-top:var(--s-6)}
  body:not(.page) .rail.hide-sm{display:none}

  /* Sized for a clear look at the footage, not a full screen: the numbers
     now live on top of it, so the frame does not need to carry a second
     block of its own height below it. The max-height:820px rule below still
     shortens it further on landscape phones and short laptop windows. */
  /* FULL HEIGHT ON MOBILE, 2026-08-29. This was a fixed min(40vh,320px), which
     left the step 120px taller than the viewport at 390x844: the film was the
     thing you wanted to look at and the page still scrolled.

     It now takes whatever height is LEFT after the question, the button and the
     footer, so it is as tall as the screen allows and the step fits in one
     viewport by construction rather than by a number that happens to work on
     one phone. The floor stops it collapsing on a short landscape window, and
     dvh (not vh) is what accounts for the mobile browser's own chrome. */
  /* FULL HEIGHT ON MOBILE, 2026-08-29, sized from the real budget rather than
     by flex-grow. `.shell` has no bounded height here, so `flex:1` had nothing
     to fill against and the rail grew to 619px, which made the overflow worse
     (376px) instead of removing it.

     Measured at 390x844: header 92, question block 337, footer 62 once it is
     compacted below. That leaves ~350 for the film, so it takes the leftover
     and no more. dvh, not vh, because vh ignores the mobile browser chrome and
     would put the button under it. The cap keeps it sane on a tall phone; the
     floor keeps it watchable on a short landscape window. */
  body:not(.page) .rail .reel{
    height:clamp(155px, calc(100dvh - 535px), 400px);
    border-radius:var(--radius);
  }
  body:not(.page) .rail .card{padding:16px}
  body:not(.page) .rail .hero{display:grid;gap:0;position:relative}
  /* The stat row sits inside the frame, low third, over the same veil that
     already darkens the caption's ground, instead of its own block below. */
  body:not(.page) .rail .hero .stats{
    position:absolute;left:0;right:0;bottom:0;z-index:1;
    background:linear-gradient(180deg,rgba(12,10,15,0),rgba(10,8,13,.72) 40%,rgba(10,8,13,.86));
    border:0;border-radius:0 0 var(--radius) var(--radius);
  }
  body:not(.page) .rail .hero .stats div{background:transparent}
  body:not(.page) .rail .stats{grid-template-columns:repeat(2,minmax(0,1fr))}
  body:not(.page) .rail .stats div{padding:12px 14px}
  body:not(.page) .rail .stats b{font-size:1.15rem}
  body:not(.page) .rail .stats span{font-size:.68rem;line-height:1.35}
  /* Caption clears the stat row now sitting under it. */
  /* The 88px lift exists to clear the stats block that overlays the foot of the
     film on the INTRO step. The film now also runs on the `name` step, which has
     no stats, and there the caption floated a third of the way up with dead
     footage under it. `document.body.dataset.step` carries the live step id, so
     scope the lift to the step that actually needs it. */
  body:not(.page)[data-step="intro"] .rail .reel figcaption{bottom:88px}
  /* ...and it needs a ground once it is up there. The base veil ramps from
     transparent at 55% to .86 at the foot, so at the caption's band (215-232px
     down a 319px frame) it composites to roughly .24-.34 -- the caption was
     reading straight off whatever the footage happened to be, which at this
     seek point is a lit face. The ramp starts higher and gets to a readable
     alpha BEFORE the caption line rather than after it. Nothing is added on
     top of the photo above 34%, so the frame still opens clean. */
  body:not(.page)[data-step="intro"] .rail .reel .veil{
    background:linear-gradient(180deg,
      rgba(10,8,13,0) 34%, rgba(10,8,13,.58) 62%,
      rgba(10,8,13,.84) 80%, rgba(10,8,13,.88));
  }
  /* The stat row sits on that .88 already, so its own plate only has to finish
     the job rather than start it again; doubled, the two gradients crushed the
     bottom third of the footage to flat black. */
  body:not(.page) .rail .hero .stats{
    background:linear-gradient(180deg,rgba(10,8,13,0),rgba(10,8,13,.34) 40%,rgba(10,8,13,.55));
  }

  /* The shell centres its content on a phone; with the rail back, the column
     is taller than the viewport on most steps, so it goes back to top-aligned
     and scrolls from there. Centring a column taller than the screen hides the
     top of it. */
  body:not(.page) .shell{align-content:start;align-items:start}
}

/* Funnel rail on a phone, revision. The proof goes above the question, the
   column centres in the viewport when it fits, and the stat block runs four
   across instead of two by two. */
/* The funnel footer on a phone was 165px tall on an 844px screen, a fifth of
   the viewport spent on a disclaimer, and it was the single reason the step
   scrolled. Same words, a lot less room: the legal line still says everything
   it said, it just stops behaving like a section. Not applied to .page, where
   the footer is real content rather than a rail under a form. */
@media (max-width:900px){
  /* `body:not(.page) footer.legal` (0,2,2) is what line 2275 uses to set
     padding-block; a plain `.legal` here is 0,2,1 and loses to it, which is why
     the first attempt shrank the type and left the 20px block padding intact.
     Matching the selector, later in the file, is what actually wins. */
  body:not(.page) footer.legal{padding:8px 16px 10px;font-size:.58rem;line-height:1.32}
  /* .compact is a 3-row grid with row-gap var(--s-3). Three rows of 12px gap is
     24px, a quarter of the footer's height, spent separating three lines that
     read as one block anyway. */
  body:not(.page) footer.legal.compact{row-gap:2px}
  body:not(.page) .legal p{margin:0 0 3px}
  body:not(.page) .legal .fine{font-size:.54rem;opacity:.7;margin:0 0 3px}
  /* The phone number and the three policy links on ONE line. As two rows they
     cost another 20px for no gain; they are reference, not navigation. */
  body:not(.page) .legal .fmeta{font-size:.56rem;gap:4px 10px;flex-wrap:wrap;row-gap:2px}
  body:not(.page) .legal .fmeta a{font-size:.56rem}
  body:not(.page) .legal .flegal{gap:10px}
}

/* SHORT PHONES. On a 667 or 780 tall screen the film, the question and the
   footer still do not fit together, and the question block is the largest of
   the three at 337px. Everything here is spacing and type scale on that block
   only; no content is removed and nothing is hidden, so the step reads the same,
   just tighter. Height-scoped, so a normal tall phone keeps the roomier setting. */
@media (max-width:900px){
  body:not(.page) .stage .h1,body:not(.page) .stage h1{font-size:1.62rem;line-height:1.16;margin-bottom:8px}
  body:not(.page) .stage .help{font-size:.82rem;line-height:1.45;margin-bottom:12px}
  body:not(.page) .stage .kicker{margin-bottom:8px}
  body:not(.page) .rail{margin-bottom:var(--s-4)}
}

.rail .stats .t-short{display:none}
@media (max-width:900px){
  /* Video first. The film is the strongest thing on the intro step, and under
     the copy it was below the fold on most phones. */
  body:not(.page) .shell{display:flex;flex-direction:column}
  body:not(.page) .rail{order:-1;margin-top:0;margin-bottom:var(--s-6)}

  /* safe centre: centres while the column fits, and falls back to start the
     moment it is taller than the viewport. Plain `center` would push the top of
     a tall step off-screen with no way to scroll back up to it. */
  body:not(.page) .shell{justify-content:safe center}

  /* Four across. Labels swap to their short forms; the figures are unchanged. */
  body:not(.page) .rail .stats{grid-template-columns:repeat(4,minmax(0,1fr))}
  body:not(.page) .rail .stats div{padding:10px 6px;text-align:center}
  body:not(.page) .rail .stats b{font-size:1.05rem}
  body:not(.page) .rail .stats .t-full{display:none}
  body:not(.page) .rail .stats .t-short{
    display:block;font-size:.62rem;letter-spacing:.05em;white-space:nowrap;line-height:1.3;
  }
}

/* ============================================================================
   Founder note. A portrait beside her own words, set in the display face at
   lede size: it is a statement, not a pull quote competing with the section
   headings around it. No card, no border; a hairline and the portrait do the
   containing. The photo is optional and the quote holds on its own.
   ========================================================================= */
.founder{
  margin:0;display:grid;gap:var(--s-6);align-items:center;
  justify-items:center;text-align:center;
}
@media (min-width:760px){
  .founder{
    grid-template-columns:auto minmax(0,1fr);
    gap:clamp(28px,4vw,56px);justify-items:start;text-align:left;
  }
}
.founder .fportrait{
  width:clamp(112px,15vw,168px);aspect-ratio:1;flex:none;
  border-radius:50%;overflow:hidden;background:var(--ink-3);
  box-shadow:inset 0 1px 0 rgba(255,206,133,.28), 0 18px 40px -26px rgba(0,0,0,.9);
}
.founder .fportrait img{
  width:100%;height:100%;object-fit:cover;object-position:50% 20%;display:block;
}
.founder blockquote{margin:0;max-width:52ch}
.founder .fq{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.16rem,2.1vw,1.62rem);line-height:1.42;
  letter-spacing:var(--tr-heading);color:#fbf7f2;margin:0 0 var(--s-4);
  text-wrap:pretty;
}
.founder .fby{display:grid;gap:2px}
.founder .fby b{font-size:.95rem;font-weight:600;color:var(--text);letter-spacing:-.005em}
.founder .fby span{font-size:.82rem;color:var(--muted-2);letter-spacing:.012em}
@media (max-width:700px){
  .founder .fq{font-size:1.06rem;line-height:1.5}
}

/* With no portrait the two-column grid kept an empty first column half the
   width of the section. One child, one column, and it needs its own
   justify-items/text-align back to centre: the two-column rule above sets
   both to start/left unconditionally, which a lone centred column inherited
   and read as the quote flushed left with the whole right half empty. */
@media (min-width:760px){
  .founder:not(:has(.fportrait)){
    grid-template-columns:minmax(0,1fr);justify-items:center;text-align:center;
  }
}

/* ============================================================================
   Funnel intro on desktop, 2026-08-12.
   ========================================================================= */
@media (min-width:901px){
  /* Centred in the window rather than parked at the top. `safe center` so a
     step taller than the viewport falls back to the top and stays reachable,
     which plain `center` does not.

     NO `min-height:100dvh` HERE, and this is the whole of the no-scroll bug.
     `header.top` is 90px and sits ABOVE the shell in the same flex column, so a
     shell asking for a full 100dvh makes the document 100dvh + 90 every time:
     measured at 1440x760 the header was 90 and the shell 691 against a 691
     viewport, overflowing by exactly 90, and 1280x640 overflowed by exactly 90
     as well. An overflow that equals a sibling's height is that sibling.

     The remainder is already handled: `body:not(.page)` is a flex column at
     100dvh and the shell is `flex:1 1 auto; min-height:0`, which is what the
     repo map means by "the body is a flex column and the shell takes the
     remainder". Asking for the viewport height a second time double-counts it.
     `safe center` still centres, because flex has given the shell the leftover
     height to centre within. */
  body:not(.page) .shell{align-content:safe center;align-items:safe center}
}

/* The reel and the stat block are one object, not two. They were separate
   rounded boxes with a gap between them, which read as an image that happened
   to have a table under it. Joined: the reel loses its bottom corners, the
   stats lose their top corners and their top border, and the seam between them
   is the reel's own edge. */
.rail .hero{display:grid;gap:0}
.rail .hero .reel{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:0}
.rail .hero .stats{
  border-top-left-radius:0;border-top-right-radius:0;border-top:0;
  background:var(--line);width:100%;
}
.rail .hero .stats div{background:var(--ink-2)}
/* On a phone the stats sit INSIDE the frame (absolute, bottom:0), not under it,
   so the join above squared off the film's bottom corners while the stat row
   kept its rounded ones: footage showed in both corners (Siavash, 2026-10-04).
   Here the film keeps all four corners and clips its own video to them. */
@media (max-width:900px){
  body:not(.page) .rail .hero .reel{border-radius:var(--radius)}
}

/* The funnel wash shows the upper part of the frame. The film is 406x720
   portrait behind a landscape window, so `cover` crops hard top and bottom and
   the default 50% centre cut sat on torsos and worktops. Pulling the focal
   point up puts faces and hands in frame instead. Funnel only: the landing page
   wash is blurred to 56px, where the crop makes no visible difference. */
body:not(.page) #amb video,
body:not(.page) #amb img{object-position:50% 16%}

/* A light blur on the funnel wash at desktop. The landing page runs 56px, the
   funnel phone rule already runs 16px, and this sits at 11px: enough to stop
   the footage competing with the one question on screen, not so much that it
   stops reading as a room full of people. The slight scale-up keeps the blur
   from thinning the edges of the frame. */
@media (min-width:901px){
  body:not(.page) #amb video,
  body:not(.page) #amb img{
    filter:saturate(.9) contrast(1.02) blur(11px);transform:scale(1.06);
  }
}

/* ============================================================================
   "Have a Question?" modal.

   Centred over a blurred veil rather than anchored to the control. Anchored, it
   landed somewhere different on every surface (nav, funnel bar, footer, sheet)
   and read as though it had appeared at random. The veil is fixed and inset:0,
   so no ancestor overflow can clip it, which the anchored version had to fight.
   ========================================================================= */
.askveil{
  position:fixed;inset:0;z-index:200;display:grid;place-items:center;
  padding:var(--s-4);
  background:rgba(12,10,15,.62);
  -webkit-backdrop-filter:blur(9px) saturate(.9);backdrop-filter:blur(9px) saturate(.9);
  animation:askIn 160ms ease-out;
}
@keyframes askIn{from{opacity:0}to{opacity:1}}
/* REDESIGNED 2026-08-25. Borderless per the house rule: the panel is separated
   from the veil by its own ground, a soft top highlight and a deep shadow, not
   by a drawn 1px rectangle. `border:0` is EXPLICIT -- removing a border without
   setting it uncovers the UA one, and the focus rings below are kept for the
   same reason (deleting a border is how a focus ring quietly disappears). */
.askpop{
  position:relative;width:min(380px,100%);
  background:var(--ink-3);border:0;border-radius:20px;
  padding:var(--s-6) var(--s-4) var(--s-4);
  box-shadow:0 30px 80px rgba(0,0,0,.7), inset 0 1px 0 rgba(255,255,255,.055);
}
/* Small, quiet, and out of the reading path: the chips are the action here. */
.askpop .askx{
  position:absolute;top:9px;right:9px;width:30px;height:30px;
  display:grid;place-items:center;padding:0;
  background:transparent;border:0;border-radius:9px;cursor:pointer;
  color:var(--muted-2);transition:color 150ms,background 150ms;
}
.askpop .askx:hover{color:var(--text);background:rgba(255,255,255,.06)}
.askpop .askx:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.askpop .askx svg{width:17px;height:17px}
.askpop .askt{
  margin:0 0 14px;padding:0 10px;font-size:1.06rem;font-weight:600;
  color:var(--text);line-height:1.3;letter-spacing:-.01em;
}
/* One column of rows, not a wrapping chip cloud. Five chips of unequal width
   wrapped so that "Something else" sat alone on a second line, which is what
   read as untidy. This is a menu, so it is shaped like one. */
.askchips{display:grid;grid-template-columns:1fr;gap:2px}
.askpop .askchip{
  display:grid;grid-template-columns:34px 1fr 16px;align-items:center;gap:12px;
  width:100%;text-align:left;
  border:0;background:transparent;border-radius:13px;
  padding:10px;cursor:pointer;font:inherit;font-size:.95rem;color:var(--text);
  transition:background 150ms;
}
.askpop .askchip:hover{background:rgba(255,255,255,.055)}
.askpop .askchip:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The icon tile. Gold at low alpha rather than another grey box, so the row has
   one warm anchor and the label stays the loudest thing in it. */
.askpop .aski{
  width:34px;height:34px;display:grid;place-items:center;border-radius:11px;
  background:rgba(255,206,133,.10);color:var(--gold);
}
.askpop .aski svg{width:18px;height:18px}
.askpop .askl{min-width:0}
/* The arrow appears on hover and focus only: five arrows sitting there
   permanently is five times the visual noise for the same information. */
.askpop .askgo{
  width:16px;height:16px;color:var(--muted-2);
  opacity:0;transform:translateX(-4px);transition:opacity 150ms,transform 150ms;
}
.askpop .askchip:hover .askgo,
.askpop .askchip:focus-visible .askgo{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){
  .askpop .askgo{transition:none;transform:none}
}
.askpop .askfoot,
.askpop .asknote{margin:14px 0 0;padding:0 10px;font-size:.75rem;color:var(--muted-2);line-height:1.5}
.askpop .asknote{color:var(--muted)}

@media (prefers-reduced-motion:reduce){.askveil{animation:none}}

/* The control itself. It keeps whatever box the old tel: link had, so the nav,
   the funnel bar and the footer each keep their own sizing; only the mark and
   the gap are added here. */
.tel[role="button"],
.callus[role="button"],
.callbtn[role="button"]{cursor:pointer}
.tel[role="button"] svg,
.callus[role="button"] svg,
.callbtn[role="button"] svg{
  width:1em;height:1em;margin-inline-end:.45em;vertical-align:-.12em;flex:none;
}
/* The narrow-bar control carries the same words as every other surface, just
   smaller. Icon-only, it gave no clue that it opened a question picker rather
   than dialling.

   NO `display` HERE. `.callbtn{display:none}` is the default and the button is
   revealed only by `.nav .callbtn{display:grid}` inside the narrow-bar media
   query. Setting display on this unscoped selector overrode that default at
   EVERY width, so the mobile-only control appeared on the desktop nav as a
   second "Have a Question?" under the real one. The display now lives with the
   rule that already owns it, below. */
.callbtn[role="button"]{
  width:auto;padding:0 12px;
  font-size:.74rem;letter-spacing:.01em;white-space:nowrap;color:var(--text);
}
.callbtn[role="button"] svg{width:15px;height:15px;margin-inline-end:.4em}

/* ---------------------------------------------------------------------------
   Open house: two measured corrections.

   1. ANCHOR CLEARANCE. The date plate is pulled up out of the section's top
      padding, so this is the one section whose first ink sits above where the
      section box starts. Jumped to, its plate landed 130px behind the sticky
      nav while the other five sections cleared it by 5-6px. Measured at 1440:
      plate top 38, nav bottom 168. This is scoped to #openhouse for that
      reason: a blanket `.sec{scroll-margin-top}` would have "fixed" five
      sections that were never broken.
   2. THE GAP BELOW. Card bottom to the next heading measured 250px at 1440 and
      149px at 390, because the open house section's bottom padding and the next
      section's top padding meet with a fully-padded card between them. The card
      already carries up to 40px of its own padding, so it reads as a hole. The
      bottom padding is halved; the top is untouched, since that side is doing
      real work separating the card from the section above it.
   ------------------------------------------------------------------------ */
#openhouse{
  scroll-margin-top:calc(var(--head,100px) + 14px);
  padding-bottom:calc(var(--band-tight) * .5);
}

/* The domain-warp field behind the founder quote. It replaces the band's flat
   fill, so the band's own background stays as the fallback underneath: if
   webgl.js returns early for any reason, nothing here shows a hole. */
#founder{position:relative;isolation:isolate;overflow:hidden}
#founder > .wrap{position:relative;z-index:1}
.qfield{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;display:block;
  pointer-events:none;
  /* Held back so the quote wins. The field is depth, not subject. */
  opacity:.85;
}

/* The vaporize wordmark under the preloader's scanner mark. Sized in JS from
   the measured canvas, so it cannot overflow the way the old text wordmark did.
   Block-level and centred; the mark is already a centred column. */
#pre .vapor{display:block;margin:0 auto;pointer-events:none}

/* ---------------------------------------------------------------------------
   Part one, narrow: the last 28px of the no-scroll rule.

   Measured at 390x844 after the 100dvh fix: stage 317 + column gap 28 + rail
   331 (307 reel + 24 margin) + shell padding 28 = 704, against 676 of usable
   height below a 91px header. Exactly 28px over.

   Taken out of the space BETWEEN things and off the reel, never off the type or
   the controls: a step that fits because its tap targets shrank is not a step
   that fits. apply-2 already fitted at this size and is untouched by these
   numbers, since it carries no rail on the step that was tight.
   ------------------------------------------------------------------------ */
@media (max-width:900px){
  body:not(.page) .shell{gap:18px;padding-bottom:calc(14px + env(safe-area-inset-bottom))}
  body:not(.page) .rail{margin-bottom:10px}
  /* The reel cap is NOT what was binding: 34vh at 844 is 261px, already under
     any cap worth setting, so lowering the ceiling changed nothing. Kept as a
     ceiling for short-and-wide viewports, where vh is the larger number. */
  body:not(.page) .reel{height:min(34vh,278px)}
}

/* The row's own rule draws itself in on reveal, left to right, the same drawn
   idiom the nav links and the open-house headline already use. scaleX, never
   width. It sits ON the existing hairline rather than adding a second line, so
   an unarmed or no-JS render still shows the finished structure. */
.ledger .row::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;
  background:linear-gradient(90deg,rgba(255,206,133,.42),rgba(255,206,133,.06) 46%,rgba(255,206,133,0));
  transform:scaleX(0);transform-origin:left center;
  transition:transform 700ms var(--ease);
}
.ledger .row.rise-in::after{transform:scaleX(1)}
@media (prefers-reduced-motion: reduce){
  .ledger .row::after{transition:none;transform:scaleX(1)}
}
/* Narrow: the numeral sits on its own line above the term rather than eating a
   third of a 390px row. */
@media (max-width:779px){
  .ledger .row{grid-template-columns:1fr;gap:var(--s-2)}
  /* margin-top back to 0. Stacked, the figure sits directly under the row's
     divider and the desktop optical pull made it straddle that hairline. On a
     wide row the numeral is beside the term with the rule far above it, which
     is a different relationship entirely. */
  .ledger .idx{font-size:1.9rem;margin-top:0;margin-bottom:2px;line-height:1}
}

/* 2026-10-07: one Add to calendar per campus row in the open house card, under
   that campus's address. Borderless like the rest of the card; the menu opens
   upward from the button as before. */
.oh-cal{margin-top:var(--s-3);justify-self:start}
.oh-cal .btn{min-height:40px;padding-block:8px;font-size:.88rem}
/* the tour cell is one link when it carries the booking url */
.facts a.fact{color:inherit;text-decoration:none;cursor:pointer}
.facts a.fact:hover b{color:var(--gold)}
.facts a.fact:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.oh-cal .btn{width:auto;white-space:nowrap;gap:8px}
.oh-cal .btn svg{width:18px;height:18px;flex:none}
