/* Students landing page (/students) - 2026 proposition.

   Standalone stylesheet, served directly and NOT compiled into main.css, so it
   does not wait on the external SCSS build. Namespaced stu- and scoped under .stu
   so it can sit inside the site layout without leaking.

   Header and footer are deliberately untouched - this covers the page body only.

   The two breakpoints differ on purpose, not by accident:
     mobile  - forest-green hero block rounded off at the bottom, panels and terms
               on the grey page ground below it (dark terms, green link)
     desktop - forest green throughout, panels and terms sit on the green
               (white terms, lime link)
   Both are as drawn; see the mobile and desktop frames.

   Figma: PJs - Working, "Student Home" 44071:9852 (desktop) / 44078:12119 (mobile).
   The two Figma variables used are already in scss/colors.scss:
     colour/accent/pickled pepperoncini #cfeb0c = $yellow2  (lime)
     colour/accent/punchy garlic        #e3c2ed = $lavender (lilac)
   The green carries a fine speckled grain at 20% opacity, as in the design. It is a
   small repeating tile rather than a full-page image: the grain is random, so it tiles
   without visible seams, and 320x320 costs 8KB against 192KB for a cover image. */

@font-face {
    font-family: "Papa Sans Medium Condensed";
    font-weight: 500;
    font-style: normal;
    src: url("/css/fonts/papasans-mediumcondensed.ttf") format("truetype");
    font-display: swap;
}

/* The hero photo is oversized and pushed off the card's right edge. clip-path only
   hides it - it still counts towards the page's scroll width, which let the page scroll
   about 70px sideways at mobile widths.

   The clip belongs at this level, not on .stu-heroblock: the photo escapes UPWARD out
   of the hero block so her cap crosses the frame, and WebKit clips both axes for
   overflow-x: clip even though it reports overflow-y as visible - on the block that
   cuts her cap off in Safari. She never leaves .stu, so clipping here costs nothing in
   any engine. `clip` not `hidden` because hidden on one axis forces the other to auto,
   which would make this a scroll container. */
/* The palette. Declared on :root rather than on .stu so it is readable from outside
   the page wrapper too - the app's terms tray is position: fixed and deliberately sits
   outside it. An undefined custom property invalidates the whole declaration, so a
   var() miss out there does not fall back, it silently drops the rule: a red button
   loses its background and keeps its white text. Tokens on :root are inert until
   something references them, so nothing else on the page is affected. */
:root {
    --stu-forest:  #1f3f32; /* page + hero card ground */
    --stu-lime:    #cfeb0c; /* pickled pepperoncini */
    --stu-lilac:   #e3c2ed; /* punchy garlic */
    --stu-red:     #e42313; /* $red - see note on the CTA below */
    --stu-basil:   #2d5d2a; /* $green1 / fresh basil - the frames' link green */
    --stu-error:   #e42313; /* $red */
    --stu-rich-sauce: #710500;
    --stu-ink:     #1c1c1c;
    /* Base, not the rendered colour. The frames render the ground at #e8ebea; with a
       5% grain over the top this base composites to exactly that. Sampling #e8ebea and
       THEN adding grain double-darkened it to #bfc7c4. */
    --stu-grey:    #f3f4f4;
    --stu-panel-shadow: 5px 5px 20px 0 rgba(0, 0, 0, 0.15);
    --stu-gap: clamp(36px, 6vw, 80px);
}

.stu {
    overflow-x: clip;
    box-sizing: border-box;
    padding: 0 0 clamp(40px, 6vw, 80px);
    font-family: "Italian Plate No2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    color: var(--stu-ink);

    /* MOBILE: page ground is the site grey and only the hero sits on forest green.
       Desktop overrides this to green throughout. Bled edge to edge from this
       element and clipped to its own height, rather than painted on .siteContainer,
       because that wrapper also holds the footer. */
    background: var(--stu-grain) repeat, var(--stu-grey);
    box-shadow: 0 0 0 100vmax var(--stu-grey);
    clip-path: inset(0 -100vmax);
    position: relative;
}

/* Grain. Baked to 20% alpha in the tile itself and used as a background layer, so it
   composites over whatever ground colour the element has and can never paint over
   content. An overlay element needed a z-index to clear the block fills, and that
   z-index turned the blocks into stacking contexts which trapped their own content
   below the grain. */
/* Two grain strengths. The green carries the full 20% tile. The grey cannot: at 20%
   even pure white composites to about #d1d4d3, well short of the frames' #e8ebea, so
   the grey uses a 5% tile which lands on the design colour with a whisper of texture
   (the frames measure ~0.004 variance on the grey against ~0.008 on the green). */
.stu-masthead,
.stu-heroblock { --stu-grain: url("/static/assets/students/bg-texture-tile.webp"); }
.stu           { --stu-grain: url("/static/assets/students/bg-texture-tile-light.webp"); }

/* Masthead: the green strip the site header sits on. The header partial is already
   transparent (components/header.scss sets background:transparent on .homeHeader), so
   all this has to do is put green behind it. It also holds the loading gate, so the
   gate is on green rather than on the grey ground. */
.stu-masthead {
    position: relative;
    background: var(--stu-grain) repeat, var(--stu-forest);
    color: #fff;
}

/* header.header sets position:sticky at desktop widths, which made the header follow
   the scroll down this page. It belongs at the top here. */
.stu-masthead .header.homeHeader {
    position: static;
    background: transparent;
}

/* The design shows the logo on its own - no bell, no account nav. The other promotion
   pages hide .rSide the same way. */
.stu-masthead .header .innerH { justify-content: center; }

.stu-masthead .header .innerH .lSide,
.stu-masthead .header .innerH .rSide { display: none; }

/* MOBILE: the forest-green hero block is full bleed and rounded off at the bottom,
   ending after the CTAs, with everything below it on the grey page ground. On
   desktop the whole page is green, so the rounding and the separate bleed come off
   and this simply merges into the ground. */
.stu-heroblock {
    position: relative;
    padding: 0 16px clamp(28px, 4vw, 44px);
    margin-bottom: var(--stu-gap);
    border-radius: 0 0 28px 28px;
    background: var(--stu-grain) repeat, var(--stu-forest);
    color: #fff;
}

.stu-heroblock__inner {
    max-width: 950px;
    margin: 0 auto;
}

.stu *,
.stu *::before,
.stu *::after { box-sizing: inherit; }

.stu [hidden] { display: none !important; }

.stu-inner {
    max-width: 950px; /* the Figma card width */
    margin: 0 auto;
    padding: 0 16px;
    display: flex;
    flex-direction: column;
    gap: var(--stu-gap);
}

/* ------------------------------------------------------------
   Loading gate
   ------------------------------------------------------------
   The CTA block and the hero copy both depend on the visitor: whether they are
   signed in, and whether they are already verified. Neither is known at render time -
   the API is JWT bearer only and the token lives in localStorage, so the server
   cannot resolve either. First paint therefore ships no state-dependent content, only
   this gate, and students-home.js reveals the body once the status is settled. That is
   what keeps the panels from flickering.                                          */
.stu-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    min-height: 260px;
    text-align: center;
    color: rgba(255, 255, 255, 0.7);
}

.stu-loading__spinner {
    width: 34px;
    height: 34px;
    border: 3px solid rgba(255, 255, 255, 0.25);
    border-top-color: var(--stu-lime);
    border-radius: 50%;
    animation: stu-spin 0.8s linear infinite;
}

@keyframes stu-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    .stu-loading__spinner { animation-duration: 2.4s; }
}

/* ------------------------------------------------------------
   Hero
   ------------------------------------------------------------
   Mobile: the lilac border wraps the artwork only, and the copy + CTAs sit below
   it on the page ground. Desktop: the border wraps the whole card and the copy sits
   inside it, to the left of the photo. Same DOM, the border just moves.          */
.stu-hero__card {
    position: relative;
    border-radius: 24px;
    /* Query container for the lockup, so its size is a fixed fraction of the card at
       every width rather than of the viewport. */
    container-type: inline-size;
}

.stu-hero__art {
    position: relative;
    /* Open at the top so her cap crosses the border, as the frames show on BOTH
       breakpoints; left, right and bottom stay clipped. overflow cannot clip one
       side, hence clip-path. */
    overflow: visible;
    clip-path: inset(-60px 0 0 0 round 24px);
    border: 5px solid var(--stu-lilac);
    border-radius: 24px;
    background: var(--stu-forest);
    /* Room for the lockup plus the photo alongside it. */
    min-height: 200px;
    padding: 16px 30% 26px 14px;
}

/* The photo breaks out above the card, as in the design. The source is trimmed to her
   actual bounds - it carries no transparent padding above her - so height maps directly
   to how tall she renders. She is flush to the bottom of those bounds, so bottom:0
   needs no clipping underneath. */
/* Sits on the padding box of whichever element is the positioned ancestor - the art
   block on mobile, the card on desktop - so it is always INSIDE the border. */
.stu-hero__photowrap {
    position: absolute;
    inset: 0;
    overflow: visible;
    clip-path: inset(-60px 0 0 0 round 18px);
    border-radius: 18px;
    pointer-events: none;
}

.stu-hero__photo {
    position: absolute;
    /* Zoomed and cropped, as the design does it: Figma renders the photo at 572x862
       but only shows a 426x558 window of it, cutting her right side and her lower
       legs. So she is oversized past the card and pushed off its right edge, with the
       card clipping the rest - fitting the whole cut-out made her read as too far
       away. Anchored to the top so the crop takes her legs, not her head. */
    top: -5.3%;
    left: 57%; /* cap lands 5px above the border, as the frames show */
    right: auto;
    height: 132%;
    width: auto;
    max-width: none;
    pointer-events: none;
}

/* Verified-state eyebrow. The card becomes a flex column purely so this can sit above
   the artwork on desktop and below it on mobile without duplicating the element. */
.stu-hero__card { display: flex; flex-direction: column; }

.stu-hero__eyebrow {
    order: 2;            /* mobile: after the artwork, above the copy */
    margin: 18px 4px 0;
    color: var(--stu-lime);
    font-size: clamp(1.3rem, 6.4vw, 1.5625rem); /* 25px; frames' cap is 17px at both widths */
    font-weight: 700;
    line-height: 0.98;                          /* frames' line pitch is ~24px */
    text-transform: uppercase;
}

.stu-hero__art   { order: 1; }
.stu-hero__intro { order: 3; }

/* Verified lockup: different words, so the width-matched sizes are solved separately
   from DOWNLOAD / FREE / PIZZA. Frames measure all three lines at ~203px on mobile. */
.stu-lockup--verified .stu-lockup__l1 { font-size: 14.25cqw; }
.stu-lockup--verified .stu-lockup__l2 { font-size: 34.28cqw; }
.stu-lockup--verified .stu-lockup__l3 { font-size: 33.97cqw; }

/* DOWNLOAD / FREE PIZZA is live text in the design, not artwork - so it stays
   selectable, translatable and searchable here too. */
.stu-lockup {
    margin: 0;
    font-family: "Papa Sans Medium Condensed", Impact, "Arial Narrow Bold", sans-serif;
    font-weight: 500;
    text-transform: uppercase;
    line-height: 0.78;
    letter-spacing: -0.01em;
}

/* Width-matched lockup: every line spans the same width, so the font size per line
   is driven by how many (and which) glyphs it has - FREE is the largest because it is
   the shortest word. Sizes below were solved by measuring the rendered widths in a
   browser and scaling each until the three matched, not guessed from cap heights.
   Mobile stacks all three; desktop puts FREE PIZZA on one line. */
.stu-lockup__l1,
.stu-lockup__l2,
.stu-lockup__l3 {
    display: block;
    white-space: nowrap;
}

.stu-lockup__l1 { color: var(--stu-lilac); font-size: 13.97cqw; }
.stu-lockup__l2 { color: var(--stu-lime);  font-size: 34.64cqw; }
.stu-lockup__l3 { color: var(--stu-lime);  font-size: 28.49cqw; }

.stu-hero__intro {
    padding: 22px 4px 0;
}

.stu-hero__copy {
    margin: 0 0 24px;
    font-size: clamp(1rem, 1.6vw, 1.25rem);
    line-height: 1.2;
    max-width: 559px; /* the Figma measure */
}

.stu-hero__copy strong { font-weight: 700; }

/* ------------------------------------------------------------
   Buttons
   ------------------------------------------------------------
   NOTE: the design specifies the CSS keyword `red` (#ff0000) for this button. That
   is almost certainly a default swatch rather than an intent, so this uses the
   brand red ($red #e42313) that the rest of the site uses. Swap the --stu-red
   token above if pure red really is wanted.                                     */
.stu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 56px;
    padding: 12px 28px;
    border: 0;
    border-radius: 5px;
    background: var(--stu-red);
    color: #fff;
    font-family: inherit;
    font-size: clamp(1.05rem, 1.5vw, 1.375rem);
    font-weight: 700;
    text-align: center;
    text-decoration: none;
    text-transform: uppercase;
    cursor: pointer;
}

/* The site's global a:hover recolours links green. On a red button that reads as a
   fault, so it holds white and underlines instead. */
.stu-btn:hover,
.stu-btn:focus,
.stu-btn:active {
    color: #fff;
    text-decoration: underline;
}

.stu-hero__cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 13px;
}

/* Verified. Small screens read as the other states do - copy, then a full-width button
   deep linking to the app store. Desktop swaps the button for a QR code sitting beside
   the copy; see the desktop block below. */
.stu-hero__verified {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 13px;
}

.stu-hero__verified .stu-hero__verified-copy { margin: 0; }

.stu-hero__qr { display: none; }

.stu-hero__verified .stu-hero__appcta { width: 100%; }

.stu-hero__cta .stu-btn { width: 100%; }

.stu-hero__alt {
    margin: 0;
    font-size: clamp(1.05rem, 1.5vw, 1.375rem);
    font-weight: 700;
    color: var(--stu-lime);
}

.stu-hero__alt a {
    color: #fff;
    text-decoration: none;
}

.stu-hero__alt a:hover,
.stu-hero__alt a:focus { text-decoration: underline; }

/* ------------------------------------------------------------
   Content panels
   ------------------------------------------------------------ */
.stu-panel {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 5px solid;
    border-radius: 24px;
    box-shadow: var(--stu-panel-shadow);
}

.stu-panel--lilac { border-color: var(--stu-lilac); }
.stu-panel--lime  { border-color: var(--stu-lime); }

/* Stacked, the artwork always sits above the copy. --reverse puts the copy first in
   the DOM (so desktop can show copy-then-artwork with row-reverse), so it needs
   column-reverse here or the tile would end up underneath the text. */
.stu-panel--reverse { flex-direction: column-reverse; }

.stu-panel__media {
    position: relative;
    overflow: hidden;
    flex: 0 0 auto;
    aspect-ratio: 1.6;
}

/* The wheel tile is taller than the pizza tile in the design. */
.stu-panel--lime .stu-panel__media { aspect-ratio: 1.38; }

/* The panel artwork is exported from Figma as one composed tile per panel: both are
   layered, rotated cut-outs (and the wheel's segment labels are separate text nodes),
   so reproducing them in CSS only ever approximated them.
   NOTE: Figma exports these at their natural 370x274 and will not upscale, so they
   are 1x and will look soft on a retina screen. Ask design for 2x/3x exports before
   this goes live. */
.stu-panel__tile {
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    inset: 0;
    object-fit: cover;
    object-position: center;
}

.stu-panel__content {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    padding: clamp(30px, 9vw, 50px) clamp(20px, 5vw, 66px);
    background: #fff;
    color: var(--stu-ink);
    text-align: center;
}

.stu-panel__title {
    margin: 0;
    font-size: 1.5625rem; /* 25px, per the design at both breakpoints */
    font-weight: 700;
    line-height: 1.2;
    text-transform: uppercase;
}

.stu-panel__copy {
    margin: 0;
    font-size: 1.25rem; /* 20px, per the design at both breakpoints */
    line-height: 1.2;
}

/* ------------------------------------------------------------
   Terms
   ------------------------------------------------------------ */
.stu-terms {
    text-align: center;
    padding-top: 4px;
}

.stu-terms__title {
    margin: 0 0 16px;
    font-size: clamp(1.25rem, 2vw, 1.5625rem);
    font-weight: 700;
    text-transform: uppercase;
}

.stu-terms__copy {
    margin: 0;
    font-size: clamp(0.95rem, 1.3vw, 1.1875rem);
    line-height: 1.2;
}

/* MOBILE: the terms sit on grey, so lime would fail contrast - the link takes the
   brand green instead. Desktop puts them back on green, white with a lime link. */
/* [data-stu-terms] is the app pages' terms trigger. It is a span rather than a link,
   because in the webview the terms open in a tray and there is no URL to go to, so it
   needs the link's appearance applied by hand. */
.stu-terms__copy a,
.stu-terms__copy [data-stu-terms] {
    color: #007a53;
    font-weight: 700;
    cursor: pointer;
}

/* ------------------------------------------------------------
   Desktop
   ------------------------------------------------------------ */
@media (min-width: 900px) {
    /* LANDING ONLY: green throughout, so the hero block stops being a distinct
       rounded panel and just merges into the ground. Scoped to .stu--home because the
       form pages keep the grey ground at every width - without the scope this turned
       their ground green, hid the green heading line against it, and left the terms
       label white on a white card. */
    .stu--home {
        color: #fff;
        --stu-grain: url("/static/assets/students/bg-texture-tile.webp");
        /* Keep the grain layer - a bare `background: <colour>` here replaces the whole
           shorthand and silently drops it. */
        background: var(--stu-grain) repeat, var(--stu-forest);
        box-shadow: 0 0 0 100vmax var(--stu-forest);
    }

    .stu--home .stu-heroblock { border-radius: 0; }

    /* The block's bottom padding holds the CTA off the rounded bottom edge on mobile.
       Here the whole page is green so it buys nothing visually, and it stacked with the
       block's margin to a 125px gap against the frames' 81. */
    .stu-heroblock { padding-bottom: 0; }

    /* The art's clip-path is a mobile device: on desktop the photo is positioned against
       the CARD, but it is still a DOM descendant of the art, so the art's clip would cut
       it. The card handles the break-out here. */
    .stu-hero__art { clip-path: none; }

    /* Green runs the whole page here, so the grain moves to .stu and the per-block
       overlays come off to avoid stacking two layers of it. Their solid green fills
       have to go too: they are positioned children, so they paint after .stu::before
       and would hide the grain behind them. */
    .stu::before { display: block; }

    .stu--home .stu-masthead,
    .stu--home .stu-heroblock { background: transparent; }

    /* Terms go back to the design's white-on-green with a lime link (landing only). */
    .stu--home .stu-terms { color: #fff; }
    .stu--home .stu-terms__copy a,
    .stu--home .stu-terms__copy [data-stu-terms] { color: var(--stu-lime); }

    /* The border moves out to wrap copy and artwork together.

       The radius is 36px rather than the base 24px so the INNER corner reads as
       rounded. A border's inner radius is whatever is left after the border width, so
       at 24px the inside came out at 24-16 = 8px, which against a 16px frame looked
       square. The desktop frame measures a ~20px inner corner, so 36 - 16 gets there.
       The outer then sits at 36 against the frame's ~29 - the two cannot be set
       independently without wrapping the card's contents in another box, and the inner
       is the one that shows. */
    .stu-hero__card {
        overflow: hidden;
        border: 16px solid var(--stu-lilac);
        border-radius: 36px;
        background: var(--stu-forest);
    }

    .stu-hero__art {
        /* static so the photo positions against the whole card, not just the
           artwork block - otherwise it only ever reaches the lockup's height. */
        position: static;
        border: 0;
        border-radius: 0;
        /* The card paints the forest here. Leaving the artwork block's own green on
           meant it drew a SQUARE green rectangle over the card's rounded inner corner,
           which is what made the inside of the frame look square. */
        background: none;
        padding: 28px 38% 0 24px;
        min-height: 0;
    }

    /* Eyebrow sits above the lockup here, inside the card. */
    .stu-hero__eyebrow {
        order: 0;
        margin: 27px 24px 2px; /* frames: 29px from the card's top border */
        /* Same 25px as mobile - the frames do not scale this between breakpoints. */
    }

    /* The artwork's top padding exists to hold the lockup off the card's top border in
       the default view. When the eyebrow is above it that spacing is already there, and
       the two stacked to a 51px gap against the frames' 15px. Desktop only: on mobile
       the eyebrow sits BELOW the artwork, which still needs its padding. */
    .stu-hero__eyebrow:not([hidden]) ~ .stu-hero__art { padding-top: 0; }

    /* Two lines on desktop: DOWNLOAD over FREE PIZZA, the pair still matched to the
       same width as each other. */
    .stu-lockup { white-space: nowrap; }
    .stu-lockup__l2,
    .stu-lockup__l3 { display: inline; }

    /* Explicit space: relying on the source whitespace between the two spans is not
       dependable once they go inline inside a nowrap parent. */
    .stu-lockup__l3::before { content: "\00a0"; }

    .stu-lockup__l1 { font-size: 16.45cqw; }             /* 151px at the 918px card */
    .stu-lockup__l2,
    .stu-lockup__l3 { font-size: 17.43cqw; }             /* 160px at the 918px card */

    /* Verified lockup, solved for its own words. */
    .stu-lockup--verified .stu-lockup__l1 { font-size: 15.04cqw; }
    .stu-lockup--verified .stu-lockup__l2,
    .stu-lockup--verified .stu-lockup__l3 { font-size: 16.84cqw; }

    /* Break out upward only: overflow cannot clip one side, so the card keeps its
       rounded clip on the left/right/bottom via clip-path and leaves the top open for
       her to escape into the section's padding. */
    .stu-hero { padding-top: 30px; }

    .stu-hero__card { overflow: visible; }
    .stu-hero__art { overflow: visible; }

    /* Open at the top only: negative inset lets her escape upward, while the other
       three sides stay clipped to the inner box so she keeps off the lilac frame. */
    .stu-hero__photowrap {
        overflow: visible;
        /* Follows the card's inner corner, so she stays off the frame. Keep in step
           with the card radius above: 36 - 16 = 20. */
        clip-path: inset(-60px 0 0 0 round 20px);
    }

    .stu-hero__photo {
        top: -30px;
        left: 66%;
        right: auto;
        height: calc(122% + 30px);
    }

    .stu-hero__intro {
        padding: 22px 24px 34px;
        max-width: 62%;
    }

    .stu-hero__cta { flex-wrap: nowrap; justify-content: flex-start; }
    .stu-hero__cta .stu-btn { width: auto; min-width: 228px; }

    /* Verified: the QR code takes the button's place, with the copy alongside it. The
       button is for small screens only - there is nothing to scan a code with here, and
       nothing to tap on a desktop either. */
    .stu-hero__verified {
        flex-direction: row;
        align-items: center;
        justify-content: flex-start;
        gap: 20px;
    }

    .stu-hero__qr {
        display: block;
        flex: 0 0 auto;
        width: 118px;
        height: 118px;
        /* The code needs a light quiet zone to scan reliably - it sits on the forest
           green otherwise. */
        padding: 8px;
        border-radius: 6px;
        background: #fff;
    }

    .stu-hero__verified .stu-hero__verified-copy {
        flex: 1 1 auto;
        max-width: 42ch;
    }

    .stu-hero__verified .stu-hero__appcta { display: none; }

    /* The hero card is the full 950px (its gutter comes from .stu-heroblock's padding),
       while .stu-inner's own 16px padding was eating into its 950px max-width - leaving
       the panels 918px and inset 16px against the card. Drop it here so both edges line
       up; mobile still needs it, and there both already measure 358. */
    .stu-inner { padding: 0; }

    /* Panels go side by side in DOM order: the free-pizza panel is artwork then copy,
       the Win-it-Wednesday panel is copy then artwork. --reverse only exists so the
       stacked view can pull the artwork back above the copy. */
    .stu-panel,
    .stu-panel--reverse { flex-direction: row; align-items: stretch; }

    .stu-panel__media,
    .stu-panel--lime .stu-panel__media {
        flex: 0 0 370px;
        min-height: 274px;
        aspect-ratio: auto;
    }

    .stu-panel__content { padding: 50px 66px; }
}


/* ============================================================
   Form pages: /students/register and /students/confirm
   ============================================================
   Same shell as the landing (green masthead, grey ground, grain), then a
   two-tone page heading, intro copy, and a white card holding the form.
   Desktop is two columns - copy and artwork left, card right; mobile stacks.

   Sampled from the frames: ground #e8ebea, card #fff, input fill #f9f9f9,
   disabled button #b9b9b9, heading green #1f3f32 (the forest token). */

.stu-page {
    max-width: 982px; /* 950px content box: two 450px columns with a 50px gutter */
    margin: 0 auto;
    padding: clamp(24px, 4vw, 48px) 16px clamp(40px, 6vw, 72px);
}

/* Two-tone heading: first line black, second line forest. */
.stu-pagehead {
    margin: 0 0 14px;
    font-family: "Papa Sans Medium Condensed", Impact, "Arial Narrow Bold", sans-serif;
    font-weight: 500;
    text-transform: uppercase;
    line-height: 0.92;
    letter-spacing: -0.01em;
    font-size: clamp(2.6rem, 14.36vw, 4.375rem); /* caps at 70px; 56px at a 390 viewport */
}

.stu-pagehead__a,
.stu-pagehead__b { display: block; }
.stu-pagehead__a { color: #000; }
.stu-pagehead__b { color: var(--stu-forest); }

.stu-pageintro {
    margin: 0 0 26px;
    font-size: clamp(1rem, 1.5vw, 1.125rem);
    line-height: 1.35;
    color: var(--stu-ink);
    /* Caps the measure on desktop and is a no-op on mobile, where the column is only
       358px - so the intro still runs the full width there. */
    max-width: 380px;
}

.stu-pageintro strong { font-weight: 700; }

/* The lilac artwork tile, reused from the landing panels. */
.stu-pagemedia {
    position: relative;
    overflow: hidden;
    border-radius: 16px;
    aspect-ratio: 1.6;
}

.stu-pagemedia img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ------------------------------------------------------------
   Card
   ------------------------------------------------------------ */
.stu-card {
    background: #fff;
    border-radius: 8px; /* frames: corner flush by +8px */
    padding: clamp(22px, 4vw, 30px);
    box-shadow: var(--stu-panel-shadow);
}

.stu-card__title {
    margin: 0 0 12px;
    font-size: 1.0625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--stu-ink);
}

.stu-card__copy {
    margin: 0 0 22px;
    font-size: 1rem;
    line-height: 1.35;
    color: var(--stu-ink);
}

.stu-card__copy strong { font-weight: 700; }

/* ------------------------------------------------------------
   Fields
   ------------------------------------------------------------ */
.stu-f { margin-bottom: 14px; } /* pitch ~82px between inputs; frames: 81 */

.stu-f > label {
    display: block;
    margin-bottom: 6px;
    font-size: 0.9375rem;
    font-weight: 700;
    color: var(--stu-ink);
}

.stu-f input[type="text"],
.stu-f input[type="email"] {
    width: 100%;
    /* Explicit height: the intrinsic height was collapsing to 30px, squeezing the
       padding, and no stylesheet rule sets a height on these - the same class of
       problem as the code boxes losing to UA sizing. The frames measure 43px, so the
       padding is horizontal only and the input centres its own text. */
    height: 44px;
    padding: 0 14px;
    border: 1px solid #dbdbdb; /* frames: #dbdbdb */
    border-radius: 8px;
    background: #fff;          /* frames: empty fields are pure white */
    color: var(--stu-ink);
    font-family: inherit;
    font-size: 1rem;
    -webkit-appearance: none;
    appearance: none;
}

.stu-f input::placeholder { color: #9a9a9a; }

.stu-f input:focus {
    outline: 2px solid var(--stu-forest);
    outline-offset: 1px;
    border-color: var(--stu-forest);
}

.stu-f input.is-invalid { border-color: var(--stu-error); }

.stu-f__error {
    display: block;
    margin-top: 6px;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--stu-error);
}

/* Terms checkbox */
.stu-agree {
    color: var(--stu-ink);
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin: 18px 0 22px;
    font-size: 0.9375rem;
    line-height: 1.35;
    cursor: pointer;
}

.stu-agree input {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    margin: 0;
    border: 1px solid #d5d5d5;
    border-radius: 6px;
    accent-color: var(--stu-forest);
}

.stu-agree a,
.stu-agree [data-stu-terms] {
    color: var(--stu-basil);
    font-weight: 700;
    text-decoration: underline;
    cursor: pointer;
}

/* The design shows the primary action in its disabled state, so it needs to read
   clearly as not-yet-available rather than just dimmed. */
.stu-btn:disabled,
.stu-btn[aria-disabled="true"] {
    background: #d8d8d8;
    color: #757575;
    cursor: not-allowed;
    text-decoration: none;
}

.stu-btn--block { width: 100%; }

/* Form-level error summary */
.stu-formerror {
    margin: 0 0 18px;
    padding: 12px 14px;
    border-radius: 8px;
    background: #fdf0ef;
    color: var(--stu-error);
    font-size: 0.9rem;
    line-height: 1.4;
}

.stu-formerror ul { margin: 0; padding-left: 0; list-style: none; }

/* ------------------------------------------------------------
   Verification code
   ------------------------------------------------------------ */
.stu-codeboxes {
    display: flex;
    gap: 8px;
    margin: 0 0 18px;
}

.stu-codeboxes input {
    flex: 1 1 0;
    min-width: 0;
    /* Explicit height, not aspect-ratio: on form controls the UA's intrinsic sizing
       wins and the boxes collapse to line-height. Measured off the frames at ~52px
       mobile / ~48px desktop. */
    height: 52px;
    padding: 0;
    border: 1px solid #e2e2e2;
    border-radius: 8px;
    background: #fff;
    color: var(--stu-ink);
    font-family: inherit;
    font-size: 1.375rem;
    font-weight: 700;
    text-align: center;
    -webkit-appearance: none;
    appearance: none;
}

.stu-codeboxes input::placeholder { color: #cfcfcf; }

.stu-codeboxes input:focus {
    outline: 2px solid var(--stu-forest);
    outline-offset: 1px;
    border-color: var(--stu-forest);
}

.stu-codeboxes.is-invalid input { border-color: var(--stu-error); }
.stu-codeboxes.is-busy { opacity: 0.55; pointer-events: none; }

/* OR divider */
.stu-or {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 18px 0;
    font-size: 0.875rem;
    color: #8a8a8a;
    text-transform: uppercase;
}

.stu-or::before,
.stu-or::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: #e2e2e2;
}

/* ------------------------------------------------------------
   Action rows (resend / change email)
   ------------------------------------------------------------ */
.stu-action {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    margin-bottom: 12px;
    padding: 14px 16px;
    border: 1px solid #e2e2e2;
    border-radius: 10px;
    background: #fff;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    color: var(--stu-ink);
}

.stu-action:disabled { opacity: 0.55; cursor: not-allowed; }

.stu-action__icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    font-size: 1.05rem;
    line-height: 1;
}

.stu-action__icon i { font-size: 1rem; line-height: 1; }

.stu-action__icon--lime  { background: var(--stu-lime); }
.stu-action__icon--lilac { background: var(--stu-lilac); }

.stu-action__text { flex: 1 1 auto; min-width: 0; }

.stu-action__label {
    display: block;
    font-size: 1rem;
    font-weight: 700;
}

/* Sub-labels are desktop only - the mobile frames show the label alone. */
.stu-action__sub {
    display: none;
    margin-top: 2px;
    font-size: 0.875rem;
    color: #6b6b6b;
}

.stu-action__chev {
    flex: 0 0 auto;
    font-size: 1rem;
    color: var(--stu-ink);
}

/* Resend outcome, reported on the button rather than under the card. The success state
   holds for a few seconds and then hands the label back to the cooldown countdown, so
   it needs to read as a confirmation and not as something still pressable - hence the
   filled green and no chevron. `disabled` is already set by the cooldown underneath,
   so the opacity rule above is overridden here or the confirmation would look greyed
   out. */
.stu-action--success,
.stu-action--success:disabled {
    background: var(--stu-basil);
    border-color: var(--stu-basil);
    color: #fff;
    opacity: 1;
    cursor: default;
}

.stu-action--success .stu-action__label,
.stu-action--success .stu-action__sub { color: #fff; }

.stu-action--success .stu-action__chev { display: none; }

/* The icon disc keeps its own colour so the row still reads as the resend action. */
.stu-action--busy,
.stu-action--busy:disabled { opacity: 1; }

.stu-action--busy .stu-action__label { color: var(--stu-ink); }

.stu-note {
    margin: 14px 0 0;
    font-size: 0.875rem;
    line-height: 1.35;
    color: #000; /* frames: darkest glyph pixel is rgb(0,0,0) */
}

.stu-status {
    display: block;
    margin-top: 8px;
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--stu-forest);
}

/* Announced but not shown. The resend outcome reads on the button itself, and repeating
   it under the card as well was the thing being replaced - but this element is the live
   region, so it still has to carry the text for a screen reader. */
.stu-status--quiet {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Stacked order is copy -> card -> media, matching the mobile frames, which is also
   the DOM order. */
.stu-cols > * + * { margin-top: 24px; }

/* ------------------------------------------------------------
   Desktop: two columns
   ------------------------------------------------------------
   Grid areas rather than source order, because the artwork sits under the copy on
   the left here but below the card when stacked. */
@media (min-width: 900px) {
    .stu-cols {
        display: grid;
        /* Two 450px columns with a 50px gutter. minmax caps rather than fixes them, so
           between the 900px breakpoint and the 982px this needs they narrow instead of
           overflowing into a horizontal scroll. */
        grid-template-columns: repeat(2, minmax(0, 450px));
        grid-template-areas:
            "copy  card"
            "media card";
        grid-template-rows: max-content max-content;
        gap: 24px 50px;
        justify-content: center;
        align-items: start;
    }

    .stu-cols > * + * { margin-top: 0; }

    .stu-cols__copy  { grid-area: copy; }
    .stu-cols__media { grid-area: media; }
    .stu-card        { grid-area: card; }

    .stu-codeboxes input { height: 48px; }

    /* The frames have the card bottom and the artwork bottom level (681 and 682), so
       the card stretches across both grid rows rather than sizing to its content. */
    .stu-card {
        align-self: stretch;
        padding: 26px; /* frames: 26px left padding */
    }

    .stu-action__sub { display: block; }
    .stu-card__title { font-size: 1.125rem; }
}


/* ------------------------------------------------------------
   Fill to the footer
   ------------------------------------------------------------
   These pages are short, so on a tall window the page ran out before the viewport did
   and left white below the footer. bodyClass puts .stu-fill on .siteContainer, which
   also wraps the footer, so a flex column with the ground growing pushes the footer to
   the bottom. */
.siteContainer.stu-fill {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.siteContainer.stu-fill > .stu { flex: 1 0 auto; }
