/* =========================================================================
   Landing page, the shared machinery: the catalogue explorer, the page-level
   button and reveal behaviour, the real report with its wall, the phone film
   and the rotating campus photographs. The page's composition is layered on
   top in landing-bold.css.
   ========================================================================= */

/* ---- University explorer (interactive) --------------------------------- */
.uni-grid {
  display: flex; flex-direction: column; gap: 0.5rem;
  max-height: 460px; overflow-y: auto; padding-right: 0.35rem;
}
.uni-grid::-webkit-scrollbar { width: 8px; }
.uni-grid::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 8px; }

.uni-card {
  display: flex; align-items: center; gap: 0.75rem; text-align: left;
  width: 100%; padding: 0.8rem 0.95rem;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.uni-card:hover { border-color: var(--line-strong); transform: translateX(3px); box-shadow: var(--shadow-sm); }
.uni-card.active { border-color: var(--primary); background: var(--primary-soft); }
/* --muted measures 4.20:1 on the selected card's tint, just under AA. The card
   is the one the reader has chosen, so ink is the right answer anyway. */
.uni-card.active .uni-card-meta { color: var(--ink); }

.uni-card-logo { width: 38px; height: 38px; object-fit: contain; flex: 0 0 auto; }
/* 71 of the 146 PTN have no crest file; the card keeps its shape either way. */
.uni-card-logo--ltr { border-radius: 10px; font-size: 1rem; object-fit: initial; }
.uni-card-body { display: flex; flex-direction: column; min-width: 0; }
.uni-card-name {
  font-weight: 700; font-size: 0.9rem; color: var(--ink); line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.uni-card-meta { font-size: 0.78rem; color: var(--muted); }

.program-list { display: flex; flex-direction: column; gap: 0.4rem; max-height: 420px; overflow-y: auto; }
.program-list::-webkit-scrollbar { width: 8px; }
.program-list::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 8px; }

/* The prodi count on the explorer panel. Bootstrap's subtle badge measured
   under 4.5:1 for a 12px label; this is the accent's deep step on its tint. */
.lp-count {
  flex: 0 0 auto; padding: .2rem .6rem; border-radius: var(--radius-sm);
  background: var(--primary-soft); color: var(--accent-ink-2);
  font-size: .78rem; font-weight: 700; white-space: nowrap;
}

/* =========================================================================
   Landing
   The app's kit (cobalt, Plus Jakarta Sans, one accent), tuned for the one
   page that has to be inviting rather than efficient: buttons that press like
   buttons, and sections that arrive as you reach them.
   ========================================================================= */
.lp { overflow: hidden; }

/* Buttons on this page used to keep a solid bottom edge and sink when
   pressed. Under a bar made of glass that read as a second material, so they
   are the same flat pill as on every other page. */

/* The quiet button: a hairline you can see, ink text, the same pill. */
.btn-ghost { background: var(--surface); border: 1px solid var(--line-strong); color: var(--ink); }
.btn-ghost:hover { background: var(--surface); border-color: var(--primary); color: var(--accent-ink); }
.btn-ghost:active { background: var(--primary-soft); border-color: var(--primary); color: var(--accent-ink-2); }

/* Sections arrive as you reach them. The hidden state exists only while the
   script is running (it adds .lp-anim to <html>), so with JavaScript off, or
   with reduced motion on, the page is simply already in its finished state. */
.lp-anim .lp-in { opacity: 0; transform: translateY(16px); }
.lp-anim .lp-in.is-in {
  opacity: 1; transform: none;
  transition: opacity .5s ease, transform .55s cubic-bezier(.16, 1, .3, 1);
}
/* Put in place by the safety net rather than reached by scrolling: no
   transition, for the section or for anything that animates inside it. */
.lp-anim .lp-in.is-set,
.lp-anim .lp-in.is-set * { transition: none !important; }
@media (prefers-reduced-motion: reduce) {
  .lp-anim .lp-in, .lp-anim .lp-in.is-in { opacity: 1; transform: none; transition: none; }
}

/* ---- The real report, on the landing page -------------------------------
   Rendered by views/partials/analysis.php, so it is the report itself and not
   a drawing of one. What changes here is only how much of it is given away:
   five chapters read normally, the sixth sits behind a blur, and the blocks
   that exist for the owner of a report — the data they typed in, the save and
   share row — never belong on a marketing page. */
.lp-report-frame { position: relative; max-width: 420px; margin: 0 auto; }
.lp-report-frame .usedata,
.lp-report-frame .report-acts,
.lp-report-frame .rs-index { display: none; }

/* The wall. The chapter behind it is real content, which is the point: a blur
   over something says there is more, a blank panel says there is nothing. */
.lp-report-frame .rs.is-story .rs-stage > section.rs-ch:last-of-type .rs-face {
  filter: blur(7px); opacity: .5; pointer-events: none; user-select: none;
  transition: filter .3s ease;
}
.ch-lock {
  /* Over the card and its bar, which has nothing left to go on to. */
  position: absolute; inset: 0; z-index: 7;
  display: grid; place-items: center; padding: 1.5rem;
  opacity: 0; visibility: hidden; transition: opacity .3s ease, visibility .3s;
}
/* Only while that last chapter is the one on screen. The class is put there
   by landing.js rather than by :has(): the story adds .on with script after
   load, and Chromium does not always re-evaluate a :has() that depends on it,
   so the wall matched in devtools and stayed invisible on the page. */
.lp-report-frame.is-walled .ch-lock { opacity: 1; visibility: visible; }
.ch-lock-in {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 18px 40px -14px rgba(var(--shadow-rgb),.28);
  padding: 1.4rem 1.3rem; text-align: center; max-width: 20rem;
}
.ch-lock-t { font-weight: 800; font-size: 1.08rem; letter-spacing: -.02em; margin-bottom: .45rem; }
.ch-lock-p { font-size: .87rem; color: var(--muted); line-height: 1.55; margin: 0 0 1rem; }
.ch-lock-p em { font-style: normal; color: var(--ink); font-weight: 700; }
/* The site's primary button, drawn here because the wall sits outside the
   Bootstrap .btn cascade: a flat pill that sinks on press. */
.ch-lock-btn {
  display: inline-block; background: var(--primary); color: #fff; text-decoration: none;
  font-weight: 700; font-size: .9rem; padding: .6rem 1.2rem; border-radius: 999px;
  transition: background-color .15s ease, transform .12s cubic-bezier(.16, 1, .3, 1);
}
.ch-lock-btn:hover { background: var(--primary-dark); color: #fff; }
.ch-lock-btn:active { transform: translateY(1px) scale(.98); }

/* Without the story running there is no wall, so nothing should be blurred
   either — a reader with no JavaScript gets the six chapters as a plain
   scroll, all of them legible. */
.lp-report-frame .rs:not(.is-story) .rs-stage > section.rs-ch:last-of-type .rs-face { filter: none; opacity: 1; }

/* ---- Hero reel ----------------------------------------------------------
   The product running, on a loop: form, wait, report, follow-up question.
   Everything inside a scene is the app's own markup with the app's own
   classes, so app.css and report.css do the styling; this file only frames it,
   scales it to fit, and sequences the four beats.

   The frame reads as a screen rather than a card pasted onto the page: a soft
   shadow instead of the chunky offset the buttons use, and a caption strip
   that says outright what it is. A visitor should never have to wonder whether
   these are somebody's real numbers.

   Scale is a viewport trick, not a redesign — the form is laid out for a full
   column, so it renders at its natural width and is scaled down, the way a
   device mockup shows a real page. */

/* ---- The device --------------------------------------------------------
   The reel used to live in a rounded box with a caption, and it read as
   decoration: nobody could tell whether it was a screenshot, a video, or
   something they were meant to click. A phone answers that before a single
   word is read. Everything here is frame, not content, and none of it is an
   image: bezel, status bar, address bar, home indicator, all drawn in CSS so
   they cost nothing and stay sharp at any density.

   Width is the one number that matters, and the report is what sets it. The
   story card is a fixed 9:16, so the screen has to be tall enough to hold one
   at full width plus its progress bars: 320px of phone, 11px of bezel each
   side, a 298px screen, a 298x530 card, 41px for bars and padding, and a 571px
   view. That is where `aspect-ratio: 100/192` below comes from. --reel-scale is
   then set so the 560px form lands on the same 298px. Change one, change all
   three. */

.lp-phone {
  position: relative; width: 100%; max-width: 320px; margin: 0 auto;
  padding: 11px; border-radius: 46px;
  background: linear-gradient(160deg, #3b465c 0%, #0f172a 38%, #1e293b 72%, #0b1120 100%);
  box-shadow:
    0 44px 70px -34px rgba(var(--shadow-rgb), .55),
    0 10px 24px -12px rgba(var(--shadow-rgb), .3),
    inset 0 0 0 1px rgba(255, 255, 255, .16);
}

/* Side keys. Purely cosmetic, and the reason the shape reads as a handset
   rather than as a rounded rectangle. */
.lp-phone-key {
  position: absolute; width: 3px; border-radius: 3px;
  background: linear-gradient(180deg, #475569, #1e293b);
}
.lp-phone-key--vol { left: -3px; top: 108px; height: 62px; }
.lp-phone-key--pwr { right: -3px; top: 132px; height: 86px; }

.lp-phone-screen {
  position: relative; overflow: hidden; overflow: clip; border-radius: 36px;
  background: var(--bg, var(--surface-2));
  display: flex; flex-direction: column;
}

/* --- status bar --- */
.lp-phone-status {
  position: relative; z-index: 4;
  display: flex; align-items: center; justify-content: space-between;
  padding: .45rem .95rem .3rem;
  background: var(--surface);
  font-size: .68rem; font-weight: 700; color: var(--ink);
}
.lp-phone-time { letter-spacing: .01em; }
.lp-phone-island {
  width: 62px; height: 17px; border-radius: 999px; background: #0b1120;
  flex: 0 0 auto; margin-top: -2px;
}
.lp-phone-sig { display: inline-flex; align-items: center; gap: .25rem; font-size: .62rem; color: var(--ink); }

/* --- home indicator --- */
.lp-phone-home {
  display: block; width: 108px; height: 4px; border-radius: 999px;
  background: var(--line-strong); margin: .4rem auto .5rem;
}

/* ---- The screen's own contents ---------------------------------------- */
.lp-reel {
  position: relative; width: 100%; background: var(--bg, var(--surface-2));
  /* The set is laid out at 560px and filmed. This is only the resting frame
     for a visitor without JavaScript: 298/560 on the desktop phone. landing.js
     measures the real screen and drives the camera from there. */
  --reel-scale: .53;
}

/* An address bar. It says what the frame is a picture of, which no caption
   ever managed to, and it puts the domain in front of the visitor for free. */
.reel-chrome {
  display: flex; align-items: center; gap: .4rem;
  margin: 0 .55rem .35rem; padding: .3rem .6rem;
  background: var(--surface-2); border-radius: 999px;
  font-size: .7rem; font-weight: 700; color: var(--muted); letter-spacing: .01em;
}
.reel-url { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.reel-reload { font-size: .72rem; flex: 0 0 auto; opacity: .55; }

/* The screen. inline-size containment so the motion-graphics scenes can be
   sized in cqw and come out identical on the 298px and the 278px phone.

   `clip`, not `hidden`, wherever something is cropped and never scrolled: an
   overflow:hidden box is still a scroll container, and it swallowed the wheel
   so that resting the pointer on the phone froze the page. */
.reel-view {
  position: relative; aspect-ratio: 100 / 192;
  overflow: hidden; overflow: clip;
  container-type: inline-size;
}

/* A recording, not an interface. `inert` in the markup covers focus and
   assistive tech; this covers the pointer and the I-beam. */
.lp-reel, .lp-reel * {
  pointer-events: none !important;
  user-select: none; -webkit-user-select: none;
  cursor: default;
}

/* ---- Scenes, bottom to top ---------------------------------------------
   The form is always underneath. Everything after the press stacks over it,
   so the loop can hand back to the form by simply fading the answer out. */
.rv-scene { position: absolute; inset: 0; overflow: hidden; overflow: clip; }
.rv-form { z-index: 1; background: var(--bg, var(--surface-2)); }
.rv-res  { z-index: 3; background: var(--surface); opacity: 0; }
.rv-wipe {
  position: absolute; left: -20px; top: -20px; z-index: 6;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--primary, #2563eb); opacity: 0;
}
.rv-load { z-index: 7; color: #fff; opacity: 0; }

/* ---- 1 · the set, and the camera ---------------------------------------
   The camera is one transform on .rv-cam: translate the point being looked
   at to the middle of the screen, then scale. Nothing inside the set moves
   to make a shot, so the form never reflows mid-film. */
.rv-cam {
  position: absolute; left: 0; top: 0; width: 560px;
  transform-origin: 0 0;
  transform: scale(var(--reel-scale));
}
.rv-set { position: relative; width: 560px; padding: 26px 24px 30px; }

/* A backdrop blur under a moving camera is recomputed every frame, and there
   is nothing behind these cards to blur. */
.rv-set .glass-card { -webkit-backdrop-filter: none; backdrop-filter: none; }

/* The form in the analysis form's own look (views/apply/index.php v3):
   numbered steps on matte cards. Pinned here rather than left to the form's
   rules, which change with the WINDOW's width and would rearrange a set that
   is always 560px wide (the grade table learned that the hard way). The tick
   that marks the form done grows over each step's number, the way the real
   form ticks a step off. */
.rv-set .rv-step { padding: 1.3rem 1.4rem 1.45rem; }
.rv-set .rv-step + .rv-step { margin-top: 1rem; }
.rv-set .af-step-head { margin-bottom: 1rem; }
.rv-step-h { grid-column: 2; grid-row: 1; margin: 0; font-size: 1.05rem; font-weight: 800; letter-spacing: -0.01em; color: var(--ink); }
.rv-set .form-label { font-size: .8rem; font-weight: 600; color: var(--ink-2); margin-bottom: .3rem; }
.rv-set .af-picks { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.rv-set .af-pick + .af-pick { padding: 0 0 0 1rem; border-top: 0; border-left: 1px solid var(--line); }
.rv-set .af-pick-f + .af-pick-f { margin-top: .7rem; }
.rv-set .rapor-shot { border: 1px solid var(--line); border-radius: calc(var(--radius) - 6px); background: var(--surface-2); padding: .75rem .85rem; }
.rv-shot-ico { flex: 0 0 auto; font-size: 1.25rem; color: var(--muted); }
/* The film draws no blur (it would be recomputed under a moving camera), so a
   see-through dropdown showed the button under it; in the film it is solid. */
.rv-set .ss-panel { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
.rv-num { position: relative; }
.rv-tick {
  position: absolute; inset: -1px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--ok-bg); color: var(--ok-ink); font-size: .9rem;
}

/* ---- Motion that a slow phone can keep up with -------------------------
   Everything the film animates is a transform or an opacity, the two things a
   browser hands to the graphics chip and plays without running layout or
   repainting. Where the film needs something else to change, it is rebuilt
   out of those two:

   - a reveal (typed text, the scanned table, the dropdown opening, the reply
     arriving) is a clipping box sliding one way while its contents slide back
     by the same amount, so the contents stay put and the edge moves;
   - a colour change (the focus ring, a pressed tint) is a second element in
     the new colour fading in over the first;
   - the pressed button is its face moving down onto a slab that stays;
   - a drawn arc is the finished arc turning into view.

   It used to animate width, clip-path, box-shadow, border and background
   colour and stroke-dashoffset. Any one of those keeps the page's main thread
   working on every frame for the whole loop; a phone six times slower than a
   laptop spent 93% of its time on them and had nothing left for a touch. */

/* Typed fields. The text is all there; landing.js reveals it one measured
   glyph at a time and moves the caret to the edge. Once the film is built
   (.is-filmed) the text takes no room, so the caret and the placeholder sit
   at the start of the field and the text is drawn over them. */
.rv-field { display: flex; align-items: center; white-space: nowrap; overflow: hidden; overflow: clip; }
.rv-type { display: inline-block; flex: 0 0 auto; white-space: nowrap; }
.rv-type-clip { display: block; width: max-content; overflow: hidden; overflow: clip; }
.rv-type-in { display: block; width: max-content; }
.is-filmed .rv-type { width: 0; }

/* A focused field: the ring is drawn once, over the field, and faded. */
.rv-focus {
  position: absolute; z-index: 5; box-sizing: border-box;
  border: 1px solid rgb(37, 99, 235); opacity: 0;
}

/* A pressed or picked control: its tint is a layer of its own, under the label. */
.rv-tinted { position: relative; z-index: 0; }
.rv-tint { position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: rgb(234, 241, 255); opacity: 0; }
/* In the dark theme the pale tint put light text on a light ground; the real
   form uses this token for a picked option, so the film does too. */
:root[data-theme="dark"] .rv-tint { background: var(--primary-soft); }
.rv-caret {
  display: inline-block; flex: 0 0 auto;
  width: 2px; height: 1.2em; margin-left: 1px; border-radius: 1px;
  background: var(--primary, #2563eb); opacity: 0;
}
.rv-ph { flex: 0 0 auto; margin-left: 2px; color: var(--muted); opacity: 0; }

/* The photo row, and the line under it that says what the scan is doing. */
.rv-shot { margin-bottom: .9rem; }
.rv-say { position: relative; display: block; height: 1.45em; }
.rv-say > span {
  position: absolute; left: 0; top: 0;
  display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap;
}
.rv-say > span + span { opacity: 0; }
.rv-say .ph-check-circle { color: var(--c-green, #16a34a); }
/* Turned by the film's own timeline while its line is on screen. As a CSS
   animation it spun for the whole loop, visible or not, and through every
   scroll further down the page. */
.rv-spin {
  width: .85em; height: .85em; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--primary, #2563eb);
}

.rv-grid { position: relative; }
.rv-grid-in { display: flex; flex-direction: column; gap: .5rem; }
.rv-grid-full { position: absolute; inset: 0; overflow: hidden; overflow: clip; }
.rv-set .subject-row .form-control { text-align: center; }
.rv-set .subject-row .subj-name { text-align: left; white-space: nowrap; overflow: hidden; overflow: clip; }
/* The grade table is the desktop form's at every width.
   apply.css turns each subject into a small card below 769px (name on its own
   line, the header hidden, the delete button pinned in a corner). Right for a
   phone filling in the real form, wrong here: the film's set is 560px wide
   whatever the phone is, so on every phone the table came out as a stack of
   boxes under a header that no longer lined up. Pinned to one grid, every cell
   the same height, and the delete column gone because nobody can press it. */
.rv-set .subject-head,
.rv-set .subject-row {
  display: grid; grid-template-columns: 2.6fr repeat(5, 1fr);
  gap: .45rem; align-items: center;
  position: static; border: 0; border-radius: 0; padding: 0;
}
.rv-set .subject-head { margin-bottom: .45rem; }
.rv-set .subject-head .col-head { font-size: .72rem; font-weight: 700; color: var(--muted); text-align: center; }
.rv-set .subject-head .col-head:first-child { text-align: left; padding-left: .55rem; }
.rv-set .subject-row .form-control {
  display: flex; align-items: center; justify-content: center;
  height: 2.6rem; margin: 0; padding: 0 .2rem; font-size: .9rem;
}
.rv-set .subject-row .subj-name {
  display: block; grid-column: auto; margin: 0; padding: 0 .55rem;
  line-height: calc(2.6rem - 2px); font-size: .85rem; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rv-grid-in { gap: .45rem; }
.rv-beam {
  position: absolute; top: -10px; bottom: -10px; left: 0;
  width: 64px; margin-left: -64px;
  background: linear-gradient(90deg, rgba(37, 99, 235, 0), rgba(37, 99, 235, .2));
  border-right: 3px solid var(--primary, #2563eb);
  box-shadow: 6px 0 16px -6px rgba(37, 99, 235, .55);
  opacity: 0;
}

/* Pickers: the empty prompt and the chosen value share one trigger. */
.rv-trig { position: relative; }
.rv-set [data-empty] { opacity: 0; }
.rv-filled { position: absolute; left: .9rem; right: 2.2rem; top: 50%; height: 1.5em; margin-top: -.75em; }
/* The dropdown opens by its clip sliding down. The clip has the panel's
   corners, so the opening edge is as round as the panel's own. */
.rv-panel-clip {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; right: 0;
  border-radius: var(--radius); overflow: hidden; overflow: clip; opacity: 0;
}
.rv-panel { position: relative; top: 0; display: block; box-shadow: none; }
.rv-set .ss-options { max-height: none; overflow: visible; }
.rv-search {
  display: flex; align-items: center; white-space: nowrap; overflow: hidden; overflow: clip;
  border: 1.5px solid var(--primary, #2563eb); border-radius: var(--radius-sm);
  padding: .5rem .75rem; font-size: .95rem; background: var(--surface);
}
.rv-set .ss-option { font-weight: 600; }
/* The button is a face on a slab. Pressed, the face goes down three of the
   slab's four pixels and the slab does not move. */
.rv-go-wrap { position: relative; display: inline-block; border-radius: 999px; }
.rv-go-slab {
  position: absolute; inset: 0; border-radius: inherit;
  background: var(--primary-deep, rgb(30, 64, 175)); transform: translateY(4px);
  box-shadow: 0 6px 22px rgba(37, 99, 235, .22);
}
/* The slab is that element now, so the page's own button edge (.lp
   .btn-primary) is switched off here or the pressed face would carry a
   second one down with it. */
.rv-go-wrap .rv-go { position: relative; cursor: default; box-shadow: none; }

/* A fingertip, the way screen recordings show touches. */
.rv-touch, .rv-ripple {
  position: absolute; left: -19px; top: -19px; z-index: 40;
  width: 38px; height: 38px; border-radius: 50%; opacity: 0;
}
.rv-touch {
  background: rgba(15, 23, 42, .2); border: 2.5px solid #fff;
  box-shadow: 0 4px 12px rgba(var(--shadow-rgb), .3), 0 0 0 1px rgba(var(--shadow-rgb), .1);
}
.rv-ripple { z-index: 39; border: 3px solid var(--primary, #2563eb); }

/* ---- 2 · the wait, on the button's blue --------------------------------
   White on #2563eb is 5,17:1. The glow sits behind the ring, well clear of
   the line of text, so the text keeps that ground. */
.rv-load::before {
  content: ""; position: absolute; left: 50%; top: 40%;
  width: 110cqw; height: 110cqw; margin: -55cqw 0 0 -55cqw; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(96, 165, 250, .45), rgba(96, 165, 250, 0));
}
.rv-ring { position: absolute; left: 50%; top: 40%; width: 44cqw; height: 44cqw; margin: -22cqw 0 0 -22cqw; overflow: visible; }
.rv-ring circle { fill: none; stroke: rgba(255, 255, 255, .2); stroke-width: 6; }
.rv-ring .rv-ring-arc { stroke: #fff; stroke-linecap: round; stroke-dasharray: 26 74; }
.rv-load-mark { position: absolute; left: 50%; top: 40%; width: 17cqw; height: 17cqw; margin: -8.5cqw 0 0 -8.5cqw; }
.rv-bits i {
  position: absolute; left: 50%; top: 40%;
  font-style: normal; font-size: 4.3cqw; font-weight: 800; white-space: nowrap; opacity: 0;
}
.rv-lines {
  position: absolute; left: 0; right: 0; top: 62%; height: 1.4em; margin: 0;
  overflow: hidden; overflow: clip;
  font-size: 5.8cqw; font-weight: 800; letter-spacing: -.015em; text-align: center;
}
.rv-lines span { position: absolute; left: 0; right: 0; top: 0; opacity: 0; }
.rv-progress {
  position: absolute; left: 32%; right: 32%; top: 71%; height: 3px; border-radius: 3px;
  background: rgba(255, 255, 255, .25); overflow: hidden; overflow: clip;
}
.rv-progress i { display: block; height: 100%; background: var(--surface); transform-origin: 0 50%; transform: scaleX(0); }

/* ---- 3 · the answer ----------------------------------------------------
   Motion graphics, sized in cqw against the screen. Every figure is printed
   by landing.php from the sample analysis. */
.rv-beat { position: absolute; inset: 0; opacity: 0; }
.rv-pick { position: absolute; left: 8cqw; right: 8cqw; top: 6%; display: flex; align-items: center; gap: 3.2cqw; opacity: 0; }
.rv-crest { flex: 0 0 auto; width: 11cqw; height: 11cqw; object-fit: contain; }
.rv-ltr {
  display: inline-grid; place-items: center; border-radius: 50%;
  background: var(--primary-soft, var(--primary-soft)); color: var(--primary, #2563eb); font-weight: 800;
}
.rv-pick > span, .rv-row-h > span { min-width: 0; }
.rv-pick b, .rv-row-h b {
  display: block; font-size: 5.3cqw; font-weight: 800; letter-spacing: -.015em; line-height: 1.15;
  color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rv-pick i, .rv-row-h i {
  display: block; margin-top: .25em; font-style: normal; font-size: 3.6cqw; font-weight: 600;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The crowd: the report's own "1 dari N", drawn as N figures. */
.rv-onein {
  position: absolute; left: 0; right: 0; top: 17%; margin: 0;
  display: flex; justify-content: center; align-items: flex-end; gap: 2.6cqw; line-height: 1;
}
.rv-one, .rv-n { font-size: 17cqw; font-weight: 800; letter-spacing: -.04em; line-height: 1.12; }
.rv-one { color: var(--primary, #2563eb); }
.rv-n { color: var(--ink); }
.rv-of { font-size: 5.2cqw; font-weight: 700; color: var(--muted); padding-bottom: 3.4cqw; }
.rv-sub { position: absolute; left: 0; right: 0; top: 34%; margin: 0; text-align: center; font-size: 4.1cqw; font-weight: 600; color: var(--muted); }
.rv-mob {
  position: absolute; left: 11cqw; right: 11cqw; top: 43%;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 2.4cqw 2cqw;
}
.rv-pgn { display: block; width: 10cqw; height: 12cqw; }
.rv-pgn svg { display: block; width: 100%; height: 100%; fill: var(--line-strong); }
.rv-pgn.is-in svg { fill: var(--primary, #2563eb); }
.rv-readout { position: absolute; left: 0; right: 0; top: 82%; margin: 0; text-align: center; font-size: 3.9cqw; font-weight: 600; color: var(--muted); }
.rv-readout b { font-weight: 800; color: var(--ink); }

/* The chance: an outer arc for this student, an inner one for the average
   applicant, both on the same 0-100 circle so neither is flattered. */
.rv-halo {
  position: absolute; left: 50%; top: 47%; width: 92cqw; height: 92cqw; margin: -46cqw 0 0 -46cqw;
  border-radius: 50%; background: radial-gradient(closest-side, var(--primary-soft), rgba(234, 241, 255, 0));
}
.rv-gauge { position: absolute; left: 50%; top: 47%; width: 66cqw; height: 66cqw; margin: -33cqw 0 0 -33cqw; transform: rotate(-90deg); overflow: visible; }
.rv-gauge circle, .rv-arc circle { fill: none; }
/* An arc is the gauge's box again, cut down to the half of the dial it ends
   up in, with the finished arc inside turning into that half from behind the
   other. The cut is the centre line exactly. "you" has round ends, and the
   round start is a dot on twelve (.rv-arc-cap): reaching the cut a cap's
   width past the centre instead left the tail square while it turned in. The
   second half overlaps the first by a hair so no seam shows at six. */
.rv-arc { position: absolute; left: 50%; top: 47%; width: 66cqw; height: 66cqw; margin: -33cqw 0 0 -33cqw; }
.rv-arc-half { position: absolute; top: 0; bottom: 0; overflow: hidden; overflow: clip; }
.rv-arc-half--a { left: 33cqw; right: 0; }
.rv-arc-half--b { left: 0; right: 32.8cqw; }
.rv-arc-turn { position: absolute; top: 0; left: 0; width: 66cqw; height: 66cqw; }
.rv-arc-half--a .rv-arc-turn { left: -33cqw; }
.rv-arc-turn svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); overflow: visible; }
.rv-arc-cap { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.rv-arc .rv-arc-cap circle { fill: var(--primary, #2563eb); }
.rv-g-track  { stroke: var(--line); stroke-width: 8; }
.rv-g-track2 { stroke: var(--surface-2); stroke-width: 4; }
.rv-g-base   { stroke: var(--field-line); stroke-width: 4; }
.rv-g-you    { stroke: var(--primary, #2563eb); stroke-width: 8; stroke-linecap: round; }
.rv-g-in {
  position: absolute; left: 50%; top: 47%; width: 66cqw; height: 66cqw; margin: -33cqw 0 0 -33cqw;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; white-space: nowrap;
}
.rv-g-k { font-size: 3.4cqw; font-weight: 700; color: var(--muted); }
.rv-big { font-size: 12.5cqw; font-weight: 800; letter-spacing: -.04em; line-height: 1.12; color: var(--ink); }
.rv-g-rng { font-size: 3.4cqw; font-weight: 600; color: var(--muted); }
.rv-chip {
  position: absolute; left: 0; right: 0; top: 75%; width: max-content; margin: 0 auto;
  padding: .45em .95em; border-radius: 999px;
  background: var(--primary-soft, var(--primary-soft)); color: var(--accent-ink-2);
  font-size: 3.9cqw; font-weight: 800; white-space: nowrap;
}
.rv-legend {
  position: absolute; left: 0; right: 0; top: 84%; margin: 0;
  display: flex; justify-content: center; gap: 4.5cqw;
  font-size: 3.3cqw; font-weight: 600; color: var(--muted);
}
.rv-legend i { display: inline-block; width: .7em; height: .7em; margin-right: .4em; border-radius: 50%; }
.k-you  { background: var(--primary, #2563eb); }
.k-base { background: var(--field-line); }

/* Both picks, side by side in time. */
.rv-both-t { position: absolute; left: 8cqw; right: 8cqw; top: 7.5%; margin: 0; font-size: 7.2cqw; font-weight: 800; letter-spacing: -.025em; color: var(--ink); }
.rv-row { position: absolute; left: 8cqw; right: 8cqw; }
.rv-row-h { display: flex; align-items: center; gap: 3cqw; }
.rv-row-h .rv-crest { width: 9.5cqw; height: 9.5cqw; }
.rv-row-n { display: block; margin-top: 2.4cqw; font-size: 12cqw; font-weight: 800; letter-spacing: -.04em; line-height: 1.12; color: var(--primary, #2563eb); }
.rv-bar { position: relative; display: block; height: 2.6cqw; margin-top: 2cqw; border-radius: 99px; background: var(--line); overflow: hidden; overflow: clip; }
.rv-bar-rng { position: absolute; top: 0; bottom: 0; background: #bfdbfe; }
.rv-bar-fill { position: absolute; top: 0; bottom: 0; left: 0; border-radius: 99px; background: var(--primary, #2563eb); transform-origin: 0 50%; }
.rv-row-rng { display: block; margin-top: 1.6cqw; font-size: 3.3cqw; font-weight: 600; color: var(--muted); }
.rv-both-foot { position: absolute; left: 8cqw; right: 8cqw; top: 80%; margin: 0; font-size: 3.9cqw; font-weight: 700; color: var(--ink); }

/* The end card, on the same white the form comes back on. */
.rv-end { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
.rv-end-mark { width: 21cqw; height: 21cqw; }
.rv-end-word { margin: 4cqw 0 0; font-size: 8cqw; font-weight: 800; letter-spacing: -.03em; color: var(--ink); }
.rv-end-word b { font-weight: 800; color: var(--primary, #2563eb); }
.rv-end-say { margin: 2cqw 0 0; font-size: 4.6cqw; font-weight: 700; color: var(--muted); }

/* The follow-up chat. Headings are the real chat's; the bubbles are drawn
   for the film rather than borrowed, so app.css's thread rules (a 420px cap,
   a scrollbar) cannot leak into a screen nobody can scroll. */
.rv-chat-stage { position: absolute; inset: 0; }
.rv-chat-eye { position: absolute; left: 8cqw; right: 8cqw; top: 7%; margin: 0; font-size: 3.5cqw; font-weight: 800; color: var(--primary, #2563eb); }
.rv-chat-head { position: absolute; left: 8cqw; right: 8cqw; top: 11.5%; margin: 0; font-size: 6.4cqw; font-weight: 800; letter-spacing: -.02em; line-height: 1.2; color: var(--ink); }
.rv-thread { position: absolute; left: 6cqw; right: 6cqw; top: 27%; display: flex; flex-direction: column; gap: 4cqw; }
.rv-msg { max-width: 82%; padding: 2.8cqw 3.6cqw; border-radius: 4.5cqw; font-size: 3.9cqw; font-weight: 600; line-height: 1.45; opacity: 0; }
.rv-msg--me { align-self: flex-end; background: var(--primary, #2563eb); color: #fff; border-bottom-right-radius: 1.4cqw; }
.rv-slot { position: relative; display: flex; align-items: flex-start; gap: 2.2cqw; }
.rv-slot .rv-msg--ai { max-width: 86%; font-size: 3.7cqw; line-height: 1.5; }
.rv-msg--ai p { margin: 0 0 .6em; overflow: hidden; overflow: clip; }
.rv-ln { display: block; }
.rv-msg--ai p:last-child { margin: 0; }
.rv-chat-body { position: absolute; inset: 0; }
/* Text scrolling up leaves through a soft edge rather than a hard cut. */
.rv-chat::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 7cqw; z-index: 2;
  background: linear-gradient(var(--surface), transparent);
}
.rv-ava {
  flex: 0 0 auto; width: 8cqw; height: 8cqw; border-radius: 50%;
  display: inline-grid; place-items: center;
  background: var(--primary-soft, var(--primary-soft)); color: var(--primary, #2563eb); font-size: 4.2cqw; opacity: 0;
}
.rv-msg--ai { background: var(--surface-2); color: var(--ink); border-bottom-left-radius: 1.4cqw; font-weight: 500; }
/* --accent-ink-2: the deep cobalt on the light page, a light blue on the dark
   one. --primary-deep stayed dark in both, 2:1 on the dark ground. */
.rv-msg--ai b { font-weight: 800; color: var(--accent-ink-2); }
.rv-dots { position: absolute; left: 10.2cqw; top: 0; display: inline-flex; gap: 1.2cqw; padding: 3.4cqw 3.8cqw; }
/* Pulsed by the film's timeline for the second they are on screen. */
.rv-dots i { width: 1.6cqw; height: 1.6cqw; border-radius: 50%; background: var(--field-line); opacity: .3; }
.rv-compose {
  position: absolute; left: 5cqw; right: 5cqw; bottom: 5%;
  display: flex; align-items: center; gap: 2.4cqw;
  padding: 1.6cqw 1.6cqw 1.6cqw 4cqw; border: 2px solid var(--line); border-radius: 999px;
  background: var(--surface); box-shadow: 0 6px 18px rgba(var(--shadow-rgb), .06); opacity: 0;
  z-index: 3;
}
.rv-compose::before {
  content: ""; position: absolute; left: -5cqw; right: -5cqw; bottom: calc(100% + 2px); height: 9cqw;
  background: linear-gradient(transparent, var(--surface));
}
.rv-compose-in {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center;
  white-space: nowrap; overflow: hidden; overflow: clip;
  font-size: 3.9cqw; font-weight: 600; color: var(--ink);
}
.rv-compose .rv-ph { font-weight: 500; }
.rv-send {
  flex: 0 0 auto; width: 10cqw; height: 10cqw; border-radius: 50%;
  display: inline-grid; place-items: center;
  background: var(--primary, #2563eb); color: #fff; font-size: 4.2cqw;
}

/* Rolling digits. Tabular figures, so a column is as wide as every digit it
   rolls through and the settled number does not shuffle sideways. */
.odo { display: inline-flex; font-variant-numeric: tabular-nums; }
.odo-d { display: inline-block; height: 1.12em; overflow: hidden; overflow: clip; }
.odo-s { display: block; }
.odo-s i, .odo-c { display: block; height: 1.12em; line-height: 1.12em; font-style: normal; }
.odo-c { display: inline-block; }

@media (max-width: 991px) {
  .lp-phone { max-width: 296px; padding: 9px; border-radius: 40px; }
  .lp-phone-screen { border-radius: 32px; }
  .lp-reel { --reel-scale: .496; }
}

/* ---- Campus photos ---------------------------------------------------------
   The layer landing.js cross-fades: one .lp-campus-shot ships with the page
   and the script adds one per photograph in [data-campus]'s data-shots. Where
   it sits and how it fades into the page is landing-bold.css's business. */
.lp-campus-shot {
  position: absolute; inset: 0;
  background-size: cover; background-position: center 55%;
  opacity: 0; transition: opacity 1.6s ease;
}
.lp-campus-shot.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .lp-campus-shot { transition: none; } }
