/* ============================================================
   PRIME ACADEMY GROUP · primeacg.com — v4
   components.css — shared components — header, footer, buttons, cards
   Lifted verbatim out of the single-file pages in www/website/.
   ============================================================ */
/* ── Prime Academy adjustments layered on the v4 shell ───────────────── */
/* H1 · hero now carries two CTAs side by side */
.s-hero__cta{gap:14px;flex-wrap:wrap}

/* H2 · portrait source in a wide band — bias the crop up so the face is in frame */
.s-who .eyebrow{color:rgba(255,255,255,.86)}
.s-who .parallax-inner > img{object-position:center 20%}

/* H4 · the capability section is now a tall scroller, so the photographic backdrop is
   capped to the section entry and dissolved into flat navy for the rest of the run. */
/* overflow here MUST NOT be `hidden` (as the base rule sets it): that makes .s-why a scroll
   container, and position:sticky on .cap-pin then resolves against a scrollport that never
   scrolls, so it never pins. `clip` clips identically without creating a scroll container. */
.s-why{background:#101c38;overflow:clip}
/* The section runs 420vh on desktop. At 120vh the backdrop only existed in the
   top quarter of it, and the mask was already dissolving from 44% inside that,
   so the picture was gone before the walkthrough even started. 210vh with the
   fade pushed to 64% keeps it present across the pinned run. */
.s-why > .parallax{bottom:auto;height:min(100%,210vh);
  -webkit-mask-image:linear-gradient(180deg,#000 64%,transparent 100%);
          mask-image:linear-gradient(180deg,#000 64%,transparent 100%)}
/* The backdrop used to be panel-field.jpg through object-fit:cover, which meant
   a 1216px image stretched over the layer at ~2.1x — coarse cells, and no way to
   set their size. It is a repeating tile now: 120px cut grid-aligned (4 cells of
   30px) out of a glow-free corner of that same image, so it carries the original
   texture but the cell size is just background-size. 128px renders 32px cells,
   about half what cover was giving.
   Tiling the whole photo was tried first and does not work — its one corner glow
   repeats into a field of blobs. So the glow is a single radial layer here
   instead, painted over the grid where the photo had it.
   The tile is dark like its source, hence the lift. */
.s-why .parallax-inner{
  background-image:radial-gradient(56% 44% at 20% 76%,rgba(33,150,243,.34),transparent 70%),
                   url(../img/panel-grid-tile.png);
  background-size:auto,128px 128px;
  background-repeat:no-repeat,repeat;
  background-position:center,center top;
  filter:brightness(1.45) contrast(1.12)}
/* No soft bridge into this section — the band starts on a hard edge, like every other
   light→dark change on the site. There used to be a white gradient here standing in for
   the removed 004 interstitial; it read as a haze across the top of the dark band. */

/* H4 · capability walkthrough — Version 1's compact layout at Version 4's scale.
   Desktop pins a single viewport for a 600vh run: clickable rail on the left, one stage on the
   right. Under 961px there is no room to pin, so it unpins into a plain stacked list. */
.cap-scroller{margin-top:clamp(40px,5vw,64px)}
.cap-rail{display:none}

/* The captures are not all the same shape — five are 2880x1800 app screens, the certificate is a
   1170x2532 phone. So the frame carries no box of its own: it reserves a fixed height and each
   shot sizes itself inside that at its own ratio, which means no empty letterbox around the odd
   ones. The reserved height never changes, so the rail and the copy underneath never move. */
.cap-frame{position:relative;width:100%;margin-inline:auto}
/* Bottom-aligned, not centred: where the capture is shorter than the reserved height (narrow
   desktops, where width is the binding constraint) centring split the slack above and below,
   so the gap down to the copy grew with the screen. Pinned to the bottom, that gap is the
   same on every screen and the slack all sits above, where there is room for it. */
.cap-slide{position:absolute;inset:0;display:grid;place-items:end center;
  opacity:0;visibility:hidden;transform:scale(1.025);
  /* visibility flips instantly, but only once the fade has finished - so the
     delay has to be the fade's own duration, not a number typed next to it. */
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility 0s linear var(--dur)}
.cap-slide.is-active{opacity:1;visibility:visible;transform:none;
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility 0s linear}
/* Sized by WIDTH, with the ratio driving the height — not the other way round. Height-first
   plus max-width let the box keep the reserved height while the picture inside shrank, so the
   border drew a rectangle with dead space above and below the actual image. */
.cap-shot{position:relative;margin:0;aspect-ratio:var(--ar,16/10);
  width:min(100%,calc(var(--cap-h,660px) * var(--ar,16/10)));height:auto}
.cap-shot > img{display:block;width:100%;height:100%;object-fit:contain;
  border-radius:var(--radius);border:1px solid var(--line-on-dark);
  box-shadow:0 40px 80px -30px rgba(0,0,0,.7)}

.cap-callout{position:absolute;left:var(--x);top:var(--y);max-width:min(42%,250px);
  padding:10px 14px;border-radius:var(--radius);
  font-family:var(--font-body);font-weight:500;font-size:13px;line-height:1.35;
  color:var(--navy);background:rgba(255,255,255,.95);border:1px solid rgba(255,255,255,.9);
  box-shadow:0 14px 34px -10px rgba(4,10,24,.6);
  opacity:0;transform:translateY(14px) scale(.94);transform-origin:0 0;
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease)}
.cap-callout::before{content:"";position:absolute;left:-5px;top:-5px;width:10px;height:10px;border-radius:50%;
  background:var(--azure);box-shadow:0 0 0 3px rgba(33,150,243,.26)}
.cap-slide.is-active .cap-callout{opacity:1;transform:none}
.cap-slide.is-active .cap-callout--1{transition-delay:.30s}
.cap-slide.is-active .cap-callout--2{transition-delay:.48s}
.cap-slide.is-active .cap-callout--3{transition-delay:.66s}

/* the certificate is portrait, so it leaves a gutter either side — the callouts sit in it
   instead of covering the card */
.cap-shot--portrait .cap-callout{width:min(220px,16vw);max-width:none}
.cap-shot--portrait .cap-callout--1{left:auto;right:calc(100% - 6px);top:29%}
.cap-shot--portrait .cap-callout--2{left:calc(100% - 6px);top:46%}
.cap-shot--portrait .cap-callout--3{left:auto;right:calc(100% - 6px);top:61%}
.cap-shot--portrait .cap-callout--1::before,
.cap-shot--portrait .cap-callout--3::before{left:auto;right:-5px}

.cap-panel h3{margin:0 0 12px;font-size:clamp(23px,2.5vw,34px);line-height:1.16}
/* Same size and weight as the name it sits beside, so the line reads as one piece of type in
   two colours. It used to be half-size at weight 700 with a manual baseline nudge to sit it
   back on the line, which made the number and the name look like different things. */
.cap-panel__n{display:inline-block;margin-right:.34em;color:var(--azure)}
.cap-panel p{margin:0;max-width:58ch}
.cap-step__shot{display:none}

/* desktop: one pinned viewport — rail picks the step, stage shows it */
@media (min-width:961px){
  .cap-scroller{height:420vh}                       /* six steps, ~0.7 viewport of scroll each */
  /* Reserve the nav's height at the top. The pin is 100vh and centres its contents, but the
     nav is fixed and sits over the first ~76px of it, so a truly centred block read as if it
     were riding too high. Centring below the nav evens the visible space top and bottom. */
  .cap-pin{position:sticky;top:0;height:100vh;display:flex;align-items:center;padding-top:76px}
  /* The copy block's two measurements live here because BOTH columns need them: the stage
     to lay the copy out, and the rail to know how far above its own column's bottom edge
     the screenshot actually ends. Kept as variables so the two can never drift apart. */
  .cap-grid{display:grid;width:100%;align-items:center;
    grid-template-columns:minmax(0,max-content) minmax(0,1fr);
    gap:clamp(30px,4vw,60px);
    --cap-copy-gap:clamp(26px,3.6vh,38px);
    --cap-copy-h:clamp(118px,12vh,162px)}

  /* Bottom-aligned to the SCREENSHOT, not to the column. The stage column runs on past the
     picture to hold the copy, so ending the rail flush with the column would drop it below
     the image. Backing off by the copy block lands it on the picture's bottom edge. */
  .cap-rail{display:block;position:relative;list-style:none;margin:0;
    padding-left:clamp(16px,1.6vw,22px);
    align-self:end;
    margin-bottom:calc(var(--cap-copy-gap) + var(--cap-copy-h) + 50px)}
  .cap-rail::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;
    background:rgba(255,255,255,.12)}
  .cap-rail__bead{position:absolute;left:0;top:0;width:2px;height:0;background:var(--azure);
    transition:transform var(--dur-state) var(--ease), height var(--dur-state) var(--ease)}
  .cap-rail li + li{margin-top:clamp(10px,1.6vh,20px)}
  .cap-rail button{display:flex;align-items:center;gap:14px;width:100%;padding:9px 0;
    background:none;border:0;cursor:pointer;text-align:left;
    font-family:var(--font-display);font-weight:500;
    font-size:clamp(15px,1.35vw,21px);line-height:1.24;white-space:nowrap;
    color:rgba(255,255,255,.56);transition:color var(--dur-state) var(--ease)}
  /* the numbered pill is Version 4's marker, kept so the rail reads as part of that system */
  .cap-rail__n{flex:none;display:inline-flex;align-items:center;justify-content:center;
    min-width:52px;height:34px;padding:0 12px;border-radius:999px;
    font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:.1em;
    color:rgba(255,255,255,.74);border:1px solid var(--line-on-dark);background:rgba(255,255,255,.06);
    transition:color var(--dur-state) var(--ease), background-color var(--dur-state) var(--ease), border-color var(--dur-state) var(--ease), box-shadow var(--dur-state) var(--ease)}
  .cap-rail li.is-active button,
  .cap-rail button:hover,
  .cap-rail button:focus-visible{color:#fff}
  .cap-rail li.is-active .cap-rail__n{color:#fff;background-color:var(--azure);border-color:transparent;
    box-shadow:0 8px 24px rgba(33,150,243,.38)}

  /* One column width for the whole stage: the landscape capture's natural width at the
     reserved height. The frame and the copy both take it, so the copy's left edge IS the
     screenshot's left edge at every screen size.
     Both sit flush RIGHT in that column: the column is wider than they are, and left-aligning
     dumped all the slack on the right, so the section's right padding grew with the screen
     (94px wider than the left at 1920, 638px at 2560). Pushed right, the two paddings match.
     The reserved height is capped by the column WIDTH as well as the viewport height, via a
     container query on the stage. Without the width term the box kept its full height on
     narrower desktops while the picture inside shrank to fit, leaving slack the rail could
     not line up against. With it, the picture always fills its box exactly. */
  .cap-stage{min-width:0;container-type:inline-size}
  .cap-frame,.cap-panels{
    --cap-h:min(calc(100vh - 340px),660px,calc(100cqw * 1800 / 2880));
    --cap-w:calc(var(--cap-h) * 2880 / 1800)}
  .cap-frame{height:var(--cap-h);max-width:var(--cap-w);margin-inline:auto 0;margin-top: 2em;}
  .cap-panels{position:relative;margin:var(--cap-copy-gap) 0 0 auto;
    width:100%;max-width:var(--cap-w);
    min-height:var(--cap-copy-h)}
  .cap-panel{position:absolute;left:0;right:0;top:0;
    opacity:0;visibility:hidden;transform:translateY(10px);
    /* same rule as .cap-slide above: the delay is this rule's own fade duration */
    transition:opacity var(--dur-state) var(--ease), transform var(--dur) var(--ease), visibility 0s linear var(--dur-state)}
  .cap-panel.is-active{opacity:1;visibility:visible;transform:none;
    transition:opacity var(--dur-state) var(--ease), transform var(--dur) var(--ease), visibility 0s linear}
}

/* under 961px there is no room to pin — plain stacked list, one screenshot per capability */
@media (max-width:960px){
  .cap-frame{display:none}
  .cap-panel + .cap-panel{margin-top:clamp(40px,8vw,60px)}
  .cap-step__shot{display:block;margin:20px 0 0;border-radius:var(--radius);overflow:hidden;
    border:1px solid var(--line-on-dark);background:rgba(255,255,255,.05)}
  .cap-step__shot img{display:block;width:100%;height:auto}
  .cap-panel h3{display:flex;align-items:baseline;gap:.5em}
}
@media (prefers-reduced-motion:reduce){
  .cap-slide,.cap-callout,.cap-panel,.cap-rail__bead,.cap-rail button{transition:none}
}

/* H6 · process visual replaces the dotted site map */
/* The frame is 3:2 and the photograph is 4:3, so it fills and crops rather than
   letterboxing. It used to hold a flat diagram that could sit at natural height. */
.exec-figure{margin:0;border-radius:var(--radius);overflow:hidden;background:var(--white);
  position:absolute;inset:0}
.exec-figure img{display:block;width:100%;height:100%;object-fit:cover;object-position:center 42%}

/* H6 · the five pipeline steps, built in the same language as .t-run on Online
   Training ("The Same Shape, Every Time"): a 2px rule across the top of each
   step with a numbered pill straddling it, then the title, then the note. The
   difference here is that each step is a card rather than an open column, so the
   rule is the card's own top border and the pill sits on the card's edge.
   Hover moves rule, pill and title to azure together at the same 180ms, so a
   step lights up as one thing - same behaviour as .t-run.
   Last step stays filled: the section is headed "Finished Course Out", and it
   is the thing you came for. */
/* the gap has to hold a 16px arrow with air either side, so it is wider than a
   plain card row would need */
.exec-steps{list-style:none;margin:clamp(40px,5vw,60px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:clamp(18px,2vw,28px)}
.exec-step{position:relative;min-width:0;display:flex;flex-direction:column;gap:10px;
  /* top padding clears the half of the pill that hangs inside the card */
  padding:clamp(26px,2.6vw,38px) 22px 26px;
  border-radius:var(--radius);background:#fff;
  border:1px solid var(--line);border-top:2px solid var(--line);
  transition:all var(--dur-state);}
/* Lifts like every other card on the site: same -3px, same shadow, same 180ms as
   .card--hover, .p-plan and the footer CTA blocks. The pill comes with it so the
   card reads as one thing responding, but the top rule stays put - moving it was
   the whole hover before and it read as a stray line rather than a card. */
.exec-step:hover{transform:translateY(-3px);
  box-shadow:0 16px 34px -20px rgba(20,33,61,.45)}
.exec-step:hover .exec-step__label{color:var(--deep)}

/* The arrow between each pair. The cards are equal height, so their centre lines
   agree, and the arrow sits on that line: centred in the gap horizontally and on
   the cards' midpoint vertically. */
.exec-step + .exec-step::before{content:"";position:absolute;z-index:2;
  top:50%;transform:translateY(-50%);
  left:calc(-1 * (clamp(18px,2vw,28px) / 2) - 8px);
  width:16px;height:16px;opacity:.55;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230D47A1'%3E%3Cpath d='M2 7.2h7.9L7.3 4.6a.9.9 0 0 1 1.27-1.27l4.1 4.1a.9.9 0 0 1 0 1.27l-4.1 4.1A.9.9 0 0 1 7.3 11.4l2.6-2.6H2a.8.8 0 0 1 0-1.6Z'/%3E%3C/svg%3E") center/16px 16px no-repeat}

/* Pill, not a plain numeral: same marker as .t-run, sitting on the rule with the
   copy below it. Left-aligned to the card's own 18px padding so it lines up with
   the title underneath. */
.exec-step__num{
  position:absolute;left:18px;top:0;transform:translateY(-50%);
  min-width:42px;height:30px;padding:0 12px;border-radius:999px;
  display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--font-display);font-weight:700;font-size:12px;line-height:1;
  letter-spacing:.02em;color:#fff;background:var(--deep);
  transition:all var(--dur-state);}
.exec-step:hover .exec-step__num{background:var(--azure);box-shadow:0 4px 8px rgba(33, 150, 243, .3);}

.exec-step__label{font-family:var(--font-display);font-weight:500;
  font-size:clamp(14px,1.15vw,16.5px);line-height:1.22;letter-spacing:var(--track-sm);
  color:var(--ink);text-wrap:balance;transition:color var(--dur-fast) var(--ease);
  /* two lines reserved whether the title needs them or not, so every note in the
     row starts on the same line instead of stepping up and down */}
.exec-step__note{font-family:var(--font-body);font-weight:400;line-height:1.56;
  font-size:clamp(11.5px,.9vw,13px);color:var(--slate);text-wrap:balance}

/* ---- the finished course ---- */
.exec-step:last-child{background:var(--navy);border-color:var(--navy)}
.exec-step:last-child .exec-step__label{color:#fff}
.exec-step:last-child .exec-step__note{color:rgba(255,255,255,.72)}
/* the pill inverts on the dark card so it stays the brightest thing on it */
.exec-step:last-child .exec-step__num{background:#fff;color:var(--navy)}
.exec-step:last-child:hover .exec-step__label{color:#fff}
.exec-step:last-child:hover .exec-step__num{background:var(--azure);color:#fff}

@media (max-width:1000px){
  .exec-steps{grid-template-columns:repeat(2,minmax(0,1fr));
    row-gap:clamp(34px,4vw,44px)}   /* row gap has to clear the pills */
  .exec-step:last-child{grid-column:1 / -1}
  .exec-step__label{min-height:0}
  /* stacked, so there is no left-to-right run for the arrows to point along */
  .exec-step + .exec-step::before{display:none}
}
@media (prefers-reduced-motion:reduce){
  .exec-step:hover{transform:none}
}
@media (max-width:520px){ .exec-steps{grid-template-columns:1fr} }

/* H8 · footer "get started" block replaces the newsletter form */
.s-footer__start{margin-top:16px;display:flex}

/* ── real brand lockups (assets/brand) replace the text wordmarks ──────────────
   All SVG now, all built off the token palette:
     logo.svg          deep blue + red, for light surfaces
     logo-azure.svg    azure + red, the nav mark over the dark hero
     logo-reverse.svg  white + red with a white cap, for navy surfaces
     logo-white.svg    single colour, spare
     mark.svg          the P on its own, white, hero only
   The old PNGs are retired to resources/brand-rasters-retired.
   Every lockup is 1394×460 (3.03:1), so these set height only and let width
   follow, and each parent keeps its own alignment. */
.nav-brand{display:flex;align-items:center;line-height:0}
.nav-brand img{display:block;height:clamp(36px,3.7vw,48px);width:auto}

/* the hero uses the standalone mark (near-square, 459×512), not the 2.9:1 lockup,
   so it carries its own larger height ramp. */
.s-hero__wordmark{line-height:0}
.s-hero__wordmark img{display:inline-block;height:clamp(72px,9vw,116px);width:auto}

.s-footer__wordmark{line-height:0}
.s-footer__wordmark img{display:block;height:clamp(36px,4vw,48px);width:auto}

/* decorative platform mock — the sidebar behind it is navy, so reverse again */
.s-portal__brand{line-height:0}
.s-portal__brand img{display:block;width:100%;max-width:132px;height:auto}

/* The nav has no surface of its own at any width now, which is right over the
   dark hero but leaves the lockup and the white links sitting on whatever
   scrolls under them — a white screenshot card, the light bands. Once the page
   has moved at all, put a surface behind the bar.
   This used to be a navy gradient ramping to nothing at the bottom. No edge
   sounds tidy but it reads as murk: the lower half of the bar is part scrim,
   part whatever is scrolling under it. It is a solid light bar with a hairline
   now, so the boundary is where the eye expects it.
   The hairline is an inset shadow, not a border: inset:0 plus a border would
   grow the box.
   z-index:-1 stays inside .nav's own stacking context (position:fixed +
   z-index), so it paints under the bar's contents, not under the page.
   This used to be scoped to ≤1320px, where the nav was already transparent. The
   white pill above 1320px is gone, so it runs everywhere. */
.nav::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:#fff;box-shadow:inset 0 -1px 0 var(--line);
  opacity:0;transition:opacity var(--dur-state) var(--ease)}
.nav.is-shrunk::before{opacity:1}
/* the full-screen mobile menu is its own navy surface, so the bar would only
   show as a light stripe across the top of it */
.nav.is-open::before{opacity:0}

/* Scrolled state. The bar is light now, so everything standing on it has to
   flip to ink. Untouched at the top of the page, where the nav is still
   transparent over the dark hero and keeps its white treatment. */
.nav.is-shrunk .nav-menu > .nav-item > .nav-trigger,
.nav.is-shrunk .nav-menu > a:not(.btn){color:var(--navy)}
.nav.is-shrunk .nav-menu > .nav-item > .nav-trigger:hover,
.nav.is-shrunk .nav-menu > a:not(.btn):hover,
.nav.is-shrunk .nav-menu > a:not(.btn)[aria-current="page"]{color:var(--deep)}
.nav.is-shrunk .nav-toggle{color:var(--navy)}

/* Under 1320px the menu is a full-screen navy overlay. When it is open the bar
   contents sit on navy again, so they go back to white whatever the scroll
   position is. This has to out-specify the block above, hence the doubled
   class. */
.nav.is-shrunk.is-open .nav-menu > .nav-item > .nav-trigger,
.nav.is-shrunk.is-open .nav-menu > a:not(.btn){color:#fff}
.nav.is-shrunk.is-open .nav-menu > .nav-item > .nav-trigger:hover,
.nav.is-shrunk.is-open .nav-menu > a:not(.btn):hover{color:var(--azure)}
.nav.is-shrunk.is-open .nav-toggle{color:#fff}

/* The mark has to change with the surface: azure over the dark hero, the full
   deep-blue lockup on the light bar. Both ship in the markup and cross-fade on
   the same .35s as the bar, so there is no swap flash and no JS. */
.nav-brand{position:relative}
.nav-brand__mark{transition:opacity var(--dur-state) var(--ease)}
.nav-brand__mark--light{position:absolute;top:0;left:0;opacity:0}
.nav.is-shrunk .nav-brand__mark--dark{opacity:0}
.nav.is-shrunk .nav-brand__mark--light{opacity:1}
.nav.is-shrunk.is-open .nav-brand__mark--dark{opacity:1}
.nav.is-shrunk.is-open .nav-brand__mark--light{opacity:0}

/* ── photo tint ────────────────────────────────────────────────────────────────
   One treatment for every photograph that carries no text, so the six of them
   across four pages read as one set rather than six stock pictures.
   Deliberately far lighter than .cap-tile__fig::after on About, which reaches
   93% at the bottom: that one exists to make white copy legible on top of the
   picture, and this one has no copy to carry. Any heavier and it starts pulling
   skin tones blue on the portraits.
   NOT for illustrations or screenshots — they are already flat brand colour and
   a tint on them just reads as a dirty layer. */
/* No `position` here on purpose. All six containers already set their own, and
   .exec-figure's is `absolute` — a blanket `position:relative` in this rule
   loads after it, wins on equal specificity, and drops that figure out of its
   layout. If a seventh photo is ever tagged, give it position in its own rule. */
.photo-tint::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  border-radius:inherit;
  background:linear-gradient(180deg,rgba(20,33,61,.14),rgba(13,71,161,.26))}
/* Nothing is set on the img either, for the same reason: four of the six are
   `position:absolute; inset:0` so the picture contributes no intrinsic height,
   and overriding that collapses them to 0x0. None is needed — z-index:1 on the
   ::after already paints it over an img at z-index auto. */
