/*
 * Gymnastics Plus homepage.
 *
 * This stylesheet is the approved mockup's stylesheet, reproduced verbatim. Selectors,
 * values, breakpoints and ordering are the mockup's own. Do not "align" any of it to the
 * theme's token layer, the frozen component library, or the Podium ramp: the owner asked
 * for this exact page, and the divergences (Inter/Anton/Oswald rather than Poppins, the
 * mockup's own :root palette, .btn rather than .gp-btn) are the design, not drift.
 *
 * Fonts (Anton, Oswald, Inter) are enqueued in inc/gp-homepage-functions.php.
 * Adjustments start from here, on the owner's word, one at a time.
 *
 * PALETTE DEDUP (2026-08-07, Phase 0 of docs/plans/2026-08-07-public-surface-token-layer.md):
 * the mockup's indistinguishable duplicate colours are collapsed, same-job pairs only,
 * approved in that plan. RGB distance beside each (1.0 = one channel step):
 *   #101114 -> #111215            (1.7)  gradient stop joins the flat card surface colour
 *   #6E7077 -> var(--slate)       (3.3)  one-off muted grey joins the slate text colour
 *   --black-2 #141518 -> #17181B  (5.2)  dark chip surface shares the ink value; the
 *                                        variable stays because its job (surface, not text)
 *                                        differs and Phase 2 maps them to separate semantics
 *   --gold-light removed          (0.0)  it was byte-identical to --gold
 * Nothing else may be chain-merged: the remaining near-blacks are 5.8+ apart with
 * distinct jobs (gradient stops, the .btn-dark hover state). See the plan.
 */

:root{
  --black:var(--gpp-color-bg-invert);
  --black-2:var(--gpp-color-text);
  --black-3:var(--gpp-color-surface-hover);
  --gold:var(--gpp-color-accent);        /* fills */
  --gold-text:var(--gpp-color-accent-text); /* text; darkens on light, see gp-public.css */
  --crimson:var(--gpp-color-brand-dot);
  --cream:var(--gpp-color-bg);
  --cream-2:var(--gpp-color-bg-alt);
  --ink:var(--gpp-color-text);
  --slate:var(--gpp-color-text-muted);
  --line:var(--gpp-line);
  --line-dark:var(--gpp-line-dark);
}
*{box-sizing:border-box; margin:0; padding:0;}
html{scroll-behavior:smooth;}
body{
  font-family:'Inter', sans-serif;
  background:var(--cream);
  color:var(--ink);
  -webkit-font-smoothing:antialiased;
  -webkit-text-size-adjust:100%;
  overflow-x:clip;
}
img,svg{display:block; max-width:100%;}
a{color:inherit; text-decoration:none;}
/* THE HEADLINE FACE. Owner direction 2026-08-17: Montserrat, replacing Anton, which had been
   the settled answer since the mockup. This is the class on the two biggest statements on the
   page, the hero H1 and the closing CTA H2. Anton stays everywhere else it is used: the
   wordmark, the score numbers, the stat strip, the section heads and the prices.

   800, and the tracking goes slightly negative. Anton is a single ultra-condensed weight that
   carries a headline on its own; Montserrat at a normal weight would not, and at large sizes a
   geometric sans set in caps needs its default tracking pulled in or the words drift apart. */
.display{font-family:'Montserrat', sans-serif; font-weight:800; text-transform:uppercase;
         letter-spacing:-0.015em;}
.label{font-family:'Oswald', sans-serif; text-transform:uppercase; letter-spacing:0.14em; font-weight:600;}
.wrap{max-width:1180px; margin:0 auto; padding:0 32px;}
section{position:relative;}

/* ---------- BUTTONS ---------- */
.btn{
  display:inline-flex; align-items:center; gap:8px;
  font-family:'Oswald', sans-serif; font-weight:600; letter-spacing:0.04em;
  text-transform:uppercase; font-size:14px;
  padding:15px 28px; border-radius:4px; border:1px solid transparent;
  cursor:pointer; transition:transform .15s ease, box-shadow .15s ease, background .15s ease;
}
.btn:hover{transform:translateY(-2px);}
/* The primary button, expressed as intent rather than as a colour. In dark these resolve to
   exactly what they always were, gold on black, so nothing moves. In light they become ink on
   white, because gold on white is 1.85:1 and the most important control on the page cannot be
   the one whose edge dissolves. Owner called this before the measurement did. */
.btn-gold{background:var(--gpp-color-cta); color:var(--gpp-color-cta-ink);}
.btn-gold:hover{background:var(--gpp-color-cta); box-shadow:0 8px 24px var(--gpp-tint-accent-35);}
.btn-ghost{background:transparent; color:var(--gpp-color-text-invert); border-color:var(--gpp-tint-white-28);}
.btn-ghost:hover{border-color:var(--gpp-color-surface); background:var(--gpp-tint-white-06);}
.btn-dark{background:var(--black); color:var(--gpp-color-text-invert);}
.btn-dark:hover{background:var(--black-3); box-shadow:0 8px 24px rgba(0,0,0,0.3);}
.btn-lg{padding:17px 40px;}

/* ---------- NAV ---------- */
/* NO RULE UNDER THE HEADER. Owner direction 2026-08-17. The mockup drew a hairline between the
   header and whatever follows, which earns its keep on a light page: it separates two white
   surfaces. Here the header is black and the hero is black, so the only thing it separated was
   black from black, and what it actually read as was a seam across the top of the page. Removed
   rather than recoloured: two surfaces of the same colour do not need a border, they need to be
   allowed to be one surface. */
header{
  position:sticky; top:0; z-index:50;
  background:var(--black);
}
nav{display:flex; align-items:center; justify-content:flex-end; padding:18px 32px; max-width:1180px; margin:0 auto;}
.logo{display:flex; align-items:center; gap:8px; margin-right:auto; color:var(--gpp-color-text-invert); font-family:'Anton',sans-serif; font-size:22px; letter-spacing:0.02em;}
.logo span{color:var(--gpp-color-accent-mark);}
.logo-img{height:30px; width:auto;}
.nav-links{display:flex; gap:32px; align-items:center; margin-left:auto;}



/* The phone halves both intervals. 160px of white between sections is generous on a 1280px page
   and absurd on a 390px one, where it reads as the content having run out. */
@media (max-width: 900px){
  }




/* The page behind the page. Every band paints its own background so this is invisible in normal
   scrolling, but it is what shows in the overscroll bounce at the top and bottom of a phone, and a
   neutral grey there under a cream page reads as a seam. Cheap to get right, conspicuous when
   wrong. */
/* The mission heading. This carried style="color:#fff !important" inline in mission.php, which
   is the one colour on this surface that no token could reach and no guard could see: the CSS
   hex guard only reads stylesheets, and an inline style beats every rule regardless. In light it
   was white on cream at 1.09:1, i.e. invisible, and it survived a full manual sweep of the
   rendered page before a test caught it. The !important is kept because the rule it was added to
   defeat is still there; what changes is that the value is now a token and follows the theme. */
.mission .gp-mission__title{ color:var(--gpp-color-text-invert) !important; }


/* The wordmark is the one element that cannot resolve through a token, so the mark itself is
   swapped. Default (dark theme) shows the white mark; light shows the ink one. */


header a, header a:hover, header a:focus{text-decoration:none !important; border-bottom:none !important; box-shadow:none !important;}
.nav-links a{
  color:var(--gpp-color-text-nav); font-family:'Oswald',sans-serif; font-size:18px; font-weight:800;
  letter-spacing:0.06em; text-transform:uppercase; transition:color .15s;
  display:inline-flex; align-items:center; gap:4px;
}
.nav-links a:not(.btn):hover{color:var(--gpp-color-text-invert);}
/* The nav CTA's label is the CTA's ink, not "black". It read color:var(--black), which is correct
   in dark by coincidence: --black is the page-INVERT role, not a literal, so in light it resolves
   to WHITE and this put white text on the gold button at 1.89:1. The button was unreadable and
   the rule looked right, which is the whole hazard of a token named for a colour rather than a
   job. It is (0,2,1) so it also outranks .btn-gold, meaning it silently won.
   Same value as .btn-gold now, from the same token, so the two cannot diverge again. */
.nav-links a.btn{
  font-family:'Oswald',sans-serif; font-size:15px; font-weight:800; letter-spacing:0.06em;
  color:var(--gpp-color-cta-ink); margin-left:4px;
}
.nav-links a.btn:hover{color:var(--gpp-color-cta-ink);}
.nav-toggle{
  display:none; background:transparent; border:0; cursor:pointer;
  width:44px; height:44px; margin-left:12px; padding:0;
  flex-direction:column; align-items:center; justify-content:center; gap:5px;
}
.nav-toggle span{
  display:block; width:22px; height:2px; background:var(--gpp-color-text-invert); border-radius:2px;
  transition:transform .25s ease, opacity .18s ease;
}
.nav-toggle.open span:nth-child(1){transform:translateY(7px) rotate(45deg);}
.nav-toggle.open span:nth-child(2){opacity:0;}
.nav-toggle.open span:nth-child(3){transform:translateY(-7px) rotate(-45deg);}

/* ---------- HERO ---------- */
/* SOLID BLACK. Owner direction 2026-08-17: the whole hero section black, not the brownish gold
   gradient it was. The mockup laid two radial washes over the black, a gold bloom from the top
   left and a crimson one from the top right, and at low opacity over a 40-second film they read
   as a brown tint on the section rather than as light. Both gone; the tint tokens they used stay
   defined for the rest of the page. */
.hero{
  background:var(--black);
  padding:88px 0 110px;
  overflow:hidden;
}
.hero-grid{
  display:flex; align-items:flex-start; justify-content:center; gap:48px; flex-wrap:wrap;
}
.eyebrow{
  display:inline-flex; align-items:center; gap:8px;
  color:var(--gold-text); font-family:'Oswald',sans-serif; font-size:12px; font-weight:600;
  letter-spacing:0.16em; text-transform:uppercase;
  border:1px solid var(--gpp-tint-accent-40); border-radius:100px; padding:7px 16px;
  margin-bottom:24px; background:var(--gpp-tint-accent-06);
}
.eyebrow::before{content:''; width:6px; height:6px; border-radius:50%; background:var(--crimson);}
/* SIZED FOR MONTSERRAT, not for Anton. MEASURED at the 428px column: Anton held this headline at
   66px, and Montserrat 800 at the same size runs to five lines and overflows, because a geometric
   sans in caps is roughly half again as wide as an ultra-condensed one. Swept the sizes: 48 and 44
   give four lines, 40 gives three at a widest line of 346. So 40 is the cap, and it keeps the film
   at its full 723px rather than paying for type with the product.
   line-height up from 1.02 to 1.06: a wider face needs a little more air between caps lines. */
.hero h1{
  color:var(--gpp-color-text-invert); font-size:clamp(28px,2.9vw,40px); line-height:1.06; margin-bottom:22px;
}
.hero h1 em{color:var(--gold-text); font-style:normal;}
.hero p.lede{
  color:var(--gpp-color-text-lede); font-size:18px; line-height:1.6; max-width:480px; margin-bottom:34px;
}
.hero-ctas{display:flex; gap:14px; flex-wrap:wrap; margin-bottom:34px;}
.hero-trust{display:flex; align-items:center; gap:12px; color:var(--gpp-color-text-muted-dark); font-size:13px;}
.avatars{display:flex;}
.avatars span{
  width:28px; height:28px; border-radius:50%; border:2px solid var(--black);
  margin-left:-8px; display:inline-block;
  background:linear-gradient(135deg,var(--gold),var(--crimson));
}
.avatars span:first-child{margin-left:0;}

/* signature scorecard mockup */
.mock{
  background:linear-gradient(180deg,var(--gpp-ink-650),var(--gpp-color-surface-dark));
  border:1px solid var(--line); border-radius:16px; padding:22px;
  box-shadow:0 40px 90px rgba(0,0,0,0.55);
  max-width:380px; flex:1 1 320px;
}
.mock-img{padding:0; overflow:hidden; line-height:0;}
.mock-photo{width:100%; height:auto; display:block;}
.mock-top{display:flex; align-items:center; justify-content:space-between; margin-bottom:18px;}
.mock-athlete{display:flex; align-items:center; gap:12px;}
.mock-pic{width:46px; height:46px; border-radius:10px; background:linear-gradient(135deg,var(--gold),var(--gpp-gold-shade)); flex-shrink:0;}
.mock-name{color:var(--gpp-color-text-invert); font-family:'Oswald',sans-serif; font-weight:600; font-size:15px;}
.mock-sub{color:var(--gpp-color-text-muted-dark); font-size:12px; margin-top:2px;}
.mock-badge{
  background:var(--gpp-tint-accent-14); color:var(--gold-text); font-family:'Oswald',sans-serif;
  font-size:11px; font-weight:600; letter-spacing:0.06em; padding:6px 10px; border-radius:6px;
  border:1px solid var(--gpp-tint-accent-30);
}
.mock-score{
  display:flex; align-items:baseline; gap:10px; background:var(--black-2); border-radius:12px;
  padding:18px; margin-bottom:16px; border:1px solid var(--line);
}
.mock-score .num{font-family:'Anton',sans-serif; font-size:44px; color:var(--gpp-color-text-invert); line-height:1;}
.mock-score .cap{color:var(--gpp-color-text-muted-dark); font-size:12px; font-family:'Oswald',sans-serif; letter-spacing:0.06em; text-transform:uppercase;}
.mock-bars{display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-bottom:16px;}
.mock-stat{background:var(--black-2); border:1px solid var(--line); border-radius:10px; padding:12px 14px;}
.mock-stat .v{color:var(--gpp-color-text-invert); font-family:'Oswald',sans-serif; font-weight:600; font-size:18px;}
.mock-stat .l{color:var(--gpp-color-text-muted-dark); font-size:11px; margin-top:2px;}
.mock-row{
  display:flex; align-items:center; gap:10px; background:var(--black-2); border:1px solid var(--line);
  border-radius:10px; padding:10px 14px; margin-bottom:8px;
}
.mock-dot{width:8px; height:8px; border-radius:50%; background:var(--gpp-color-success); flex-shrink:0;}
.mock-row span{color:var(--gpp-slate-100); font-size:13px;}

/* ---------- STAT BAR ---------- */
.statbar{background:var(--black); padding:0 0 70px;}

/* ---------- LOGO MARQUEE ---------- */
.logo-marquee{background:var(--gpp-color-surface); padding:64px 0 8px;}
/* ==========================================================================
   THE TWO MARQUEE LABELS ARE CAPTIONS, NOT HEADLINES
   ==========================================================================
   Owner: "Active NCAA Programs Recruiting on the Platform and Active Countries Represented in
   the Platform are not like other headlines. Should they be standing out, or should Your
   all-in-one solution and We exist to remove the guesswork stand out more?"

   The second. Measured, these two were the loudest text on the page after the hero:

     .marquee-label   44px / 400     "Active NCAA Programs Recruiting on the Platform"
     .flag-label      38px / 700     "Active Countries Represented in the Platform"
     h2.sec-title     32px / 700     every actual section heading

   So the captions outranked the headings by 12 and 6 pixels. This is the same defect fixed on
   2026-08-17 one level up, where the section heads had grown to match the hero headline: a thing
   that merely LABELS had been given the size of a thing that ARGUES.

   And they are labels. "Active NCAA Programs Recruiting on the Platform" describes what you are
   looking at; it does not make a case. The two section headings are the argument, and they are
   what should carry the page. This is the standard logo-wall pattern for exactly that reason: a
   small quiet line over the proof, never a headline competing with the pitch.

   They take the eyebrow's own treatment, which already exists in this file for section eyebrows,
   so the page gains no new type style. Muted rather than the eyebrow's crimson: crimson is an
   accent that draws the eye, and the whole point here is to stop drawing it. The result is a
   clean three-step ramp: hero 40/800, section headings 32/700, captions 13/600 uppercase.
   ========================================================================== */
.marquee-label,
.flag-label{
  text-align:center; text-wrap:balance;
  font-family:'Oswald',sans-serif; font-weight:600;
  font-size:15px; letter-spacing:0.13em; text-transform:uppercase; line-height:1.4;
  margin-bottom:28px;
}

/* SHARED TYPE, SEPARATE COLOUR, because the two sit on OPPOSITE surfaces in the dark theme and a
   single muted token cannot serve both. The logo wall is one of the dark theme's WHITE bands; the
   flag strip lives inside the black mission section. Giving both the dark-surface muted put the
   marquee label at 3.69:1 on white, which the contrast row caught immediately.
   Each now takes the muted token for the ground it actually stands on, and both hold in both
   themes: marquee 4.9:1 on white either way, flags 5.5:1 on black and 4.9:1 on white. */
.marquee-label{ color:var(--gpp-color-text-muted); }
.flag-label{ color:var(--gpp-color-text-muted-dark); }
.marquee-outer{
  width:100%; overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg, transparent, var(--gpp-color-bg-invert) 8%, var(--gpp-color-bg-invert) 92%, transparent);
  mask-image:linear-gradient(90deg, transparent, var(--gpp-color-bg-invert) 8%, var(--gpp-color-bg-invert) 92%, transparent);
}
.marquee-track{
  display:flex; align-items:center; gap:22px; width:max-content;
  animation:marquee-scroll 46s linear infinite;
}
.marquee-outer:hover .marquee-track{animation-play-state:paused;}
.marquee-item{
  flex-shrink:0; background:var(--gpp-color-surface); border:1px solid var(--line-dark); border-radius:10px;
  padding:8px 10px; display:flex; align-items:center; justify-content:center;
}
.marquee-item img{height:64px; width:auto; display:block; border-radius:4px;}
@keyframes marquee-scroll{
  from{transform:translateX(0);}
  to{transform:translateX(-50%);}
}
@media (prefers-reduced-motion: reduce){
  .marquee-track{animation:none;}
}
.stat-cards{
  display:grid; grid-template-columns:repeat(4,1fr); gap:1px;
  background:var(--line); border:1px solid var(--line); border-radius:12px; overflow:hidden;
  transform:translateY(-46px); max-width:980px; margin-left:auto; margin-right:auto;
}
.stat-card{background:var(--gpp-color-surface-dark); padding:28px 22px; text-align:center; text-wrap:balance;}
.stat-card .num{font-family:'Anton',sans-serif; color:var(--gold-text); font-size:clamp(26px,2.6vw,34px);}
/* 13px, up from 11. Owner asked whether these should be bigger; measured, they should.
   UPPERCASE AND TRACKED READS SMALLER THAN ITS NUMBER. There are no ascenders or descenders to
   give the eye word-shape, and the tracking pulls the letters further apart, so 11px set this way
   lands nearer 9px of ordinary text. It was also the smallest type on the page while carrying the
   most weight: "292+" means nothing without "Gymnasts Profiles" beside it, and the label was set
   four pixels smaller than the marquee caption, which does far less work.
   ROOM WAS NEVER THE CONSTRAINT: the longest label, "Countries Represented", inks 121px inside a
   200px content box, so 79px was going spare. The number still leads at 33 against 13, a 2.5:1
   ratio that keeps the figure the loudest thing in the card. */
.stat-card .lbl{color:var(--gpp-color-text-muted-dark); font-family:'Oswald',sans-serif; font-size:13px; letter-spacing:0.06em; margin-top:6px; text-transform:uppercase; line-height:1.3;}

/* ---------- SECTION HEADERS ---------- */
.sec-head{text-align:center; text-wrap:balance; max-width:640px; margin:0 auto 48px;}
.sec-eyebrow{color:var(--crimson); font-family:'Oswald',sans-serif; font-weight:600; font-size:13px; letter-spacing:0.14em; text-transform:uppercase; margin-bottom:12px; display:block;}
.sec-head h2{font-size:clamp(28px,3.4vw,42px); line-height:1.08; margin-bottom:14px;}
.sec-head p{color:var(--slate); font-size:16px; line-height:1.6;}

/* ---------- TABS SECTION ---------- */
.tabs-section{padding:100px 0 90px; background:var(--gpp-color-surface);}
.tab-nav{display:flex; justify-content:center; gap:12px; flex-wrap:wrap; margin-bottom:44px;}
.tab-btn{
  font-family:'Oswald',sans-serif; font-weight:600; font-size:16px; letter-spacing:0.06em;
  text-transform:uppercase; padding:16px 32px; border-radius:100px; border:1.5px solid var(--line-dark);
  background:var(--gpp-color-surface); color:var(--slate); cursor:pointer; transition:all .15s;
}
.tab-btn.active{background:var(--ink); color:var(--gpp-color-surface); border-color:var(--ink);}
.tab-panel{display:none;}
.tab-panel.active{display:grid;}
.tab-card{
  display:grid; grid-template-columns:1fr 1fr; gap:48px; align-items:center;
  background:var(--gpp-color-surface); border:2px solid var(--ink); border-radius:18px; padding:48px;
}
/* THE RULE: balance is for CENTRED text, pretty is for LEFT-ALIGNED text.
   Measured: .tabs-section h2 computes text-align center, .tab-card h3 computes
   start. Balancing a left-aligned heading manufactures two similar-length lines,
   which is what centred text wants and what a ragged right edge does not, and it
   optimises for line LENGTH rather than phrasing, so it broke "Build your list
   and / reach your top schools" after a conjunction. pretty leaves the natural
   ragged edge alone and only protects the last line from a stranded word. */
.tab-card h3{font-size:26px; margin-bottom:14px; font-family:'Montserrat',sans-serif; font-weight:700; text-wrap:pretty;
             text-transform:none; letter-spacing:-0.01em;}
.tab-card ul{list-style:none;}
.tab-card li{
  display:flex; gap:10px; align-items:flex-start; padding:9px 0; color:var(--gpp-color-text-list); font-size:15px; border-top:1px solid var(--line-dark);
}
.tab-card li:first-of-type{border-top:none;}
.tab-check{
  width:18px; height:18px; border-radius:50%; background:var(--gold); flex-shrink:0; margin-top:2px;
  display:flex; align-items:center; justify-content:center;
}
.tab-check svg{width:10px; height:10px;}
.tab-visual{
  background:var(--cream-2); border-radius:14px; padding:26px; min-height:220px;
  display:flex; flex-direction:column; gap:10px; justify-content:center;
}
.tv-row{background:var(--gpp-color-surface); border:1px solid var(--line-dark); border-radius:9px; padding:12px 16px; display:flex; justify-content:space-between; align-items:center; font-size:13px;}
.tv-row b{font-family:'Oswald',sans-serif;}
.tv-pill{background:var(--black); color:var(--gpp-color-text-invert); font-family:'Oswald',sans-serif; font-size:10px; letter-spacing:0.06em; padding:4px 9px; border-radius:100px; text-transform:uppercase;}
.tab-visual-media{padding:0; overflow:hidden; box-shadow:0 20px 50px rgba(0,0,0,0.16); border:1px solid var(--line-dark);}

/* ---------- FLAG MARQUEE (dark) ---------- */
/* .flag-label is styled with .marquee-label above: same job, same treatment. */

.flag-outer{margin-bottom:70px;}
/*
 * NO animation-duration OF ITS OWN, deliberately. Owner, 2026-09-08: "wow the countries are
 * going to fast make them go the same speed as the universities." This element carries
 * .marquee-track as well, so deleting the override IS the instruction: it now inherits the
 * universities' 46s, and the two can never drift apart again because there is one value.
 * Setting 46s here would have matched them today and let the next change to one of them
 * separate the pair silently.
 *
 * WHAT THE MEASUREMENT ACTUALLY SAID, because it did not agree with the impression and the
 * comment should record that rather than pretend it did. On dev at 1280, before this change:
 * universities 6794px track, 46s, 73.8 px/sec; countries 3895px, 38s, 51.3 px/sec. The flags
 * were already the SLOWER of the two in pixels per second. What made them read as fast is
 * repetition, not velocity: 6 distinct flags against 24 distinct logos, so the same flag comes
 * back around several times in one pass while a logo rarely does, and the shorter 38s cycle
 * made that worse. Matching the duration is the owner's literal instruction AND it moves in
 * the direction he asked, so it is the right change; but if it still reads fast, the lever is
 * more countries or a longer duration for both, not a smaller number here.
 */
.flag-track{
  gap:26px;
  animation-direction:reverse;
}
.flag-item{
  flex-shrink:0; background:var(--gpp-tint-white-05); border:1px solid var(--line);
  border-radius:10px; padding:12px 14px;
  display:flex; align-items:center; justify-content:center;
}
.flag-item img{height:64px; width:auto; display:block; border-radius:3px;}

/* ---------- MISSION / FRICTION ---------- */
.mission{
  background:var(--black); color:var(--gpp-color-text-invert); padding:80px 0 50px; text-align:center; text-wrap:balance;
  background-image: radial-gradient(circle at 50% 0%, var(--gpp-tint-accent-10), transparent 60%);
}
.mission h2{font-size:clamp(26px,3.6vw,40px); max-width:820px; margin:0 auto 56px; line-height:1.15;}
.mission-cols{display:grid; grid-template-columns:repeat(3,1fr); gap:36px; margin-bottom:0; text-align:left; text-wrap:pretty;}
.mission-col .label{color:var(--gold-text); font-size:clamp(22px,2.3vw,28px); font-weight:700; letter-spacing:0.01em; text-transform:none; margin-bottom:14px; display:block; line-height:1.15;}
.mission-col p{color:var(--gpp-color-text-body-dark); font-size:14.5px; line-height:1.6;}
.quote-card{
  max-width:720px; margin:0 auto; background:var(--gpp-color-surface-dark); border:1px solid var(--line); border-radius:16px;
  padding:34px 40px; text-align:left; text-wrap:pretty; display:flex; gap:20px; align-items:flex-start;
}
.quote-mark{font-family:'Anton',sans-serif; font-size:44px; color:var(--gold-text); line-height:0.6;}
.quote-card p{color:var(--gpp-color-text-table); font-size:16px; line-height:1.6; margin-bottom:16px; font-style:italic;}
.quote-who{display:flex; align-items:center; gap:10px;}
.quote-pic{width:38px; height:38px; border-radius:50%; background:linear-gradient(135deg,var(--crimson),var(--gold));}
.quote-who .n{color:var(--gpp-color-text-invert); font-family:'Oswald',sans-serif; font-size:13.5px; font-weight:600;}
.quote-who .r{color:var(--gpp-color-text-muted-dark); font-size:12px;}

/* ---------- COMPARISON TABLE ---------- */
.compare{background:var(--black); padding:40px 0 110px;}
.compare-inner{
  background:linear-gradient(180deg,var(--gpp-color-surface-dark),var(--gpp-ink-900)); border:1px solid var(--line); border-radius:20px; padding:52px;
}
.compare-head{text-align:center; text-wrap:balance; margin-bottom:36px;}
.compare-head h2{color:var(--gpp-color-text-invert); font-size:clamp(24px,3vw,34px);}
table.ctable{width:100%; border-collapse:collapse; border:1px solid var(--gpp-tint-white-30);}
.ctable th{
  font-family:'Oswald',sans-serif; text-transform:uppercase; letter-spacing:0.08em; font-size:12px;
  color:var(--gpp-color-text-muted-dark); text-align:left; padding:14px 16px; border-bottom:1px solid var(--line);
}
.ctable th.center{font-size:15px; font-weight:700; color:var(--gpp-color-text-table); letter-spacing:0.1em;}
.ctable th.center, .ctable td.center{text-align:center;}
.ctable td{padding:18px 16px; border-bottom:1px solid var(--line); color:var(--gpp-color-text-table); font-size:14px;}
.ctable tr:last-child td{border-bottom:none;}
.ctable td.feature{color:var(--gpp-color-text-invert); font-weight:700; font-family:'Oswald',sans-serif; font-size:18px; letter-spacing:0.02em;}
.old-cost{color:var(--slate); font-size:13px;}
.gp-yes{
  display:inline-flex; align-items:center; gap:6px; background:var(--gpp-tint-accent-14); color:var(--gold-text);
  border:1px solid var(--gpp-tint-accent-30); font-family:'Oswald',sans-serif; font-size:11.5px; font-weight:600;
  padding:5px 12px; border-radius:100px; letter-spacing:0.03em;
}
/*
 * The tick is the glyph and nothing else. It used to sit in a 26px circle on a translucent
 * green wash with a green ring, which made a column of seven of them read as a column of
 * buttons rather than a column of answers. The cross never had a disc, so the two columns
 * were not even a pair. Both are now the same weight and size in their own colour, which is
 * all the table was ever saying.
 */
.cell-no{color:var(--gpp-color-danger); font-size:19px; font-weight:800;}
.cell-yes{color:var(--gpp-color-success); font-size:19px; font-weight:800;}
.cell-text{color:var(--gpp-color-text-body-dark); font-family:'Oswald',sans-serif; font-size:12.5px; font-weight:600; letter-spacing:0.03em; text-transform:uppercase;}
.compare-total{
  display:flex; justify-content:space-between; align-items:center; margin-top:28px; padding-top:28px;
  border-top:1px solid var(--line); flex-wrap:wrap; gap:20px;
}
.compare-total .cap{color:var(--gpp-color-text-muted-dark); font-family:'Oswald',sans-serif; font-size:12px; letter-spacing:0.08em; text-transform:uppercase;}
.compare-total .price{color:var(--gold-text); font-family:'Anton',sans-serif; font-size:30px;}

/* ---------- APP SECTION ---------- */
/* Bottom trimmed 110 -> 72. Owner on the closing CTA: "I think we should shorten the space above
   here, or add space to the bottom of this area." Shorten the top, and this padding was two
   thirds of it.
   MEASURED: 190px of white above that heading against 80px below its button, a 2.4:1 lean that
   made the CTA float rather than land. It was the largest gap on the page. Adding to the bottom
   instead would have put roughly 380px of white around one heading and one button and made the
   page longer for nothing.
   The top stays the LARGER of the two on purpose. Below the CTA the footer's black edge already
   does the separating; above it is white meeting white, where only distance can. 128 over 80 is
   about 1.6:1, which reads as deliberate where 2.4:1 read as a gap. */
.app-sec{padding:110px 0 72px; overflow:hidden; background:var(--gpp-color-surface);}
.app-sec .app-grid{
  display:grid; grid-template-columns:minmax(0,470px) minmax(0,470px);
  justify-content:center; gap:56px; align-items:center;
  box-sizing:border-box;
  width:100% !important; max-width:1080px !important;
  margin-left:auto !important; margin-right:auto !important;
  border:2px solid var(--ink); border-radius:20px; padding:48px 56px;
}
.app-grid > div:first-child{max-width:470px; justify-self:end;}
.app-grid h2{font-size:clamp(28px,3.4vw,40px); margin-bottom:16px; line-height:1.1;}
.app-grid p.lede{color:var(--slate); font-size:16px; line-height:1.6; margin-bottom:26px; max-width:440px;}
.app-list{margin-bottom:30px;}
.app-list li{display:flex; gap:12px; align-items:flex-start; padding:10px 0; list-style:none; font-size:15px; color:var(--gpp-color-text-list);}
.app-list b{font-family:'Oswald',sans-serif;}
.check-gold{
  width:20px; height:20px; border-radius:50%; background:var(--black); color:var(--gold-text); flex-shrink:0;
  display:flex; align-items:center; justify-content:center; font-size:11px; margin-top:1px;
}
.phone-img-wrap{
  justify-self:center; display:flex; gap:24px; align-items:center; justify-content:center;
}
.phone-real-img{width:205px; height:auto; display:block; filter:drop-shadow(0 30px 60px rgba(0,0,0,0.24));}

/* ---------- PRICING ---------- */
.pricing{padding:100px 0 120px;}
.price-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:26px; max-width:820px; margin:0 auto;}
.price-card{
  background:var(--gpp-color-surface); border:1px solid var(--line-dark); border-radius:18px; padding:38px 34px;
}
.price-card.pop{background:var(--black); color:var(--gpp-color-text-invert); position:relative; border-color:var(--black);}
.pop-tag{
  position:absolute; top:-13px; left:50%; transform:translateX(-50%);
  background:var(--gold); color:var(--black); font-family:'Oswald',sans-serif; font-size:11px; font-weight:700;
  letter-spacing:0.08em; text-transform:uppercase; padding:6px 16px; border-radius:100px;
}
.price-card .plan{font-family:'Oswald',sans-serif; text-transform:uppercase; letter-spacing:0.1em; font-size:13px; color:var(--slate); margin-bottom:14px;}
.price-card.pop .plan{color:var(--gpp-color-text-body-dark);}
.price-card .amt{font-family:'Anton',sans-serif; font-size:46px; margin-bottom:4px;}
.price-card .per{color:var(--slate); font-size:13px; margin-bottom:24px;}
.price-card.pop .per{color:var(--gpp-color-text-muted-dark);}
.price-card ul{list-style:none; margin-bottom:28px;}
.price-card li{display:flex; gap:10px; align-items:flex-start; padding:8px 0; font-size:14px; border-top:1px solid var(--line-dark);}
.price-card.pop li{border-top:1px solid var(--line);}
.price-card li:first-of-type{border-top:none;}
.price-card .btn{width:100%; justify-content:center;}

/* ---------- FOOTER CTA ---------- */
.final-cta{
  background:var(--gpp-color-surface); padding:56px 0 80px; text-align:center; text-wrap:balance;
}
.final-cta h2{color:var(--ink); font-size:clamp(28px,4vw,44px); margin-bottom:16px; line-height:1.08;}
.final-cta p{color:var(--slate); font-size:16px; margin-bottom:30px;}

footer{background:var(--black); color:var(--gpp-color-text-muted-dark); padding:56px 0 30px; font-size:13.5px;}
.foot-grid{display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr; gap:40px; padding-bottom:40px; border-bottom:1px solid var(--line); margin-bottom:24px;}
.foot-logo{color:var(--gpp-color-text-invert); font-family:'Anton',sans-serif; font-size:20px; margin-bottom:12px;}
.foot-col h4{color:var(--gpp-color-text-invert); font-family:'Oswald',sans-serif; font-size:12px; letter-spacing:0.1em; text-transform:uppercase; margin-bottom:14px;}
.foot-col a{display:block; color:var(--gpp-color-text-muted-dark); margin-bottom:10px; transition:color .15s;}
.foot-col a:hover{color:var(--gpp-color-text-invert);}
.foot-bottom{display:flex; justify-content:space-between; flex-wrap:wrap; gap:10px;}
/* The legal links in the bottom row. They were PLAIN TEXT until 2026-09-05
   (owner: "those links are not live"). They shipped underlined and the owner
   took the underline off on 2026-09-07: "you dont need to under line the
   privacy and terms links in the footer". They now match every other link in
   this footer, which carries no underline either, and keep the same hover
   token as .foot-col a:hover as the affordance. */
.foot-legal a:hover{color:var(--gpp-color-text-invert);}

/* ================= RESPONSIVE ================= */

/* --- Tablet / small laptop --- */
@media (max-width:1180px){
  #how .sec-head h2{white-space:normal !important;}
}

@media (max-width:1024px){
  .wrap{padding:0 28px;}
  nav{padding:16px 28px;}
  .hero{padding:72px 0 96px;}
  .tabs-section{padding:80px 0 72px;}
  .app-sec{padding:88px 0 56px;}
  .compare{padding:32px 0 88px;}
  .compare-inner{padding:40px 34px;}
  .app-sec .app-grid{gap:36px; padding:40px 40px;}
}

/* --- Tablet portrait / large phone --- */
@media (max-width:900px){
  /* nav becomes a drawer */
  .nav-toggle{display:flex;}
  .nav-links{
    position:absolute; top:100%; left:0; right:0;
    display:none; flex-direction:column; align-items:stretch; gap:0;
    background:var(--black); border-bottom:1px solid var(--line);
    padding:6px 24px 22px; box-shadow:0 24px 40px rgba(0,0,0,0.45);
  }
  .nav-links.open{display:flex;}
  .nav-links a:not(.btn){
    display:block; font-size:16px; color:var(--gpp-color-text-table);
    padding:15px 2px; border-bottom:1px solid var(--line);
  }
  .nav-links a.btn{
    width:100%; justify-content:center; margin:16px 0 0; font-size:15px; padding:15px 20px;
  }

  /* hero */
  .hero{padding:56px 0 88px;}
  .hero-grid{flex-direction:column; align-items:center; gap:38px;}
  .hero-grid > div:first-child{max-width:640px !important; flex:1 1 auto !important; text-align:center; text-wrap:balance;}
  .hero h1{font-size:clamp(30px,5.6vw,40px);}
  .hero p.lede{margin-left:auto; margin-right:auto; font-size:16.5px;}
  .hero-ctas{justify-content:center;}
  .hero-trust{justify-content:center;}
  .mock{max-width:440px; width:100%; margin:0 auto; flex:1 1 auto;}

  /* stats */
  .stat-cards{grid-template-columns:1fr 1fr; transform:translateY(-36px);}
  .statbar{padding:0 0 46px;}

  /* marquee */
  .logo-marquee{padding:52px 0 8px;}
  .marquee-label{margin-bottom:28px;}
  .marquee-item img{height:54px;}
  .marquee-track{gap:16px;}

  /* sections */
  .tabs-section{padding:64px 0 60px;}
  .sec-head{margin-bottom:34px;}
  .mission{padding:64px 0 44px;}
  .mission h2{margin-bottom:34px;}
  .flag-label{margin-bottom:24px;}
  .flag-outer{margin-bottom:52px;}
  .flag-track{gap:18px;}
  .flag-item{padding:10px 12px;}
  .flag-item img{height:52px;}
  .mission-cols{grid-template-columns:1fr; gap:28px;}
  .compare{padding:24px 0 72px;}
  .app-sec{padding:64px 20px 44px;}
  .final-cta{padding:40px 0 60px;}

  /* tab card stacks */
  .tab-card{grid-template-columns:1fr; gap:28px; padding:28px;}
  .tab-card h3{font-size:23px;}
  .tab-visual{min-height:0;}

  /* app section */
  .app-sec .app-grid{
    grid-template-columns:minmax(0,1fr); max-width:560px !important;
    gap:8px; padding:32px 26px;
  }
  .app-grid > div:first-child{max-width:none; justify-self:stretch;}
  .app-grid p.lede{max-width:none;}
  /*
   * Centre the CTA the moment the section stacks. Side by side it sat under a 470px column
   * and filled most of it; stacked, the column is the full card and the button kept its own
   * width at the far left, leaving a third of the row empty beside it. Only phones under
   * 430px were saved, by the width:100% rule further down, so every large iPhone showed it
   * pushed to one side. fit-content is what auto margins need to centre a button that is
   * otherwise inline-flex.
   */
  .app-grid .btn{display:flex; width:fit-content; margin-left:auto; margin-right:auto;}
  .phone-img-wrap{justify-self:center; gap:14px; margin-top:26px;}
  .phone-real-img{width:42vw; max-width:172px;}

  .price-grid{grid-template-columns:1fr;}
  .foot-grid{grid-template-columns:1fr 1fr; gap:32px;}
}

/* --- Phones --- */
@media (max-width:700px){
  .wrap{padding:0 22px;}
  nav{padding:14px 22px;}
  .nav-links{padding:6px 22px 20px;}
  .logo-img{height:26px;}
  .hero-br{display:none;}

  /* tab nav: 2x2 grid so all four are visible without scrolling */
  .tab-nav{
    display:grid; grid-template-columns:1fr 1fr; grid-auto-rows:1fr;
    gap:10px; margin-bottom:32px; overflow:visible;
  }
  .tab-btn{
    width:100%; text-align:center; font-size:12.5px; letter-spacing:0.03em;
    padding:13px 8px; line-height:1.2;
    display:flex; align-items:center; justify-content:center;
  }

  .compare-inner{padding:32px 20px;}
  .compare-head{margin-bottom:26px;}
}

@media (max-width:560px){
  .wrap{padding:0 20px;}
  nav{padding:13px 20px;}
  .nav-links{padding:6px 20px 20px;}

  .hero{padding:48px 0 76px;}
  .hero p.lede{font-size:16px;}
  .stat-cards{grid-template-columns:1fr 1fr; transform:translateY(-30px);}
  .statbar{padding:0 0 40px;}
  .stat-card{padding:22px 12px;}
  .stat-card .lbl{font-size:12px; letter-spacing:0.05em;}

  .marquee-item img{height:46px;}
  .marquee-item{padding:6px 8px;}
  .marquee-track{gap:12px;}

  .sec-head h2{font-size:clamp(24px,6.4vw,34px);}
  .tab-nav{gap:8px;}
  .tab-btn{font-size:11.5px; padding:12px 6px; letter-spacing:0.02em;}
  .tab-card{padding:22px 20px;}
  .tab-card h3{font-size:21px;}
  .tab-card li{font-size:14.5px;}

  .mission h2{font-size:clamp(23px,6vw,32px);}
  .mission-col .label{font-size:21px;}
  .flag-outer{margin-bottom:44px;}
  .flag-track{gap:14px;}
  .flag-item{padding:8px 10px; border-radius:8px;}
  .flag-item img{height:42px;}

  /* comparison table: fixed narrow check columns, readable feature column */
  .compare-inner{padding:26px 14px;}
  .ctable{table-layout:fixed;}
  .ctable th{font-size:10px; letter-spacing:0.05em; padding:11px 6px;}
  .ctable th.center{font-size:11px; letter-spacing:0.02em; white-space:nowrap;}
  .ctable td{padding:13px 6px; font-size:13px;}
  .ctable td.feature{font-size:14px; line-height:1.25; padding-left:12px; letter-spacing:0;}
  .ctable th:first-child{padding-left:12px;}
  .ctable th:first-child, .ctable td.feature{width:46%;}
  .ctable th.center, .ctable td.center{width:27%; padding-left:4px; padding-right:4px;}
  .cell-yes, .cell-no{font-size:17px;}

  .app-sec .app-grid{padding:28px 20px;}
  .app-grid h2{font-size:clamp(24px,6.4vw,32px);}
  .phone-real-img{width:40vw; max-width:150px;}

  .final-cta{padding:36px 0 52px;}
  .final-cta h2{font-size:clamp(24px,6.4vw,34px);}
  footer{padding:42px 0 26px;}
  /* THE BOTTOM BAR STACKS AND CENTRES ON EVERY PHONE, NOT JUST THE NARROW ONES.
     Owner, 2026-09-07, from a live iPhone: "on mobile below the copyright line lets put
     the privacy and terms centered on the footer."

     These five declarations are not new. They were written for the phone layout and shipped
     inside the max-width:430px block below, where they missed the owner's own handset by ten
     pixels: an iPhone 17 Pro Max reports a 440px viewport, so it fell through to the base
     row rule and put the copyright hard left and the legal links hard right. Measured on
     production before the move, at 440 the two sat on one row (copyright left:20, legal
     right:420, same top); at 430 they stacked and the legal block centred to within 0.01px.
     Nothing was wrong with the rule, only with where it lived.

     560 is the boundary because it is where this file already treats the page as a phone,
     and it is the line directly above: footer's own phone padding is set here. Every phone
     in portrait is under it and no tablet is, so two phones ten pixels apart now render the
     same footer. This is the same breakpoint-miss that rows 8 to 12 of
     tests/system/journey-homepage-static-layout.mjs were written for, ten lines further down
     the same stylesheet, which is why the footer rows there probe 440 and 431 and not
     "mobile".

     The copyright centres too. It already did on a 430px phone, so leaving it left at 440
     would make the fix itself the thing that renders differently between two handsets. */
  .foot-bottom{flex-direction:column; align-items:center; text-align:center; text-wrap:balance; gap:6px;}
}

@media (max-width:430px){
  /* 18px, down from 21. Owner direction 2026-08-24 added "gymnast" to the Recruiting
     Portal heading and asked for the phone break to fall after that word, so the first
     line has to carry "Know exactly how gymnast". At 21px that measures 300px against a
     283px box at 375, which does not fit: the forced break cannot help, and "gymnast"
     drops onto a line of its own. 18px takes it to 9 percent headroom.

     ALL FOUR HEADINGS MOVE, not just the one that needed it, and the owner chose that
     knowing the cost. The docblock at the top of features.php is the reason: the eye
     reads repeated UI as a set and expects the rhythm to repeat, so sizing one card's
     heading differently from its three siblings reads as broken before anyone can say
     why. A section-wide problem gets a section-wide answer.

     Only below 430. Between 430 and 480 the break is showing and 21px still fits with
     11 percent to spare, so nothing changes there. Measured at 375 across all four:
     profile 26 percent, portal 9, tracker 4, mindlab 23. Tracker is the tight one and
     that is fine: its lines are a NATURAL wrap, so a device with wider metrics simply
     re-wraps it. Only a FORCED line can fail by fitting in one browser and not another,
     which is why the margin is spent on the one heading that has a <br> in it. */
  .tab-card h3{font-size:18px;}

  /* 8.0vw, down from 8.4. Owner direction 2026-08-24: "can the Recruiting
     Begins be the same line". It already WAS on one line in every harness, and
     that is exactly the trap. Measured on dev with the real Montserrat loaded,
     "RECRUITING BEGINS" filled 334px of a 335px line box at 375 and 350 of 353
     at 393. One pixel and three pixels of headroom. It fits in Chrome and it
     stops fitting on his phone, because iOS rounds text metrics differently and
     anything under about a percent flips on the difference.

     8.0vw takes the phrase to 6 percent headroom at both widths (17px at 375,
     20px at 393) while the headline still leads the biggest section heading by
     5.4px, so the type ramp that has broken twice before still holds.

     7.6vw was measured too and rejected: 10 percent headroom, but it closes the
     gap over the section headings to 3.9px, which is not a hierarchy anyone can
     see. Safety was bought as far as it could be without spending the ramp. */
  .hero h1{font-size:clamp(28px,8.0vw,40px);}
  .btn{font-size:13.5px; padding:14px 22px;}
  .btn-lg{padding:16px 24px;}
  .hero-ctas .btn,
  .final-cta .btn,
  .app-grid .btn{width:100%; justify-content:center;}
  .mock{padding:14px;}
  .tab-btn{font-size:11px; padding:12px 4px;}
  .stat-card{padding:20px 8px;}
  .stat-card .lbl{font-size:11px; letter-spacing:0.04em;}
  .foot-grid{grid-template-columns:1fr; gap:26px; padding-bottom:30px;}
  .ctable th.center, .ctable td.center{width:27%;}
  .ctable td.feature{font-size:13px;}
}

/* touch devices: kill hover-lift so taps don't look stuck */
@media (hover:none){
  .btn:hover{transform:none; box-shadow:none;}
}


/* ==========================================================================
   CORRECTIONS TO THE MOCKUP
   Everything above this line is the approved mockup's stylesheet, verbatim.
   Everything below is an accessibility or performance correction found by a
   measured audit on 2026-08-07. Each one is here rather than edited into the
   block above so the mockup stays readable as the mockup, and every deviation
   from it is visible in one place.
   No rule below changes a colour, size, spacing value or layout the mockup set,
   with the two exceptions noted at their own rules (tab button height, and the
   stat strip recolour requested by the owner).
   ========================================================================== */

/* --- P0: make Inter actually apply -----------------------------------------
   MEASURED: body computed to Poppins and Inter rendered on ZERO elements, while
   still being downloaded (35 font faces of wasted payload). Cause: WordPress puts
   `gp-app` on this body via gp_add_body_class() (it fires for every page template,
   not just the app door), and gp-core.css carries `body.gp-app { font-family: ... }`
   at specificity (0,1,1), which beats the mockup's plain `body` (0,0,1) no matter
   what order the files load in.
   `body.gp-homepage` is also (0,1,1) and this file loads later, so it wins.
   This is not cosmetic: Poppins sets the same string 26% wider than Inter
   (340.2px vs 270.6px measured), so every line break on the page was wrong. */
body.gp-homepage {
  font-family: 'Inter', sans-serif;
}

/* --- P1: keyboard focus ----------------------------------------------------
   MEASURED: zero rules matching :focus existed anywhere on this page, so a
   keyboard user had no idea where they were. WCAG 2.4.7.
   The ring colour follows the SURFACE the ring lands on, not the control, because
   outline-offset puts it outside the element. Dark sections get a white ring;
   everything else gets ink. Gold is deliberately never used for this: it measures
   1.7:1 against the gold CTA it would sit next to. */
/* NO border-radius HERE. It used to set 4px, and because this selector is
   (0,2,2) it beat every control's own radius: focusing the 100px pill tab
   buttons squared them into rounded rectangles, so a tab visibly CHANGED SHAPE
   when touched. That is the ring JP saw around MIND LAB and not around COLLEGE
   TRACKER, since only the last-touched control is focused.

   Nothing replaces it, deliberately. Since Chrome 94, Firefox 88 and Safari
   16.4 an outline follows the element's OWN border-radius, so the ring is now a
   capsule around a capsule and a rectangle around a rectangle without anyone
   declaring it per control. Older engines draw a square-cornered ring, which is
   the historic default and is not a regression. */
body.gp-homepage a:focus-visible,
body.gp-homepage button:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* Each of these carries body.gp-homepage deliberately. Without it the selector is (0,1,2)
   and LOSES to the (0,2,2) base rule above, so the override silently does nothing and the
   ring stays near-black. MEASURED before this prefix was added: tabbing to a footer link gave
   outline-color rgb(23,24,27) on a black footer, i.e. an invisible focus ring on five of the
   page's surfaces. Verified after: white. */
body.gp-homepage header a:focus-visible,
body.gp-homepage header button:focus-visible,
body.gp-homepage .hero a:focus-visible,
body.gp-homepage .statbar a:focus-visible,
body.gp-homepage .mission a:focus-visible,
body.gp-homepage .compare a:focus-visible,
body.gp-homepage footer a:focus-visible {
  outline-color: var(--gpp-color-text-invert);
}

/* No special case for the gold CTA, on purpose. outline-offset puts the ring 3px OUTSIDE the
   button, so it lands on the surface behind it and never on the gold: white in the dark header
   and hero, ink in the light closing section. The surface rules above already get both right,
   and a per-button colour would break the header case. */

/* --- P1: touch targets -----------------------------------------------------
   MEASURED: footer links 20px tall, header nav links 27px, against a 44px minimum.
   Expanded with an invisible overlay rather than padding, so the visual size and
   the mockup's spacing are untouched and only the hit area grows. */
.nav-links a:not(.btn),
.foot-col a {
  position: relative;
}

.nav-links a:not(.btn)::after,
.foot-col a::after {
  content: '';
  position: absolute;
  inset: -9px -6px;      /* 27 + 18 = 45, clears 44 */
}

.foot-col a::after {
  inset: -12px 0;        /* 20 + 24 = 44 */
}

/* The one place a visual value moves: tab buttons measured 39px and take the 44px
   minimum. Five pixels, and they are the page's main interactive control. */
.tab-btn {
  min-height: 44px;
}

/* --- P1: marquee accessibility ---------------------------------------------
   Both marquees render their contents twice so the loop has no seam, and the flag
   track's script clones further until it exceeds the viewport: MEASURED at 48 logo
   images and 96 flag images. With alt text on every copy a screen reader read the
   same 24 programs twice and the same 6 countries sixteen times.
   The tracks are now marked decorative, and each section states its real list once
   in text only it can reach. */
.gp-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- Owner request: stat strip ---------------------------------------------
   Numbers white, labels gold. Contrast improves on both (number 9.84 -> 18.73,
   label 5.08 -> 9.84), and it stops four gold stat numbers competing with the one
   gold CTA on the same screen. The data becomes the loudest thing in the strip,
   which is what it should be, and gold goes back to meaning "act here". */
.stat-card .num {
  color: var(--gpp-color-text-invert);
}

.stat-card .lbl {
  color: var(--gold-text);
}

/* --- P2: radius ladder -----------------------------------------------------
   The audit counted 11 distinct radii and called it drift. Looking at what owns each
   value, most of it is not drift at all, so only the two genuine duplicates move.

   The ladder the mockup is actually working to:
       4px    things inside a tile (logo images, buttons)
       10px   tiles (marquee items, flag items)
       12-16  panels (stat strip, tab visual, hero mock, quote card)
       20px   the three big bordered panels (tab card, compare, app grid)
       50% / 100px  circles and pills

   Deliberate, left alone:
   - 13px on the tab visual's media is 14px minus its 1px border. A nested radius has to be
     the outer one minus the border or the corner reads thick. That is craft, not drift.
   - 2px on the hamburger bars is a 2px-tall bar; the radius just rounds its ends.

   Genuine duplicates, fixed:
   - 3px on flag images, where every other logo-in-a-tile is 4px.
   - 18px on .tab-card, where the page's two other large bordered panels are both 20px. */
.flag-item img {
  border-radius: 4px;
}

.tab-card {
  border-radius: 20px;
}

/* --- P2: stat numbers hold their width -------------------------------------
   Required by the count-up below: without tabular figures the row visibly jitters as the
   digits change width mid-animation. Also stops the four cards shifting against each other
   when the real counts change on a cache refresh. */
.stat-card .num {
  font-variant-numeric: tabular-nums;
}

/* --- Logo scale ------------------------------------------------------------
   The mockup asks for height:30px. Our wordmark is 1328x136, a 9.76:1 lockup, so
   30px renders it 293px wide: a quarter of the 1180px content column, and visually
   heavier than the nav and the CTA combined. The file is byte-identical to the
   mockup's, so this was faithful reproduction of an oversized mark rather than a bug.

   21px puts the wordmark's cap height alongside the 18px Oswald nav links, so the two
   read as peers and the gold CTA stays the heaviest object in the bar, which is what a
   conversion page wants. Mobile drops proportionally (26 -> 18): below 900px the nav
   collapses to a drawer and the header is just the mark and the hamburger, where a
   254px logo took 73% of the content box. */
.logo-img {
  height: 21px;
}

/* THE WORDMARK, EVERYWHERE, AT THE SIZE IT ALWAYS WAS.
   Owner direction 2026-08-17, closing a loop worth recording. The lockup was replaced on phones
   by the GP hexagon because it looked like it was crowding the headline, then set to swap back to
   the wordmark once the hero had scrolled past. Both worked. Both were also machinery: two
   images, an IntersectionObserver, a rebuild on resize and a class on the header, in service of a
   small gain. Reverted to one image and one rule per breakpoint, at the original heights. */
.logo-img { height: 21px; }

@media (max-width: 700px) {
  .logo-img { height: 18px; }
}

/* --- Program wall: no plate, name under each mark ---------------------------
   Owner direction 2026-08-07. Two changes to the mockup's .marquee-item.

   1. THE PLATE COMES OFF. The mockup wrapped each logo in a white tile with a hairline
      border, on a band whose background is already white, so the border was drawing a box
      around nothing. University marks are designed to sit on white; boxing them adds 24
      rectangles competing with 24 logos.

   2. THE NAME GOES UNDER IT. Many of these marks are initials or an animal, and a visitor
      who does not already know the crest learns nothing from the logo alone. The names in
      the database are already the right ones (short common names, "Denver" not "University
      of Denver", longest is "Eastern Michigan" at 16 characters), so they are printed as
      stored with no transformation.

   Sizing decisions:
   - Items take a FIXED 120px width. Names run from 3 to 16 characters, and letting the item
     size to its content made the wall's rhythm visibly ragged. 120px clears the longest name
     (measured ~97px at this size) with room to breathe.
   - The mark drops 64px -> 56px. The name now carries identification, so the logo does not
     have to work as hard, and it keeps the taller item from making the band heavy.
   - Name is Oswald uppercase at 11px, the same treatment as the stat-strip labels, so the
     page has one label voice rather than two. --ink measures 17.75:1 on the white band;
     --slate would have been 5.05:1, passing but needlessly weak for 11px type. */
.marquee-item {
  flex-direction: column;
  gap: 10px;
  width: 120px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
}

.marquee-item img {
  height: 56px;
}

.marquee-name {
  font-family: 'Oswald', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  text-align: center;
  line-height: 1.25;
}

@media (max-width: 900px) {
  .marquee-item { width: 104px; gap: 8px; }
  .marquee-item img { height: 48px; }
  .marquee-name { font-size: 10px; letter-spacing: 0.04em; }
}

@media (max-width: 560px) {
  .marquee-item { width: 92px; }
  .marquee-item img { height: 42px; }
}

/* --- Signed-in account menu -------------------------------------------------
   Logged-out is the mockup untouched. This is only what the signed-in state adds.
   Split by breakpoint on purpose: the dropdown is desktop-only and the drawer items are
   mobile-only, so a member never sees the same three links twice. */

/* Mobile-first: drawer items visible, desktop dropdown and bar avatar hidden. */
.acct { display: none; }

.acct-mobile {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  padding: 7px;              /* 30px avatar + 14 = 44 exactly. Measured at 5px it was 40. */
  border-radius: 50%;
  flex-shrink: 0;
}

/* EXPLICIT width AND height, both required.
   gp-base.css:744 sets a site-wide `img { height: auto }`, which OVERRIDES the height
   ATTRIBUTE that gp_render_avatar() writes. With only a width, the height then came from the
   photo's own aspect ratio: a portrait avatar measured 34x41 and rendered as an ellipse, not a
   circle. The portal header escapes this by declaring both, which is why its avatar looks
   right and this one did not. aspect-ratio is belt and braces for a future non-square source. */
.acct-avatar {
  display: block;
  width: 30px;
  height: 30px;
  aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  border: 2px solid var(--gpp-tint-white-20);
}

.acct-avatar--fallback {
  display: grid;
  place-items: center;
  background: var(--gold);
  color: var(--black);
  font-family: 'Oswald', sans-serif;
  font-weight: 700;
  font-size: 13px;
}

@media (min-width: 901px) {
  /* Desktop: the drawer copies go, the dropdown arrives. */
  .acct-drawer-item { display: none !important; }
  .acct-mobile { display: none; }

  .acct { display: block; position: relative; margin-left: 4px; }

  .acct-trigger {
    display: flex;
    align-items: center;
    gap: 5px;
    height: 44px;            /* full target height; the chevron supplies the width */
    padding: 0 2px;
    background: none;
    border: 0;
    cursor: pointer;
    border-radius: 22px;
  }

  .acct-trigger .acct-avatar {
    width: 34px;
    height: 34px;
    transition: border-color .15s ease;
  }

  /* The chevron, matching the portal header's: same 14px, same polyline, same muted white.
     It is what tells a visitor the avatar OPENS something rather than being a link. */
  .acct-chevron {
    width: 14px;
    height: 14px;
    color: var(--gpp-tint-white-70);
    flex-shrink: 0;
    transition: transform .18s ease, color .15s ease;
  }

  @media (hover: hover) {
    .acct-trigger:hover .acct-avatar { border-color: var(--gold); }
    .acct-trigger:hover .acct-chevron { color: var(--gpp-color-text-invert); }
  }

  .acct-trigger[aria-expanded="true"] .acct-avatar { border-color: var(--gold); }
  .acct-trigger[aria-expanded="true"] .acct-chevron { transform: rotate(180deg); color: var(--gpp-color-text-invert); }

  .acct-menu {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    min-width: 210px;
    padding: 8px;
    background: var(--black-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: 0 18px 40px rgba(0,0,0,0.55);
    z-index: 60;
  }

  .acct-menu[hidden] { display: none; }

  .acct-who {
    padding: 8px 12px 10px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--line);
    font-family: 'Oswald', sans-serif;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--gpp-color-text-muted-dark);          /* 5.4:1 on --black-2, and it is a label not a control */
  }

  .acct-menu a {
    display: block;
    padding: 11px 12px;
    border-radius: 8px;
    font-family: 'Oswald', sans-serif;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--gpp-color-text-invert);
  }

  @media (hover: hover) {
    .acct-menu a:hover { background: var(--gpp-tint-white-07); }
  }
}

/* --- Nav links defer to the CTA --------------------------------------------
   Owner direction: this page is as close to a funnel as possible, so the nav should be
   present but quiet.

   MEASURED before: nav links 18px/800, the CTA button 15px/800. The secondary navigation was
   typed 20% LARGER than the primary action it is supposed to defer to, and larger than the
   21px logo is tall. On a conversion page that is the hierarchy upside down.

   14px puts them clearly under the CTA while staying legible: uppercase Oswald at 800 reads
   cleanly at that size, and the tracking is unchanged because small caps need their spacing.
   Weight stays 800 so the mockup's character is intact; this is a size change, not a restyle.

   Scoped to 901+ deliberately. Below that the nav becomes a full-screen drawer where the links
   are the only content and a 16px rule already governs them; shrinking those would hurt a
   touch target for no gain, since nothing competes with them there. */
@media (min-width: 901px) {
  .nav-links a:not(.btn) {
    font-size: 14px;
  }
}

/* --- Owner request (2026-08-08): one section-heading role, lighter voice ------
   MEASURED before: the same section-heading role was set at 58/44/44/40/40px, all
   in Anton, whose single weight is very heavy; the largest (What's Included, 58px)
   was 88 percent of the hero's size and competed with it. One role, one size now.
   Sentence-case section heads move to Oswald Medium, the lighter voice already in
   this surface's family (Anton has no lighter cut). Anton remains where loud is
   the point: the hero, the caps CTA line, the stat numbers. A/B render approved.
   h2.sec-title is (0,1,1) and sits after the per-section rules and their media
   overrides, so it wins those ties by order on purpose. */
/* SECTION HEADINGS IN MONTSERRAT. Owner direction 2026-08-17, following the hero. These were a
   split: Oswald 500 by default, with .sec-title-anton switching two of them to Anton for a
   different voice. One face now, so that distinction has nothing left to express and the -anton
   rule below simply agrees rather than overriding.

   700, not the 800 the hero headline uses. Two weights of one family is the hierarchy: the two
   biggest statements on the page sit above everything that merely labels a section.

   AND NOW THE SIZE SAYS SO TOO. Owner, 2026-08-17: the section heads read bigger than the
   headline. Measured on dev, they were not merely close, they were IDENTICAL: hero h1 and
   every section h2 both landed on 40px at 1440, and between roughly 1000 and 1380 the section
   heads were about a pixel LARGER, because their 3vw beat the hero's 2.9vw. The paragraph
   above had been describing an intention the numbers never carried out.

   32, not a bigger hero. The headline is deliberately small so the film keeps its full width;
   taking it to 56 would put it on five lines in a 428px column or make the video narrower, and
   that is a settled decision working as intended. Stepping the heads down to 32 gives a 1.25
   ratio that the 800-vs-700 weight difference then reinforces.

   THE MINIMUM IS UNCHANGED AT 26, so phones do not move. Mobile already had the hierarchy
   right (hero 32.8 against 26 at 390px); only the desktop cap was wrong, so only the cap
   changes. 2.4vw reaches 32 at 1333 and holds there. */
h2.sec-title{
  font-family:'Montserrat',sans-serif; font-weight:700; text-transform:none;
  letter-spacing:-0.01em; font-size:clamp(26px,2.4vw,32px); line-height:1.18;
}
.final-cta h2{font-size:clamp(26px,3vw,40px);}

/* MEASURE, which does more work here than the size did. The size was only half the reason the
   heads dominated: the headline wraps to three lines inside a 428px column while these ran
   1000px+ on a single line, so equal type read as unequal weight. .sec-head already caps its
   block at 640 and the app heading sits in its own column, but these two were unbounded (the
   comparison head had no max-width at all, and the mission statement carried its own 820).
   One measure for both, so the page has two deliberate widths rather than three accidental
   ones. margin-left/right only: the mission head's 56px bottom margin is load-bearing. */
.compare-head h2, .mission h2{max-width:760px; margin-left:auto; margin-right:auto;}

/* Owner request (2026-08-08, round 2) was that the features and discovery headings match the
   marquee labels' voice one size down, so the two "Active ..." labels and these two heads read
   as one family. That still holds; it is just that the whole family moved to Montserrat on
   2026-08-17, so this no longer switches the face, only the line-height it always tightened.
   The class stays in the markup because the intent it records is still the intent. */
h2.sec-title-anton{font-weight:700; line-height:1.1;}

/* ==========================================================================
   HERO FILM  (added 2026-08-16)

   The 40-second product film replaces hero-athlete.jpg in the hero's right column.
   Markup and behaviour: template-parts/homepage/hero.php and js/gp-homepage.js.

   The whole finding of docs/research/2026-08-08-clean-video-container.md is that the
   "floating app window" look is INSIDE the asset, not in CSS. So this block does three
   things and nothing else: round the corners, keep the load-in from flashing, and keep
   the control legible. No box-shadow, no border, no padding: the depth is baked into
   the film, and adding a second shadow here would double it.
   ========================================================================== */

/* The column, and note the --hero modifier: these are the HERO's sizing rules, not the
   component's. CAUGHT IN THE LAB when the same component landed in a feature tab: .tab-visual
   is a flex COLUMN, and in a column the main axis is the height, so an unscoped
   `flex: 1 1 440px` stopped meaning "440 wide" and started meaning "440 TALL". The clip kept
   its own height and the box grew 59px of dead cream underneath it. Sizing belongs to the
   context, never to the shared block.

   640 is the cap because a 16:9 film wants about that much beside a headline,
   and it is half of the asset's own 1280 width, so the film renders at 2x density.
   flex-basis stays under the cap so both hero columns grow into their max-widths; see the
   sizing note in hero.php for the 428 + 48 + 640 = 1116 arithmetic.

   THE BASIS IS 440 AND NOT 560, AND THE REASON IS NOT COSMETIC. .hero-grid is flex-wrap:wrap,
   and a wrapping row breaks the line as soon as the two BASES plus the gap exceed it: it never
   gets as far as shrinking them. So the bases alone decide the width at which this hero
   stacks, and that width has to stay under 901px, where the stacked rules (centred text,
   full-width column) take over. CAUGHT IN THE LAB at 1024px with a 560 basis: 380 + 560 + 48
   came to 988, the row wrapped at about 1044px, and everything from there down to 901 rendered
   as a left-aligned headline with the film dumped underneath it and none of the stacked
   treatment. 320 + 440 + 48 = 808 fits inside the 829 available at 900px, so the only stack
   that ever happens is the deliberate one. The caps still pin the desktop split, because grow
   fills the rest. Rows 18 to 21 of tests/system/journey-hero-video-player.mjs walk that band. */
.gp-media--hero{flex:1 1 440px; max-width:640px; width:100%;}

/* The mat. Flush with the video, same rect, same radius. It is visible for one frame at
   most, before the poster paints, and --gpp-color-media-mat is the film's own edge colour
   so that frame shows nothing. */
.gp-media__frame{
  position:relative;
  border-radius:var(--gpp-media-radius);
  background:var(--gpp-color-media-mat);
}

/* The video clips its own corners rather than relying on overflow on the parent: a
   transformed or composited video layer can paint straight through an ancestor's clip. */
.gp-media__video{
  display:block; width:100%; height:auto;
  border-radius:var(--gpp-media-radius);
}

/* The control.
   Owner direction 2026-08-16: bigger, and no dark disc behind it. A bare white glyph, which
   is what the reference player does.

   THE PROBLEM THAT DISC WAS SOLVING IS STILL REAL, so it is solved a different way. The
   reference player gets away with a bare glyph because its mat is one constant blue. This
   film is not: MEASURED across all 40 seconds, the square under this control is near-white
   (luminance 233 to 245) for 7 of them, second 0 included, and second 0 is the poster
   everybody sees before anything moves. White on white is nothing at all.

   So the glyph carries its own outline instead of sitting on a plate. Two stacked
   drop-shadows: a tight dark one that traces the letterform's edge and reads as a hairline
   keyline on a pale frame, and a soft wider one that seats it on a bright one. The shadow
   follows the shape of the icon rather than boxing it, so on the dark two thirds of the film
   it is invisible and the control looks exactly like a plain white glyph, which is the point.

   30px glyph in a 48px target, up from 18 in 44. Both are past the 44px accessibility
   minimum; the icon is what got bigger. */
.gp-media__control{
  position:absolute; right:.5rem; bottom:.5rem;
  width:48px; height:48px; padding:0;
  display:flex; align-items:center; justify-content:center;
  border:0; cursor:pointer;
  background:transparent;
  color:var(--gpp-color-text-invert);
  filter:
    drop-shadow(0 0 1px var(--gpp-glyph-keyline))
    drop-shadow(0 0 2px var(--gpp-glyph-keyline))
    drop-shadow(0 1px 5px var(--gpp-glyph-shadow));
  transition:transform .15s ease, opacity .15s ease;
}
/* Beats the display:flex above; the pair is swapped by the `hidden` attribute. */
.gp-media__control[hidden]{display:none;}
.gp-media__control svg{display:block; width:30px; height:30px;}

@media (hover:hover){
  .gp-media__control:hover{transform:scale(1.08);}
}
@media (prefers-reduced-motion:reduce){
  .gp-media__control{transition:none;}
  .gp-media__control:hover{transform:none;}
}

/* Focus, and the one place this diverges from the page's own focus rule.
   That rule paints a white ring on dark sections, which is right everywhere else on this page
   because those surfaces hold still. This one does not, and the same 7 near-white seconds
   measured above would swallow a white ring whole.

   So focus is the one state that DOES get a plate: a dark rounded backing appears behind the
   glyph with the white ring outside it, and the pair reads on any frame. A keyboard user is
   the only person who ever sees it, so it costs the design nothing at rest, which is exactly
   why the plate is acceptable here and was not acceptable as the permanent treatment.
   filter:none because a drop-shadow would otherwise smear the ring it is meant to sharpen.

   body.gp-homepage is on the front deliberately: without it this is (0,2,0) and loses to the
   base rule's (0,2,2), exactly the trap documented at the focus block above. */
body.gp-homepage .gp-media__control:focus-visible{
  outline:2px solid var(--gpp-color-text-invert);
  outline-offset:2px;
  background:var(--gpp-scrim-control-strong);
  border-radius:10px;
  filter:none;
}

/* Desktop radius tier. min-width:1025 rather than the page's usual max-width queries
   because it is the reference player's own breakpoint. */
@media (min-width:1025px){
  .gp-media__frame,
  .gp-media__video{border-radius:var(--gpp-media-radius-lg);}
}

/* The trust line, on every view. Owner direction 2026-08-16: smaller, and sitting right under
   the button instead of adrift below it. 34px of air under a CTA reads as the end of the hero;
   this line is the answer to the question the button just raised ("free until when?"), so it
   belongs against it. 12px still measures 5.7:1 on the black hero, past 4.5:1.
   Not scoped to mobile: the gap was worst on a phone and wrong everywhere. */
.hero-ctas{margin-bottom:14px;}
.hero-trust{font-size:12px;}

/* Stacked, and the running order on a phone.

   OWNER DIRECTION 2026-08-16: the film comes BEFORE the button. It is the thing that explains
   what someone is signing up for, and asking for the signup first and showing the product
   after is the wrong way round on the one screen where you cannot see both at once.

   HOW, because it is not obvious. The film and the words are siblings in a two-column row, but
   the button lives INSIDE the words, so no amount of `order` on the row can slot the film
   between the lede and the button. `display:contents` is what makes it possible: the text
   column stops generating a box, its four children become flex items of .hero-grid alongside
   the film, and then one `order` each puts them in any sequence. Inheritance still passes
   through it, so the mockup's `text-align:center` on that div keeps working.

   The row's 38px gap goes to 0 with it, because that gap was spacing two blocks and would now
   space five. The mockup's own margins take back over, which is where the spacing was already
   defined. */
@media (max-width:900px){
  .hero-grid > div:first-child{display:contents;}
  .hero-grid{gap:0;}

  .hero h1{order:1;}
  .hero p.lede{order:2;}
  .gp-media--hero{order:3;}
  .hero-ctas{order:4;}
  .hero-trust{order:5;}

  /* The cap the text column used to hold for its children, now that it holds nothing. */
  .hero h1,
  .hero-ctas,
  .hero-trust{max-width:640px;}

  .gp-media--hero{
    max-width:640px; width:100%; margin:0 auto 34px; flex:1 1 auto;
  }
}

/* ==========================================================================
   FEATURE TAB CLIPS  (added 2026-08-16)

   The four tab panels held screenshots; they now hold four 4-second clips cut from JP's own
   screen recordings, one per surface. Markup: template-parts/homepage/features.php.
   Playback: the same player as the hero film, in play-once mode.
   ========================================================================== */

/* THE CARD: SIDE BY SIDE, with the clip taking the larger share.
   Owner direction 2026-08-16, and it took three passes to land, so the history is the comment.

   The mockup split this 1fr 1fr, product in a 484px box beside 484px of bullets, and at that
   size a recording of a real interface is an impression rather than something you can read. Asked
   to make it as big as possible, I stacked it: bullets above, clip across the full width. That
   IS the biggest a clip can be, and it was wrong. JP: the tab video should be horizontal, not
   vertical. A feature card reads as one thought when the claim and its proof sit beside each
   other, and as two when one is under the other.

   So it is two columns again, but not evenly. The bullets do not need half a card: MEASURED, the
   longest of them sets comfortably at 320. Everything else goes to the clip, which also bleeds
   out through the card's right and bottom padding. That is 728x535 against the mockup's 484x249,
   three times the area, still beside the words.

   The width that made the app inside it legible did not come from here at all. It came from the
   encode, where the app's left rail is cropped out of the frame. See build.sh note 5. */
/* Owner direction 2026-08-17: the clip closer to the bullets, and more room at the sides. It read
   as cramped because it was doing both the opposite ways round: a 32px gutter between the copy and
   the clip, and then the clip pushed flush to the card's edge with nothing beyond it. Swapped. The
   gap comes in to 24 so the claim and its proof read as one unit, and the card's padding goes out
   to 44 so the clip has air on the outside where the eye expects the card to breathe. */
/* Owner direction 2026-08-24, chosen by looking at five sizes side by side: "make the tab
   screens smaller they are too big". The clip was 680x500 and the biggest thing in the section,
   while the words beside it were crammed into a 320px lane, which is why all four bullets wrapped
   onto two lines. 560 was the pick: an 18 percent narrower clip, a card 89px shorter, and the
   width it gives up goes to the copy, which lands at 440 and lets most bullets sit on one line.
   460 was tried and rejected as the floor rather than a target, because the app inside the clip
   stops being readable and a section whose whole job is to show the product working cannot
   afford that.

   WHY minmax AND NOT A FLAT 560px. A fixed clip track takes its width first and leaves the copy
   whatever is left, and between 900 and about 1100 there is not enough left: at a 1024px window
   the copy would be squeezed to 284, NARROWER than the 320 this change exists to relieve.
   Written this way the clip gives way first. `minmax(320px, 1fr)` puts a hard floor under the
   copy and stays flexible, so above ~1244px the clip sits at its full 560 with the copy at 440,
   and as the window narrows the clip shrinks while the copy holds 320. Measured at 1024: copy
   320, clip 524. */
.tab-card{grid-template-columns:minmax(320px,1fr) minmax(0,560px); gap:24px; padding:44px;
          align-items:center; overflow:hidden;}

/* No bleed. An earlier pass pushed the clip out through the card's padding to win about 70px;
   owner direction 2026-08-17 reversed it, because flush to the edge reads as cramped rather than
   as big. The clip keeps the card's padding on every side and its own rounded corner back. */
.tab-visual-media{margin:0;}
.tab-card ul{columns:260px 2; column-gap:40px;}   /* self-collapsing: one column in the 320px lane, two only if a wider one ever appears */
/* `columns: 260px 2` and not `columns: 2`, and the phone is why. CAUGHT IN THE LAB at 390px: a
   flat 2 dropped the longest bullet, "Complete access to the mental performance system used by
   top college programs", into eight lines of one or two words each. Giving the shorthand a
   minimum column WIDTH as well as a maximum count lets the browser fall back to one column
   whenever two will not fit, at whatever width that turns out to be, instead of at a
   breakpoint someone guessed. */
/* A bullet must not break across the column boundary: half a promise at the foot of one column
   and the rest at the head of the next is worse than an uneven pair of columns. */
.tab-card li{break-inside:avoid;}
/* The mockup kills the top border on the first item, which is right in one column. In two, the
   head of the SECOND column keeps a border the first one lost and the pair reads lopsided, so
   above the width where two columns actually fit, both get one. */
@media (min-width:701px){
  .tab-card li:first-of-type{border-top:1px solid var(--line-dark);}
}

/* The clip's frame. Same three jobs as the hero's: round the corners, stop the load-in from
   flashing, stay out of the way. The mat matters more here than it does in the hero, because
   three of these four sit behind display:none and paint for the first time mid-click. */
.gp-media--tab .gp-media__frame{
  border-radius:13px;
  background-color:var(--gpp-color-media-mat);
  /* The poster URL itself is set inline per clip in features.php; see the note there for why. */
  background-size:cover; background-position:center; background-repeat:no-repeat;
}
.gp-media--tab .gp-media__video{border-radius:13px;}

/* 13px, not the hero's 12/24 tier, and not an accident: the panel around it is 14px with a 1px
   border, and a nested radius has to be the outer one minus the border or the corner reads
   thick. That rule is already written down in the radius-ladder note above; this obeys it. */
@media (min-width:1025px){
  .gp-media--tab .gp-media__frame,
  .gp-media--tab .gp-media__video{border-radius:13px;}
}

/* Stacked, below the mockup's own breakpoint for this card. Side by side stops being possible
   long before a phone: at 700px a 320px copy column would leave the clip 260px, narrower than
   it gets stacked. So under 900 the clip goes full width under the copy and bleeds on three
   sides instead of one. */
@media (max-width:900px){
  .tab-card{grid-template-columns:1fr; gap:20px; padding:24px;}
}

/* THE CONTROL, OUT OF THE WAY. Owner direction 2026-08-16 round 3: lose the pause sign, let it
   play. It cannot go away entirely and the reason is not stylistic: WCAG 2.2.2 asks for a way to
   stop anything that moves by itself for more than five seconds, and these run for ten to twelve.
   The same rule is why an auto-advancing carousel needs a stop button.

   So it is invisible rather than absent. While the clip plays there is no control on screen,
   which is what was asked for. It appears on hover, on keyboard focus, and whenever the clip is
   not playing, and the picture itself is clickable to stop it, which is the way out on a touch
   screen where there is no hover. Nobody sees a pause sign on a running clip; anybody who wants
   one can get it.

   (The hero used to keep an always-visible control. As of 2026-08-17 it does not; it is reached
   the same three ways as the clips.) */
.gp-media .gp-media__frame{cursor:pointer;}
.gp-media .gp-media__control{opacity:0; pointer-events:none; transition:opacity .18s ease;}

/* HOVER ONLY WHERE HOVER IS REAL. Owner direction 2026-08-17: no pause button on the gymnast
   profile. He was seeing one on a phone, and the cause is a thing touch screens do rather than
   anything in this file: tapping an element makes iOS Safari apply its :hover state and LEAVE it
   applied, so one tap on the card revealed the control and it stayed. Gating on
   (hover:hover) and (pointer:fine) means a finger can never trigger it. */
@media (hover:hover) and (pointer:fine){
  .gp-media:hover .gp-media__control{opacity:1; pointer-events:auto;}
}

/* The two ways the control can still be reached, both deliberate and neither visible at rest:
   keyboard focus, because a keyboard user has no other way to stop a looping video, and the
   paused state, because someone who tapped the picture to stop it needs a way to start it again.
   Between them they satisfy WCAG 2.2.2 without putting a button on screen. */
.gp-media:focus-within .gp-media__control,
.gp-media.is-idle .gp-media__control{opacity:1; pointer-events:auto;}
@media (prefers-reduced-motion:reduce){
  .gp-media .gp-media__control{transition:none;}
}

/* PHONE. Landscape, always. Owner direction 2026-08-16 round 3, after seeing the alternative:
   an earlier pass turned the phone box portrait and cropped into the frame to buy legibility,
   and it read as four vertical videos, which these are not. They are recordings of a desktop
   product and they stay that shape at every width.

   The size a phone needed came from two other places instead, neither of which changes the
   shape: the app's left rail is cropped out in the encode (build.sh note 5), and the clip runs
   to the card's edges rather than sitting inside its padding. Together that is about 1.6 times
   the app UI a phone was getting, still landscape. The stacked rule above carries the bleed. */

/* ==========================================================================
   MOBILE LINE BREAKS (owner audit, 2026-08-21)
   ==========================================================================
   Two of these are the owner's own break points and one is left to the browser,
   because they are different kinds of problem.

   THE TWO STAT LABELS get an explicit break, because JP named where he wants it:
   after "Programs" and after "Countries". Measured on dev before choosing the
   width: .marquee-label is one line down to 480px and wraps by 440;
   .flag-label is one line down to 440 and wraps by 410. Both then broke badly,
   orphaning the single word "Platform" onto its own line. The break shows below
   480px only, so nothing changes at any width where the line already fits.

   A <br> is used rather than white-space:nowrap on a phrase. A nowrap cannot
   overflow-protect itself, and this page already carries one inline nowrap that
   needs an !important counterweight in a media query to stay safe. A break that
   only appears below a width cannot overflow anything.

   THE FOUR TAB HEADINGS use text-wrap:balance instead, because JP asked for the
   breaks to be "clean" rather than naming a point, and balance is the answer
   that stays correct when the copy changes. THREE OF THEM STILL DO. The
   Recruiting Portal heading was given a named break on 2026-08-24, after
   "gymnast", because the owner named the point that time; see 'title_break' in
   features.php and the 18px note in the max-width:430 block. Measured at 390px, as shipped
   against balanced:
     "Build a profile coaches / actually trust"      -> "Build a profile / coaches actually trust"
     "Know exactly how / recruiting works"           -> unchanged, already clean
     "Build your list and reach / your top schools"  -> "Build your list and / reach your top schools"
     "Train the mental side of the / sport"          -> "Train the mental / side of the sport"
   The last one is the point: balance removes the single-word orphan without
   anyone hardcoding where the line ends. CSS.supports reports true in the test
   browser, and engines without it simply wrap as they do today.

   MEASUREMENT NOTE: the first pass read all four headings as single-line at
   every width, which was wrong. Three of the four panes are display:none until
   their tab is selected, and a hidden element reports every string as one line.
   The numbers above come from activating each tab before measuring it. */
.hp-br-phone { display: none; }

@media (max-width: 480px) {
  .hp-br-phone { display: inline; }

  /* THE SOLUTION HEADING GIVES UP ITS SIDE PADDING, and only where the break is
     showing. Owner direction 2026-08-24: the first break moves to after
     "Solution", and "i need to see that breakpoint longer, it should work on
     most screens".

     The binding line is "for Gymnastics Recruiting" at 348px, and it does not
     get smaller: .sec-head h2 is pinned at 26px on phones by a floor that
     journey-homepage-static-layout defends, so type size is not available as a
     lever here. Width is the only one left, which is why the owner asked for the
     padding rather than a smaller heading.

     .wrap gives it 20px a side below 560, so -20px cancels exactly that and no
     more. Measured on dev, the width at which the two-line shape survives:
       padding 20 (before)  ->  fails at 375, becomes three lines
       padding  8           ->  holds to 375, 3 percent
       padding  0 (this)    ->  holds to 360, and 7 percent at 375
     Nothing actually touches the screen edge: the text is centred and shorter
     than the box, so at 375 it still carries 13px a side and at 393 it carries
     22. The box is full-bleed; the words are not. */
  #how .sec-head { margin-left: -20px; margin-right: -20px; }
}

/* ==========================================================================
   LINE WRAPPING, PAIRED WITH ALIGNMENT (owner audit, 2026-08-21, revised)
   ==========================================================================
   THE RULE: balance is for CENTRED text, pretty is for LEFT-ALIGNED text.
   Balance evens line LENGTHS, which reads as deliberate between two free edges
   and reads as an odd short first line against a ragged right one. It also
   optimises geometry rather than phrasing, so on left-aligned copy it will
   happily break after a conjunction.

   WHY THIS IS DECLARED WHERE ALIGNMENT IS DECLARED, and not as a list of
   selectors. The first version of this rule named the elements it thought were
   centred. Enumerating the page proved that wrong in SEVEN places, because a
   selector cannot know its own alignment:
     .label is centred in some sections and left inside .mission-cols
     h2.sec-title is centred in three sections and left in .app-sec
     the hero is left on desktop and CENTRED ONLY BELOW 900px
   That last one is decisive: alignment changes with VIEWPORT, so no selector
   list can be right at every width. Both properties inherit, so declaring them
   together is the only form that cannot drift: wherever alignment is set, the
   matching wrap treatment is set in the same declaration, including inside the
   media query and including the left override nested in a centred section.

   pretty is the page default because it is safe on anything: it leaves the
   paragraph alone and only protects the last line from a stranded word. Engines
   without either property wrap exactly as they do today. */
body.gp-homepage {
  text-wrap: pretty;
}

/* ...AND PARAGRAPHS ARE ALWAYS pretty, centred or not. A direct rule beats an
   inherited one, so this holds inside the centred containers above.

   This clause exists because MEASURING IT CAUGHT A REGRESSION I WAS ABOUT TO
   SHIP. Balancing the centred hero lede changed it from

     "Everything gymnasts need to get recruited. / Everything college coaches
      need to discover / talent. All in one place."
   to
     "Everything gymnasts need to get / recruited. Everything college coaches
      need to discover talent. All in one place."

   which is more even in LENGTH and splits "get / recruited" mid-phrase. Balance
   is for DISPLAY text, headings and labels, where symmetry between two free
   edges reads as design. A paragraph's job is reading, and there the only thing
   worth fixing is a stranded last word, which is exactly what pretty does. So
   the rule is: balance for centred DISPLAY text, pretty for prose at any
   alignment. */
body.gp-homepage p {
  text-wrap: pretty;
}

/* A proper noun is one thing. See the note in template-parts/homepage/personas.php
   for why this is safe here and why it is not a general licence for nowrap. */
.hp-atomic {
  white-space: nowrap;
}
