/* ============================================================================
   Student Application V3 - shared shell, Craig hero, teleprompter, chat

   Scope: EVERYTHING here is under .sa-v3. The public CEO sites load the Porto
   theme globally, so an unscoped rule would leak into the marketing pages. The
   extranet's body.v3 brand layer is NOT loaded out here, which is why the brand
   tokens are declared locally instead of pulled from v3-branding.css.

   UNITS ARE px ON PURPOSE - do NOT reintroduce rem. The Porto theme this page
   loads sets a 10px root font size (the 62.5% trick), so `1rem` here is 10px,
   not 16px, and every rem-sized rule silently rendered at 62.5% of its intent
   (the 22px phone input came out at 10px). rem is relative to the ROOT, so
   scoping a font-size onto .sa-v3 does not fix it. px matches the mockup, which
   is also authored in px.

   Mobile-first: ~90% of applications are completed on a phone, so the phone
   rules are the base and desktop is the media query.

   Design direction (mockups/student-app-v3-design.html): card-LESS. No card
   border, no shadow container, no top accent bar. Edge-to-edge content,
   full-width fields, brand colour in accents only.
   ============================================================================ */

.sa-v3 {
    --sa-orange: #f95602;
    --sa-orange-soft: #ffe1c8;
    --sa-ink: #242424;
    --sa-body: #2d3748;
    --sa-muted: #6b7280;
    --sa-mid-grey: #4d4d4d;
    --sa-line: #e5e7eb;
    --sa-bg: #ffffff;

    background: var(--sa-bg);
    color: var(--sa-body);
    font-family: Roboto, "Open Sans", "Helvetica Neue", Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------- shell --- */

.sa-v3 .sa-shell {
    max-width: 640px;
    margin: 0 auto;
    padding: 20px 18px 64px;
}

.sa-v3 .sa-brandhead {
    text-align: center;
    margin: 8px 0 2px;
}

.sa-v3 .sa-brandhead img {
    max-width: 210px;
    height: auto;
}

/* Compact header, revealed only once the real brand head has scrolled out of view. Mirrors the
   pinned composer's geometry so the two agree on desktop: shell width, centred, fixed. Off-screen
   and inert until the observer turns it on, so it never covers the top of a short page. */
.sa-v3 .sa-stickyhead {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%) translateY(-100%);
    width: 100%;
    max-width: 640px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 11px 18px calc(11px + env(safe-area-inset-top, 0px));
    background: var(--sa-bg);
    border-bottom: 1px solid var(--sa-line);
    z-index: 25;
    opacity: 0;
    pointer-events: none;
    transition: transform .22s ease, opacity .22s ease;
}

.sa-v3 .sa-stickyhead.is-on {
    transform: translateX(-50%) translateY(0);
    opacity: 1;
}

.sa-v3 .sa-stickyhead img {
    max-height: 24px;
    width: auto;
    flex: 0 0 auto;
}

/* Same script wordmark as the page, sized down - so the compact header reads as the same
   thing rather than a different component. */
.sa-v3 .sa-stickyhead-title {
    font-family: "Shadows Into Light", cursive;
    color: var(--sa-orange);
    font-size: 17px;
    line-height: 1;
    white-space: nowrap;
    flex: 0 0 auto;
}

@media (min-width: 768px) {
    .sa-v3 .sa-stickyhead { padding-left: 24px; padding-right: 24px; }
}

.sa-v3 .sa-script {
    font-family: "Shadows Into Light", cursive;
    color: var(--sa-orange);
    font-size: 30px;
    line-height: 1.1;
    text-align: center;
    margin: 6px 0 14px;
}

/* Stepper - numbered circles on a connecting rail, values from the mockup's .pstep.
   Completed steps show a check in an orange ring, the current one is filled, the rest
   are outlined grey. The rail is drawn with ::before on each step and suppressed on
   the first, so it never overhangs either end. */

.sa-v3 .sa-steps {
    display: flex;
    margin: 2px 0 18px;
}

.sa-v3 .sa-step {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    box-sizing: border-box;
    text-align: center;
    font-size: 8.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #b5b5b5;
    padding: 0 2px;
}

/* The stepper is navigation now: a step at or before the furthest one reached is an <a> with
   an href, anything ahead of it is an <a> WITHOUT one. Reset the theme's link styling so a
   clickable step looks identical to the old <span> until it is hovered. */
.sa-v3 .sa-step,
.sa-v3 a.sa-step:link,
.sa-v3 a.sa-step:visited {
    color: #b5b5b5;
    text-decoration: none;
}

.sa-v3 a.sa-step[href] {
    cursor: pointer;
}

.sa-v3 a.sa-step[href]:hover,
.sa-v3 a.sa-step[href]:focus {
    color: var(--sa-orange);
    text-decoration: none;
}

.sa-v3 a.sa-step[href]:hover i {
    border-color: var(--sa-orange);
    color: var(--sa-orange);
}

.sa-v3 a.sa-step.is-current[href]:hover i,
.sa-v3 a.sa-step.is-done[href]:hover i {
    /* already filled - keep the fill, do not wash it out on hover */
    color: #fff;
}

/* Ahead of where they have got to: inert, and visibly so. */
.sa-v3 .sa-step.is-locked {
    cursor: default;
}

.sa-v3 .sa-step i {
    font-style: normal;
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 5px;
    border: 2px solid #cfcfcf;
    border-radius: 50%;
    background: #fff;
    font-size: 11px;
    font-weight: 700;
    position: relative;
    z-index: 1;
    color: #b5b5b5;
}

.sa-v3 .sa-step::before {
    content: '';
    position: absolute;
    top: 13px;
    left: calc(-50% + 13px);
    right: calc(50% + 13px);
    height: 2px;
    background: #dedede;
}

.sa-v3 .sa-step:first-child::before { display: none; }

.sa-v3 .sa-step.is-done i { border-color: var(--sa-orange); color: var(--sa-orange); }
.sa-v3 .sa-step.is-done::before,
.sa-v3 .sa-step.is-current::before { background: var(--sa-orange); }
.sa-v3 .sa-step.is-current i { background: var(--sa-orange); border-color: var(--sa-orange); color: #fff; }
.sa-v3 .sa-step.is-current { color: var(--sa-orange); }

.sa-v3 .sa-qhead {
    font-family: Montserrat, Roboto, sans-serif;
    font-weight: 700;
    font-size: 16px;
    line-height: 1.3;
    color: var(--sa-ink);
    margin: 18px 0 2px;
}

.sa-v3 .sa-lead {
    font-size: 14px;
    line-height: 1.55;
    color: var(--sa-mid-grey);
    margin: 4px 0 14px;
}

/* Fields go full-width and edge-to-edge; no card chrome. */
.sa-v3 .sa-field { margin-bottom: 13px; }

.sa-v3 .sa-field label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: #333;
    margin-bottom: 4px;
}

.sa-v3 .sa-field input[type="text"],
.sa-v3 .sa-field input[type="email"],
.sa-v3 .sa-field input[type="tel"],
.sa-v3 .sa-field input[type="date"],
.sa-v3 .sa-field select,
.sa-v3 .sa-field textarea {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 11px 12px;
    font-size: 15px;
    color: #333;
    background: #fff;
    transition: border-color .15s ease, box-shadow .15s ease;
}

.sa-v3 .sa-field input:focus,
.sa-v3 .sa-field select:focus,
.sa-v3 .sa-field textarea:focus {
    outline: none;
    border-color: var(--sa-orange);
    box-shadow: 0 0 0 3px rgba(249, 86, 2, .12);
}

.sa-v3 .sa-btn {
    display: inline-block;
    width: 100%;
    border: 0;
    border-radius: 10px;
    background: var(--sa-orange);
    color: #fff;
    box-sizing: border-box;
    font-weight: 700;
    font-size: 15px;
    padding: 13px 24px;
    cursor: pointer;
    transition: filter .15s ease;
}

.sa-v3 .sa-btn:hover { filter: brightness(1.06); }
.sa-v3 .sa-btn[disabled] { opacity: .55; cursor: default; }

.sa-v3 .sa-btn-quiet {
    background: transparent;
    color: var(--sa-muted);
    font-weight: 500;
    text-decoration: underline;
}

.sa-v3 .sa-note {
    font-size: 13px;
    color: var(--sa-muted);
    text-align: center;
    margin-top: 14px;
}

.sa-v3 .sa-error {
    background: #fef2f2;
    border: 1px solid #fecaca;
    color: #991b1b;
    border-radius: 10px;
    padding: 12px 14px;
    font-size: 14px;
    margin-bottom: 16px;
}

/* Fronted by the Craig hero until his audio ends; the JS drops this class. */
.sa-v3 .sa-hidden { display: none; }

/* Fragment swap - the shell cross-fades rather than flashing white. */
.sa-v3 .sa-step-body { transition: opacity .18s ease; }
.sa-v3 .sa-step-body.is-swapping { opacity: .35; }

/* ------------------------------------------------------------ Craig hero --- */
/* Two states on one element: hero (big, centred) collapsing to compact
   (small photo top-left, bubble keeps his words, form fades in below). The
   collapse trigger is the audio 'ended' event, never a timer. */

.sa-v3 .sa-craig { margin: 0 0 22px; }

.sa-v3 .sa-craig-photo {
    display: block;
    border-radius: 50%;
    border: 3px solid var(--sa-orange);
    object-fit: cover;
    margin: 0 auto;
    width: 120px;
    height: 120px;
    transition: width .4s ease, height .4s ease, margin .4s ease, border-width .4s ease;
}

.sa-v3 .sa-craig.is-playing .sa-craig-photo {
    box-shadow: 0 0 0 0 rgba(249, 86, 2, .45);
    animation: saCraigPulse 2s infinite;
}

@keyframes saCraigPulse {
    0%   { box-shadow: 0 0 0 0 rgba(249, 86, 2, .40); }
    70%  { box-shadow: 0 0 0 14px rgba(249, 86, 2, 0); }
    100% { box-shadow: 0 0 0 0 rgba(249, 86, 2, 0); }
}

.sa-v3 .sa-craig.is-compact { display: flex; align-items: flex-start; gap: 12px; }

.sa-v3 .sa-craig.is-compact .sa-craig-photo {
    width: 44px;
    height: 44px;
    border-width: 2px;
    margin: 0;
    flex: 0 0 auto;
}

/* Compact: the text sits BESIDE the avatar, so it starts at his top edge. The 14px below is for
   the large centred state, where the text sits under the photo and needs the gap. */
.sa-v3 .sa-craig.is-compact .sa-craig-body { flex: 1 1 auto; text-align: left; margin-top: 0; }
.sa-v3 .sa-craig-body { text-align: center; margin-top: 14px; }

/* Karaoke teleprompter - a fixed TWO-LINE rolling window, not a paragraph
   reveal. Mirrors the timeline tour (.tct-viewport / .tct-caption in
   170-timeline-craig.css); matters most for the guardian script, which runs
   long. The caption translateYs up a line at a time as Craig speaks. */

.sa-v3 .sa-tct-viewport {
    height: 3.6em;
    line-height: 1.8;
    overflow: hidden;
    position: relative;
    /* Fade the BOTTOM only. scrollToWord translateYs the spoken word's line to offset 0, so
       line 1 is the line Craig is reading and line 2 is the upcoming preview - a top fade
       would dim the active line. Opaque through 58% covers line 1 (0-50%) with a margin. */
    -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 58%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 0, #000 58%, transparent 100%);
}

.sa-v3 .sa-tct-caption {
    transition: transform .35s ease;
    will-change: transform;
    font-size: 16px;
}

.sa-v3 .sa-tct-word { color: #b4bcc8; transition: color .12s linear; }
.sa-v3 .sa-tct-word.is-spoken { color: var(--sa-body); }
.sa-v3 .sa-tct-word.is-current { color: var(--sa-orange); font-weight: 700; }

.sa-v3 .sa-craig-controls {
    display: flex;
    justify-content: center;
    gap: 16px;
    margin-top: 10px;
}

.sa-v3 .sa-craig.is-compact .sa-craig-controls { justify-content: flex-start; }

.sa-v3 .sa-craig-controls button {
    background: none;
    border: 0;
    padding: 0;
    font-size: 13px;
    color: var(--sa-muted);
    text-decoration: underline;
    cursor: pointer;
}

/* Full text, shown when the visitor asks for it (accessibility). */
.sa-v3 .sa-craig-fulltext {
    display: none;
    /* No top margin: it is the FIRST thing in the body, so its own spacing would stack on the
       body's and push the text off the top of the avatar. The body owns the gap. */
    margin-top: 0;
    font-size: 15px;
    line-height: 1.6;
    color: var(--sa-body);
    text-align: left;
}

.sa-v3 .sa-craig.is-showtext .sa-craig-fulltext { display: block; }
.sa-v3 .sa-craig.is-showtext .sa-tct-viewport { display: none; }

/* ------------------------------------------------------------ interview --- */
/* Per docs/design/craig-chat-design.md: Craig speaks as plain flowing text
   with no bubble; the visitor gets a compact right-aligned pill. No Send
   button - Enter sends. */

.sa-v3 .sa-chat { margin-top: 8px; }

.sa-v3 .sa-msgs {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-bottom: 18px;
}

.sa-v3 .sa-msg-craig {
    font-size: 13px;
    line-height: 1.6;
    color: var(--sa-body);
}

/* The applicant's turn mirrors Craig's: bubble plus a round avatar, but on the
   right and with their initials in place of a photo. */
.sa-v3 .sa-msg-user {
    align-self: flex-end;
    display: flex;
    gap: 10px;
    align-items: flex-start;
    max-width: 88%;
}

.sa-v3 .sa-msg-bubble-user {
    background: var(--sa-orange-soft);
    color: var(--sa-ink);
    border-radius: 14px 14px 4px 14px;
    padding: 10px 14px;
    font-size: 13px;
    line-height: 1.55;
}

.sa-v3 .sa-msg-initials {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--sa-orange);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .02em;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 2px;
    text-transform: uppercase;
}

.sa-v3 .sa-typing { display: none; gap: 5px; align-items: center; }
.sa-v3 .sa-typing.is-on { display: flex; }

.sa-v3 .sa-typing span {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--sa-orange);
    opacity: .35;
    animation: saBlink 1.3s infinite;
}

.sa-v3 .sa-typing span:nth-child(2) { animation-delay: .18s; }
.sa-v3 .sa-typing span:nth-child(3) { animation-delay: .36s; }

@keyframes saBlink {
    0%, 80%, 100% { opacity: .28; }
    40%           { opacity: 1; }
}

.sa-v3 .sa-composer textarea {
    width: 100%;
    min-height: 76px;
    resize: vertical;
    border: 1px solid var(--sa-line);
    border-radius: 12px;
    padding: 13px 14px;
    font-size: 16px;
    line-height: 1.5;
    color: var(--sa-ink);
}

.sa-v3 .sa-composer textarea:focus {
    outline: none;
    border-color: var(--sa-orange);
    box-shadow: 0 0 0 3px rgba(249, 86, 2, .12);
}

.sa-v3 .sa-composer-note {
    font-size: 12px;
    color: var(--sa-muted);
    margin-top: 6px;
}

/* Claude unreachable - the interview is a hard stop, there is no survey
   fallback (locked 2026-07-17). Other steps stay completable. */
.sa-v3 .sa-unavailable {
    background: #fffbeb;
    border: 1px solid #fde68a;
    color: #92400e;
    border-radius: 10px;
    padding: 14px;
    font-size: 15px;
}

/* --------------------------------------------------------------- desktop --- */

@media (min-width: 768px) {
    .sa-v3 .sa-shell { padding: 32px 24px 80px; }
    .sa-v3 .sa-qhead { font-size: 28px; }
    /* Auto width would leave the button flush LEFT while the heading and lead above it are
       centered - it only looked right on mobile because width:100% filled the column. */
    .sa-v3 .sa-btn { width: auto; min-width: 200px; display: block; margin-left: auto; margin-right: auto; }
}

/* --------------------------------------------------------- accessibility --- */

@media (prefers-reduced-motion: reduce) {
    .sa-v3 .sa-stickyhead,
    .sa-v3 .sa-craig-photo,
    .sa-v3 .sa-tct-caption,
    .sa-v3 .sa-step-body { transition: none; }

    .sa-v3 .sa-craig.is-playing .sa-craig-photo { animation: none; }
    .sa-v3 .sa-typing span { animation: none; opacity: .6; }
}

/* ------------------------------------------------------------------ 2FA --- */
/* Values lifted from mockups/student-app-v3-design.html sections 2 and 3 so the
   built screen matches the approved design rather than approximating it. */

/* Logo sizing, derived rather than eyeballed.

   The mockup embeds the NON-web horizontal PNG: 500x154 (3.25:1), with roughly
   35px of whitespace baked above and below the mark. The -Web asset is that same
   mark trimmed: 1071x179 (5.98:1). So the mockup's CSS height:62px does NOT mean
   a 62px logo - it means a 62px BOX whose visible mark is about 201x34.

   Hence: 34px tall on the trimmed asset reproduces the mockup's optical size,
   and the padding here replaces the whitespace the web asset no longer carries
   (~14px each side) plus the mockup's own .brandhead margin (8px top / 2px
   bottom) and the gap before the script line. */
.sa-v3 .sa-brandhead.sa-lg { padding: 22px 0 22px; }
.sa-v3 .sa-brandhead.sa-lg img { height: 34px; width: auto; max-width: 100%; }

.sa-v3 .sa-center { text-align: center; }
.sa-v3 .sa-req { color: var(--sa-orange); }

/* Phone entry is set large so the number fills the field; type="tel" brings up
   the number pad on iPhone, which is where most of these are typed. */
.sa-v3 .sa-field input.sa-tel,
.sa-v3 .sa-tel {
    font-size: 32px;
    font-weight: 600;
    letter-spacing: .02em;
    padding: 14px;
    text-align: center;
    /* FormBuilder adds .form-control, and bootstrap pins that to height:34px - which
       clamps the field no matter what font-size we set. Selector specificity already
       beats .form-control, so an explicit auto is enough; no !important needed. */
    height: auto;
}

/* The 2FA screen centres its single field; multi-field steps keep left-aligned
   labels, so this is opt-in per field rather than a change to .sa-field. */
.sa-v3 .sa-field-center label { text-align: center; }

.sa-v3 .sa-smsopt {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    font-size: 13px;
    line-height: 1.45;
    margin: 2px 0 8px;
    font-weight: 400;
    color: var(--sa-body);
}

.sa-v3 .sa-smsopt input {
    width: 19px;
    height: 19px;
    accent-color: var(--sa-orange);
    flex: 0 0 auto;
    margin-top: 1px;
}

.sa-v3 .sa-smsterms {
    font-size: 11px;
    color: #9a9a9a;
    line-height: 1.5;
    margin: 0 0 4px;
}

.sa-v3 .sa-smsterms a { color: var(--sa-orange); }

.sa-v3 .sa-finenote {
    font-size: 11.5px;
    color: #9a9a9a;
    text-align: center;
    margin-top: 12px;
    line-height: 1.5;
}

.sa-v3 .sa-finenote a { color: var(--sa-orange); font-weight: 600; }

/* The code boxes are the one place this screen spends its accent. */
.sa-v3 .sa-codes {
    display: flex;
    gap: 12px;
    justify-content: center;
    margin: 32px 0 28px;
}

.sa-v3 .sa-code-box {
    width: 52px;
    height: 62px;
    border: 1.5px solid var(--sa-orange);
    border-radius: 10px;
    text-align: center;
    font-size: 26px;
    font-weight: 700;
    color: var(--sa-ink);
    background: #fff;
    padding: 0;
    -moz-appearance: textfield;
}

.sa-v3 .sa-code-box::-webkit-outer-spin-button,
.sa-v3 .sa-code-box::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.sa-v3 .sa-code-box:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(249, 86, 2, .22);
}

/* Narrow phones: 6 boxes at 52px will not fit 390px minus padding. */
@media (max-width: 400px) {
    .sa-v3 .sa-codes { gap: 8px; }
    .sa-v3 .sa-code-box { width: 44px; height: 56px; font-size: 22px; }
}

/* FormBuilder renders its own validation summary markup; keep it legible
   without restyling the framework's classes globally. */
.sa-v3 .form-error,
.sa-v3 .validation-summary-errors {
    background: #fef2f2;
    border: 1px solid #fecaca;
    color: #991b1b;
    border-radius: 10px;
    padding: 12px 14px;
    font-size: 14px;
    margin-bottom: 16px;
}

.sa-v3 .validation-summary-errors ul { margin: 0; padding-left: 18px; }

/* ----------------------------------------------------- Twilio SMS opt-in --- */
/* Twilio.FormOptInInputAndMessage is shared with the SignupLead forms, so its
   markup is not ours to change - it emits .control-radio + a label + a raw
   localization message. Style it in place instead of forking the function.
   It renders ONLY when the visitor has not opted in before (cookie); returning
   students get a hidden input and see nothing here. */

.sa-v3 .sa-smsopt-wrap {
    font-size: 11px;
    color: #9a9a9a;
    line-height: 1.5;
    margin: 2px 0 4px;
}

.sa-v3 .sa-smsopt-wrap a { color: var(--sa-orange); }

.sa-v3 .sa-smsopt-wrap .control-radio { margin: 0 0 8px; }

.sa-v3 .sa-smsopt-wrap .control-radio label {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    font-size: 13px;
    line-height: 1.45;
    font-weight: 400;
    color: var(--sa-body);
    margin: 0;
    cursor: pointer;
}

.sa-v3 .sa-smsopt-wrap input[type="checkbox"] {
    width: 19px;
    height: 19px;
    accent-color: var(--sa-orange);
    flex: 0 0 auto;
    margin: 1px 0 0;
}

/* The mockup gives the button and the fine note more air on this screen. */
.sa-v3 .sa-smsopt-wrap + .sa-btn { margin-top: 18px; }
.sa-v3 .sa-finenote { margin-top: 16px; }
.sa-v3 .sa-finenote a { text-decoration: underline; }

/* ------------------------------------------------------- paired field rows --- */
/* Mockup .frow: tight pairs share a row (name, DOB/phone, school/year,
   city/state/ZIP). Collapses to stacked on very narrow screens. */

.sa-v3 .sa-row { display: flex; gap: 10px; }
.sa-v3 .sa-row .sa-field { flex: 1; min-width: 0; }
.sa-v3 .sa-row .sa-field-st { flex: 0 0 66px; }
.sa-v3 .sa-row .sa-field-zip { flex: 0 0 86px; }

.sa-v3 .sa-fieldnote {
    display: block;
    font-size: 11px;
    color: #9a9a9a;
    line-height: 1.45;
    margin-top: 4px;
}

/* ~44px tall at 15px so iOS does not zoom-jump on focus. */
.sa-v3 .sa-field input.sa-ctrl,
.sa-v3 .sa-field select.sa-ctrl {
    min-height: 44px;
}

@media (max-width: 360px) {
    .sa-v3 .sa-row { flex-wrap: wrap; }
    .sa-v3 .sa-row .sa-field-st,
    .sa-v3 .sa-row .sa-field-zip { flex: 1 1 auto; }
}

/* --------------------------------------------------------- agreement (2) --- */

.sa-v3 .sa-agreement {
    border: 1px solid var(--sa-line);
    border-radius: 10px;
    padding: 14px 16px;
    margin: 4px 0 16px;
    max-height: 300px;
    overflow-y: auto;
}

.sa-v3 .sa-agreement-head {
    font-family: Montserrat, Roboto, sans-serif;
    font-size: 14px;
    font-weight: 700;
    color: var(--sa-ink);
    margin: 0 0 8px;
}

.sa-v3 .sa-agreement p {
    font-size: 13px;
    line-height: 1.55;
    color: var(--sa-mid-grey);
    margin: 0 0 10px;
}

.sa-v3 .sa-agree-check { display: flex; gap: 10px; align-items: flex-start; }
.sa-v3 .sa-agree-check label { font-size: 13px; line-height: 1.45; color: var(--sa-body); margin: 0; }

/* ------------------------------------------------------------ review (5) --- */

.sa-v3 .sa-rev {
    border-top: 1px solid var(--sa-line);
    padding: 14px 0 12px;
}

.sa-v3 .sa-rev:first-of-type { border-top: 0; }

.sa-v3 .sa-rev-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-family: Montserrat, Roboto, sans-serif;
    font-size: 13px;
    font-weight: 700;
    color: var(--sa-ink);
    margin: 0 0 6px;
}

.sa-v3 .sa-rev-head a { font-size: 12px; font-weight: 600; color: var(--sa-orange); text-decoration: underline; }

.sa-v3 .sa-rev-kv { font-size: 13px; line-height: 1.6; color: var(--sa-mid-grey); }

.sa-v3 .sa-chip {
    display: inline-block;
    background: var(--sa-orange-soft);
    color: #7c3a06;
    border-radius: 999px;
    padding: 4px 11px;
    font-size: 12px;
    font-weight: 600;
}

.sa-v3 .sa-chip-todo { background: #f3f4f6; color: var(--sa-muted); }

/* A link styled as the primary button (the step-3 skip). */
.sa-v3 a.sa-btn { text-align: center; text-decoration: none; }

/* -------------------------------------------------- interview voice mode --- */
/* Governed by Configuration.StudentApplication.InterviewAudioEnabled. With voice ON,
   Craig's words are hidden behind a play control until the visitor asks for them,
   either by playing or by flipping "Show text". With voice OFF the text is simply
   always there and none of this applies. */

.sa-v3 .sa-voicebar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 11.5px;
    color: var(--sa-muted);
    border-bottom: 1px solid var(--sa-line);
    padding-bottom: 8px;
    margin-bottom: 14px;
}

.sa-v3 .sa-voicebar-lbl { font-weight: 600; color: var(--sa-orange); }

.sa-v3 .sa-voicebar-toggle {
    background: none;
    border: 0;
    padding: 0;
    font-size: 11.5px;
    color: var(--sa-muted);
    text-decoration: underline;
    cursor: pointer;
}

/* Voice on: the words are ALWAYS on screen - the same karaoke mechanic as the
   timeline tour and the step-intro hero, just without the rolling viewport. The
   word being spoken lights orange; everything else reads normally, so a visitor
   who would rather read is never waiting on audio. */

/* Craig gets his photo and a bubble on this surface. That is a deliberate departure
   from craig-chat-design.md, which prefers unadorned flowing text - but that guidance
   is written for the 380px Ask Craig rail, and the mockup for the student application
   (a full-width column an applicant reads on a phone) draws avatar + bubble. */

.sa-v3 .sa-msg-craig {
    position: relative;
    display: flex;
    gap: 10px;
    align-items: flex-start;
    /* Stops short of the right edge so Craig's turns and the applicant's stagger
       against each other rather than both spanning the column - the shape that
       makes a transcript read as a conversation at a glance. */
    max-width: 88%;
    align-self: flex-start;
}

.sa-v3 .sa-msg-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 2px solid var(--sa-orange);
    object-fit: cover;
    flex: 0 0 auto;
    margin-top: 2px;
}

.sa-v3 .sa-msg-bubble {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    background: #f6f7f9;
    border-radius: 14px 14px 14px 4px;
    /* Extra bottom padding reserves a band for the audio control, so it never lands
       on the last line - cheaper than indenting every line for it. */
    padding: 11px 14px 30px;
    min-height: 40px;
}

.sa-v3 .sa-word { transition: color .1s linear; }

.sa-v3 .sa-word.is-current {
    color: var(--sa-orange);
    font-weight: 600;
}

/* The audio control sits bottom-right of every one of Craig's bubbles - history
   included - so any turn can be heard again. Quiet until hovered: it is a
   second-listen affordance, not a call to action. */
.sa-v3 .sa-replay {
    position: absolute;
    right: 8px;
    bottom: 8px;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    /* Quiet grey at rest, brand orange on hover - it is a second-listen affordance,
       so it should be findable without competing with the answer box for attention. */
    background: #c7ccd4;
    color: #fff;               /* knocked-out glyph on the fill */
    font-size: 12px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background .15s ease, transform .15s ease;
}

.sa-v3 .sa-replay:hover,
.sa-v3 .sa-replay:focus-visible {
    background: var(--sa-orange);
    transform: scale(1.06);
}

/* Fetching the audio for the first time. A replay skips this entirely - the clip is
   cached on the device - so the pulse only ever shows on a first listen. */
.sa-v3 .sa-replay.is-loading { background: var(--sa-orange); animation: saBlink 1s infinite; }

/* Craig is mid-sentence. */
/* While Craig is actually speaking that turn the button stays orange and rings,
   so the source of the voice is obvious when several turns are on screen. */
.sa-v3 .sa-msg-craig.is-speaking .sa-replay {
    background: var(--sa-orange);
    box-shadow: 0 0 0 0 rgba(249, 86, 2, .45);
    animation: saCraigPulse 2s infinite;
}

/* Craig's turn indents slightly while it has a replay control so the glyph never
   collides with the last line of text. */


/* The done strip lives INSIDE the composer, between the answer box and the hint line: the box
   stays (they may still want to add something) and this animates in to say they are finished and
   hand them Next. */
.sa-v3 .sa-interview-done {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
    /* Animates from nothing, so it slides the hint line down rather than appearing on top of it. */
    max-height: 0;
    opacity: 0;
    transform: translateY(-4px);
    overflow: hidden;
    transition: max-height .28s ease, opacity .28s ease, transform .28s ease;
}

.sa-v3 .sa-interview-done.is-in {
    max-height: 80px;
    opacity: 1;
    transform: translateY(0);
}

.sa-v3 .sa-done-note {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--sa-orange);
}

/* The button sizes to its label here rather than filling the width like a step's submit. */
.sa-v3 .sa-interview-done .sa-btn {
    flex: 0 0 auto;
    width: auto;
    min-width: 120px;
    /* Not the centered step button: this one is a FLEX ITEM, where auto side margins would eat
       the strip's free space and shove it away from the note beside it. */
    margin-left: 0;
    margin-right: 0;
    padding-top: 9px;
    padding-bottom: 9px;
}

@media (prefers-reduced-motion: reduce) {
    .sa-v3 .sa-interview-done { transition: none; }
}

/* ------------------------------------------------- pinned chat composer --- */
/* The interview is a conversation, so the answer box behaves like one: pinned to
   the bottom of the viewport while the transcript scrolls behind it. Only inside
   .sa-chat - the form steps keep their composer-less flow and their button sits
   in the document where it belongs.

   Matched to the shell's own width and centred, so on desktop it lines up with the
   conversation rather than spanning the whole window. */

.sa-v3 .sa-chat .sa-composer {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 0;
    width: 100%;
    max-width: 640px;
    box-sizing: border-box;
    padding: 12px 18px calc(12px + env(safe-area-inset-bottom, 0px));
    background: var(--sa-bg);
    border-top: 1px solid var(--sa-line);
    z-index: 20;
}

.sa-v3 .sa-chat .sa-composer textarea { min-height: 58px; }

/* Room for the pinned composer so the last turn is never trapped underneath it,
   and so the thinking dots stay visible while Craig is working. */
.sa-v3 .sa-chat .sa-msgs { padding-bottom: 8px; }
.sa-v3 .sa-chat { padding-bottom: 150px; }

/* Compact stepper for the sticky header: same markup and same states as the page stepper, just
   smaller and with no labels.

   MUST LIVE AFTER `.sa-v3 .sa-steps` - same specificity, so source order decides. Written above
   it, the base `margin: 2px 0 18px` won and that 18px of phantom space below the circles pushed
   them off-centre in the header. Same trap as `.sa-tel` losing to `.sa-field input[type="text"]`:
   put the modifiers last. */
.sa-v3 .sa-steps.sa-steps-mini {
    margin: 0;
    flex: 1 1 auto;
    max-width: 210px;
}

.sa-v3 .sa-steps-mini .sa-step {
    font-size: 0;          /* no labels here - the circles carry it */
    padding: 0 1px;
    line-height: 0;        /* font-size:0 still inherits a line box; drop it so height == circle */
}

.sa-v3 .sa-steps-mini .sa-step i {
    width: 22px;
    height: 22px;
    margin: 0 auto;
    border-width: 2px;
    font-size: 10px;
}

/* The rail is pinned to the circle's vertical middle, so it moves with the size. */
.sa-v3 .sa-steps-mini .sa-step::before {
    top: 11px;
    left: calc(-50% + 11px);
    right: calc(50% + 11px);
}

/* When the interview is finished the composer is gone, so the reserved space goes
   with it. */
.sa-v3 .sa-chat.is-complete { padding-bottom: 24px; }

@media (min-width: 768px) {
    .sa-v3 .sa-chat .sa-composer { padding-left: 24px; padding-right: 24px; }
}

/* --------------------------------------------------------- bubble tails --- */

/* A small tail on the top corner nearest the avatar, so each bubble visibly belongs to the
   photo or initials circle beside it rather than just sitting next to it.

   The corner it grows from also moves: the bubbles used to notch their BOTTOM inner corner
   (14px 14px 14px 4px), which pointed at nothing - the rows are `align-items: flex-start`, so
   the avatar is at the TOP. Notch and tail now agree on the same corner.

   Deliberately AFTER the base bubble rules: same specificity on the radius, so source order is
   what makes these win (see the modifier-ordering note in the canon).

   clip-path rather than the border-triangle trick: the vertices are readable, and the two sides
   are exact mirrors of each other. */

.sa-v3 .sa-msg-craig .sa-msg-bubble {
    border-radius: 4px 14px 14px 14px;
}

.sa-v3 .sa-msg-craig .sa-msg-bubble::before {
    content: '';
    position: absolute;
    top: 0;
    left: -7px;
    width: 8px;
    height: 14px;
    background: #f6f7f9;
    /* top-right -> bottom-right -> top-left: flat top, vertical right edge against the
       bubble, hypotenuse falling away to a point aimed up at the avatar. */
    clip-path: polygon(100% 0, 100% 100%, 0 0);
}

.sa-v3 .sa-msg-user .sa-msg-bubble-user {
    position: relative;                  /* the tail is absolutely positioned against it */
    border-radius: 14px 4px 14px 14px;
}

.sa-v3 .sa-msg-user .sa-msg-bubble-user::before {
    content: '';
    position: absolute;
    top: 0;
    right: -7px;
    width: 8px;
    height: 14px;
    background: var(--sa-orange-soft);
    /* Mirrored: the point aims up at the initials circle on the right. */
    clip-path: polygon(0 0, 0 100%, 100% 0);
}

/* Soundwave glyph inside the replay circle. `fill: currentColor` means the button's own colour
   knocks it out of the fill, so the hover state (circle -> orange) needs no rule of its own. */
.sa-v3 .sa-replay-icon {
    width: 13px;
    height: 13px;
    display: block;
    fill: currentColor;
}

/* The sticky header's bottom edge IS the progress bar: the 1px line is the track, and this fills
   across it as the applicant advances. Width comes from the JS (data-current-step), so it starts
   at 0 and is set on load and on every step swap by the same code path. */
.sa-v3 .sa-stickyhead-progress {
    position: absolute;
    left: 0;
    bottom: -1px;              /* sits ON the border-bottom, not above it */
    height: 2px;
    width: 0;
    background: var(--sa-orange);
    border-radius: 0 2px 2px 0;
    transition: width .3s ease;
}

/* "3 of 6" beside the Enter hint - the page's own statement of where the interview stands, so it
   never depends on how Craig happened to word his last message. */
.sa-v3 .sa-composer-note {
    display: flex;
    justify-content: space-between;
    gap: 12px;
}

.sa-v3 .sa-progress-count {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: var(--sa-muted);
}

.sa-v3 .sa-progress-count.is-done {
    color: var(--sa-orange);
    font-weight: 600;
}

/* Post-submit confirmation - replaces the review + submit button once the application is in. */
.sa-v3 .sa-submitted { padding: 4px 0 8px; }

.sa-v3 .sa-submitted-head {
    font-family: Montserrat, Roboto, sans-serif;
    font-size: 22px;
    line-height: 1.25;
    color: var(--sa-ink);
    margin: 12px 0 8px;
}

/* ------------------------------------------------- post-submit ending --- */

/* The confirmation is the only screen with nothing to do on it, so it gets room to breathe:
   larger type than a form step, and real margin off the edges rather than the tight wizard
   padding that exists to keep fields above the fold. */
.sa-v3 .sa-craig[data-stay-large="true"] {
    padding: 8px 10px 0;
}

.sa-v3 .sa-craig[data-stay-large="true"] .sa-craig-fulltext,
.sa-v3 .sa-craig[data-stay-large="true"] .sa-tct-caption {
    font-size: 17px;
    line-height: 1.8;
    text-align: center;
}

/* A THREE-line rolling window here, matching Craig's Voice Demo player - the ending script runs
   longer than a step intro, and two lines makes it feel clipped. Height is 3 x line-height so a
   line slides in as one slides out. */
.sa-v3 .sa-craig[data-stay-large="true"] .sa-tct-viewport {
    /* em here resolves against the VIEWPORT's font-size, not the caption's - without matching them
       the window came out 70px (3 x 13px inherited) instead of three 17px lines. */
    font-size: 17px;
    line-height: 1.8;
    height: 5.4em;
    /* The base rule fades from 58%, which is sized for the TWO-line window (line 1 = 0-50%). Three
       lines put line 2 at 33-67% and line 3 at 67-100%, so that fade ate most of line 2 and all of
       line 3 - a 3-line window showing 1.7 lines. Hold opaque through line 2 and fade only line 3,
       which is the upcoming-preview line. */
    -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 72%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 0, #000 72%, transparent 100%);
}

.sa-v3 .sa-craig[data-stay-large="true"] .sa-craig-body {
    margin-top: 18px;
}

/* What happens next: three beats, top to bottom, in the order they happen. */
.sa-v3 .sa-next {
    list-style: none;
    margin: 26px 10px 8px;
    padding: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

.sa-v3 .sa-next-card {
    display: flex;
    flex-direction: column;
    gap: 3px;
    border: 1px solid var(--sa-line);
    border-radius: 12px;
    padding: 14px 16px;
    background: #fff;
    text-align: center;
}

/* Done, now, later - the applicant should be able to tell at a glance which one is theirs. */
.sa-v3 .sa-next-card.is-done {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: var(--sa-muted);
    background: #f6f7f9;
}

.sa-v3 .sa-next-card.is-now {
    border-color: var(--sa-orange);
    border-width: 2px;
    box-shadow: 0 2px 10px rgba(249, 86, 2, .10);
}

.sa-v3 .sa-next-card.is-later {
    color: var(--sa-muted);
    border-style: dashed;
}

.sa-v3 .sa-next-mark { color: var(--sa-orange); font-weight: 700; }

.sa-v3 .sa-next-title {
    font-family: Montserrat, Roboto, sans-serif;
    font-size: 15px;
    font-weight: 600;
    color: inherit;
}

.sa-v3 .sa-next-card.is-now .sa-next-title { color: var(--sa-ink); }

.sa-v3 .sa-next-meta { font-size: 13px; color: var(--sa-muted); }

.sa-v3 .sa-next-due {
    font-size: 13px;
    font-weight: 600;
    color: var(--sa-orange);
}

.sa-v3 .sa-next-arrow {
    text-align: center;
    color: #c7ccd4;
    font-size: 18px;
    line-height: 1;
    padding: 6px 0;
}

/* Waiting on someone else - visible progress, but not the applicant's to chase. */
.sa-v3 .sa-next-card.is-waiting {
    border-style: solid;
    border-color: var(--sa-line);
    background: #fff;
}

.sa-v3 .sa-next-card.is-waiting .sa-next-title { color: var(--sa-ink); }
