/* =============================================================================
   University page v3 — the four university tabs and the seven programme tabs.
   Reference: docs/university-page-redesign/ (spec + 11 screenshots).

   Built entirely on the semantic tokens in style.css (--bg-surface, --border,
   --text-primary …). Nothing here hard-codes a light-mode colour, which is what
   makes dark mode work without a second stylesheet — and what stops these pages
   from drifting away from the rest of the site the next time the palette moves.

   Icons are Lucide, from theme/new/icons/lucide-sprite.svg. See v3_icon().
   ========================================================================== */

:root {
    --v3-accent:      #1d4ed8;   /* the blue the reference uses for figures and links */
    --v3-accent-weak: rgba(29, 78, 216, 0.08);
    --v3-radius:      14px;
    --v3-radius-sm:   9px;
    --v3-gap:         18px;
}

:root[data-theme="dark"] {
    --v3-accent:      #7aa2ff;
    --v3-accent-weak: rgba(122, 162, 255, 0.12);
}

/* ------------------------------------------------------------------ layout */

.v3-page {
    background: var(--bg-page);
    /* The site header is `position: fixed`, and the global clearance for it is
       `body:not(.home-new) { padding-top: 60px }` — but the header actually
       stands 86px tall (logo 58px + 14px padding top and bottom). The missing
       26px used to eat this panel's entire top padding, which is why the
       breadcrumb sat flush against the navigation bar. 26px of real space plus
       the 26px the body is short = 52px. */
    padding: 52px 0 60px;
    min-height: 60vh;
}

.v3-wrap {
    /* The reference draws these pages at ~1810px. 1240 (the rest of the site's
       width) squeezes rows the design keeps on one line — five course domains,
       a full timeline date. 1400 is the compromise: wide enough for those rows,
       close enough to the site's other pages not to read as a different site. */
    max-width: 1400px;
    margin: 0 auto;
    padding: 0 20px;
}

.v3-panel {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-radius: var(--v3-radius);
    box-shadow: var(--shadow-card);
    margin-bottom: var(--v3-gap);
    overflow: hidden;
}

.v3-panel__body { padding: 22px 26px; }

.v3-panel h2 {
    font-size: 19px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 0 0 12px;
}

.v3-panel h3 {
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 22px 0 10px;
}

.v3-muted  { color: var(--text-muted); }
.v3-note   { color: var(--text-secondary); font-size: 13px; line-height: 1.6; }

/* ------------------------------------------------------------------- icons */

.v3-i {
    width: 18px;
    height: 18px;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex: none;
    vertical-align: -3px;
}

.v3-icon-circle {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--v3-accent-weak);
    color: var(--v3-accent);
    flex: none;
}

.v3-icon-circle .v3-i { width: 20px; height: 20px; }

/* The outlined ring. Used where the icon is the only thing carrying the row —
   fact cards, course domains, learning formats, "other costs" — which is where
   the reference draws a large open circle rather than a small filled disc. */
.v3-icon-circle--outline {
    width: 56px;
    height: 56px;
    background: transparent;
    border: 1.5px solid var(--v3-accent-line, var(--border-strong));
    color: var(--text-primary);
}

.v3-icon-circle--outline .v3-i { width: 26px; height: 26px; }

/* A row of icon + text items with no card border, separated by hairlines.
   "Learning approach", "Core learning formats" and "Other costs to plan for"
   are all this shape in the reference — a card border there would read as four
   separate facts rather than one list. */
.v3-iconrow {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 0;
    margin: 14px 0 4px;
}

.v3-iconrow > div {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 6px 22px;
    color: var(--text-primary);
    font-size: 14px;
    line-height: 1.45;
}

.v3-iconrow > div + div { border-left: 1px solid var(--border); }
.v3-iconrow > div:first-child { padding-left: 0; }

/* The same row inside a bordered band — how the reference closes the Student
   Life tab. */
.v3-iconrow--boxed {
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    padding: 8px 20px;
    margin-top: 20px;
}

.v3-iconrow--boxed > div:first-child { padding-left: 0; }
.v3-iconrow a { color: var(--v3-accent); text-decoration: none; }
.v3-iconrow a:hover { text-decoration: underline; }
.v3-iconrow strong { display: block; font-weight: 500; margin-bottom: 3px; }
.v3-iconrow small { display: block; font-size: 13px; color: var(--text-secondary); }

/* -------------------------------------------------------------- breadcrumb */

.v3-crumbs {
    font-size: 13px;
    color: var(--text-muted);
    margin-bottom: 10px;
}

.v3-crumbs a { color: var(--v3-accent); text-decoration: none; }
.v3-crumbs a:hover { text-decoration: underline; }
.v3-crumbs span { margin: 0 7px; }

/* -------------------------------------------------------------------- hero */

/* No padding on the hero itself: the campus photo runs to the panel's own edge
   in the reference, so the padding belongs to the blocks beside it. */
.v3-hero { display: flex; align-items: stretch; gap: 0; padding: 0; }

.v3-hero__logo {
    margin: 22px 0 22px 26px;
    width: 92px;
    height: 92px;
    border-radius: 50%;
    object-fit: contain;
    background: var(--white);
    border: 1px solid var(--border);
    flex: none;
}

/* The neutral fallback: a monogram, not a symbol. Any glyph picked here — a
   tower, a crest, a book — would be an unofficial emblem invented by the
   developer for an institution that did not authorise it. Initials claim
   nothing. Padded, because a logo file carries its own whitespace and a bare
   monogram without any looks larger than the marks it sits alongside. */
.v3-hero__logo--fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 26px;
    font-weight: 800;
    letter-spacing: .02em;
    color: var(--text-muted);
    background: var(--bg-surface-2);
}

.v3-hero__shot {
    width: 300px;
    align-self: stretch;
    min-height: 132px;
    object-fit: cover;
    border-radius: var(--v3-radius) 0 0 var(--v3-radius);
    flex: none;
}

/* ------------------------------------------------------- hero photo gallery */

/* The gallery occupies exactly the slot the single photo did — same 300px
   column, same rounded outer corners — so a university with one photo and one
   with nine draw the identical hero box. Controls only appear on hover (and
   always on touch, where there is no hover to wait for). */
.v3-gallery {
    position: relative;
    overflow: hidden;
    cursor: zoom-in;
    background: var(--bg-page);
}

.v3-gallery__track {
    position: absolute;
    inset: 0;
}

.v3-gallery__slide {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity .35s ease;
    pointer-events: none;
}

.v3-gallery__slide.is-active { opacity: 1; }

.v3-gallery__nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(15, 23, 42, 0.55);
    color: #fff;
    cursor: pointer;
    opacity: 0;
    transition: opacity .2s ease, background .2s ease;
}

.v3-gallery__nav .v3-i { width: 17px; height: 17px; stroke-width: 2.2; }
.v3-gallery__nav:hover { background: rgba(15, 23, 42, 0.8); }
.v3-gallery__nav--prev { left: 8px; }
.v3-gallery__nav--next { right: 8px; }

.v3-gallery:hover .v3-gallery__nav,
.v3-gallery__nav:focus-visible { opacity: 1; }

.v3-gallery__dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 9px;
    display: flex;
    justify-content: center;
    gap: 6px;
    z-index: 2;
}

.v3-gallery__dot {
    width: 6px;
    height: 6px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.5);
    cursor: pointer;
    transition: width .2s ease, background .2s ease;
}

.v3-gallery__dot.is-active {
    width: 16px;
    border-radius: 3px;
    background: #fff;
}

/* The only permanent control: without it nothing on a still photo says there
   are eight more behind it. */
.v3-gallery__count {
    position: absolute;
    top: 8px;
    right: 8px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    border-radius: 999px;
    background: rgba(15, 23, 42, 0.6);
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.6;
    pointer-events: none;
}

.v3-gallery__count .v3-i { width: 13px; height: 13px; vertical-align: 0; }

/* ----------------------------------------------------------- photo lightbox */

.v3-lightbox {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(8, 11, 20, 0.92);
    opacity: 0;
    transition: opacity .2s ease;
}

.v3-lightbox.is-open { opacity: 1; }

.v3-lightbox__figure {
    max-width: min(1200px, 92vw);
    max-height: 86vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.v3-lightbox__img {
    max-width: min(1200px, 92vw);
    max-height: 86vh;
    object-fit: contain;
    border-radius: 10px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
    user-select: none;
    -webkit-user-drag: none;
}

.v3-lightbox__nav,
.v3-lightbox__close {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    cursor: pointer;
    transition: background .2s ease;
}

.v3-lightbox__nav:hover,
.v3-lightbox__close:hover { background: rgba(255, 255, 255, 0.26); }

.v3-lightbox__nav {
    top: 50%;
    transform: translateY(-50%);
    width: 48px;
    height: 48px;
}

.v3-lightbox__nav .v3-i { width: 26px; height: 26px; stroke-width: 2; }
.v3-lightbox__nav--prev { left: 20px; }
.v3-lightbox__nav--next { right: 20px; }

.v3-lightbox__close { top: 18px; right: 20px; width: 40px; height: 40px; }
.v3-lightbox__close .v3-i { width: 22px; height: 22px; stroke-width: 2; }

.v3-lightbox__count {
    position: absolute;
    bottom: 22px;
    left: 50%;
    transform: translateX(-50%);
    color: rgba(255, 255, 255, .85);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: .02em;
}

/* A film strip only earns its space when there is something to scan. */
.v3-lightbox__strip {
    position: absolute;
    bottom: 52px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    max-width: 92vw;
    overflow-x: auto;
    padding: 4px;
    scrollbar-width: none;
}

.v3-lightbox__strip::-webkit-scrollbar { display: none; }

.v3-lightbox__thumb {
    width: 64px;
    height: 44px;
    flex: none;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 6px;
    overflow: hidden;
    background: none;
    opacity: .5;
    cursor: pointer;
    transition: opacity .2s ease, border-color .2s ease;
}

.v3-lightbox__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.v3-lightbox__thumb:hover { opacity: .85; }
.v3-lightbox__thumb.is-active { opacity: 1; border-color: #fff; }

body.v3-lightbox-open { overflow: hidden; }

@media (max-width: 820px) {
    /* No hover on a phone — the arrows would otherwise never appear. */
    .v3-gallery__nav { opacity: 1; }

    .v3-lightbox__nav { width: 40px; height: 40px; }
    .v3-lightbox__nav--prev { left: 8px; }
    .v3-lightbox__nav--next { right: 8px; }
    .v3-lightbox__strip { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .v3-gallery__slide,
    .v3-lightbox { transition: none; }
}

.v3-hero__main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 22px 26px;
}

.v3-hero__logo + .v3-hero__main,
.v3-hero__shot + .v3-hero__main { padding-left: 30px; }

.v3-hero__name {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 30px;
    font-weight: 800;
    color: var(--text-primary);
    margin: 0 0 8px;
    line-height: 1.2;
}

.v3-verified {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    font-weight: 600;
    color: var(--v3-accent);
    border: 1px solid var(--v3-accent);
    border-radius: 20px;
    padding: 3px 11px;
}

.v3-verified .v3-i { width: 14px; height: 14px; }

.v3-hero__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 18px;
    color: var(--text-secondary);
    font-size: 14px;
}

.v3-hero__meta span { display: inline-flex; align-items: center; gap: 6px; }
.v3-hero__meta .v3-sep { color: var(--border-strong); }

.v3-hero__side {
    display: flex;
    align-items: center;
    gap: 0;
    flex: none;
    text-align: center;
    padding: 22px 30px 22px 0;
}

/* A hairline between the ranking and the student count, as in the reference —
   without it the two numbers read as one four-part figure. */
.v3-hero__side > * + * { border-left: 1px solid var(--border); }
.v3-hero__stat { padding: 2px 26px; }
.v3-hero__stat:last-child { padding-right: 0; }

/* The student count is the one KPI the reference gives an icon to. */
.v3-hero__stat--icon { display: flex; align-items: center; gap: 12px; text-align: left; }
.v3-hero__stat--icon .v3-i { width: 28px; height: 28px; color: var(--text-primary); flex: none; }

.v3-hero__stat strong {
    display: block;
    font-size: 22px;
    font-weight: 800;
    color: var(--text-primary);
    line-height: 1.1;
}

.v3-hero__stat span { font-size: 12px; color: var(--text-muted); }

/* -------------------------------------------------------------------- tabs */

.v3-tabs {
    display: flex;
    gap: 38px;
    padding: 0 26px;
    border-top: 1px solid var(--border);
    overflow-x: auto;
    scrollbar-width: none;
}

/* The university tab bar is a panel of its own (see university-detail.php), so
   it carries no divider of its own and the row is its whole height. */
.v3-tabpanel .v3-tabs { border-top: 0; }
.v3-tabpanel { margin-bottom: 18px; }

.v3-tabs::-webkit-scrollbar { display: none; }

.v3-tab {
    padding: 15px 4px;
    font-size: 15px;
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
    border-bottom: 3px solid transparent;
    white-space: nowrap;
}

.v3-tab:hover { color: var(--text-primary); }

.v3-tab.is-active {
    color: var(--v3-accent);
    border-bottom-color: var(--v3-accent);
}

/* ------------------------------------------------------------- stat strip */

.v3-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    border-bottom: 1px solid var(--border);
}

.v3-stat {
    padding: 18px 20px;
    text-align: center;
    border-right: 1px solid var(--border);
}

.v3-stat:last-child { border-right: 0; }
.v3-stat span { display: block; font-size: 13px; color: var(--text-secondary); margin-bottom: 5px; }

.v3-stat strong {
    font-size: 25px;
    font-weight: 800;
    color: var(--v3-accent);
    line-height: 1.1;
}

/* The PROGRAMME strip (Duration / Quota / Routes / Language / Start) is set in
   ink, not accent — those are five facts of equal weight, and colouring them
   all blue makes the row read as five links. The UNIVERSITY strip above keeps
   the accent, because there the numbers ARE the headline. */
.v3-stat small { font-size: 17px; color: var(--text-primary); font-weight: 700; }

/* --------------------------------------------------------- fact / mini cards */

.v3-cols {
    display: grid;
    grid-template-columns: 1.25fr 1fr 1.1fr;
    gap: 26px;
    align-items: start;
}

/* The reference separates the two fact columns with a hairline, which is what
   stops "Academic scale" and "Global community" reading as one long list. */
.v3-factlist { display: flex; gap: 14px; }
.v3-factlist + .v3-factlist { border-left: 1px solid var(--border); padding-left: 28px; }
.v3-factlist > div { min-width: 0; }

.v3-factlist h3 { margin-top: 0; }

.v3-fact-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 5px 0;
    font-size: 14px;
    color: var(--text-secondary);
}

.v3-fact-row b {
    color: var(--v3-accent);
    font-weight: 700;
    min-width: 34px;
    text-align: right;
    flex: none;
}

.v3-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
}

.v3-card {
    display: flex;
    align-items: center;
    gap: 13px;
    padding: 15px 17px;
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    background: var(--bg-surface);
}

.v3-card strong { display: block; font-size: 14px; color: var(--text-primary); }
.v3-card span   { font-size: 13px; color: var(--text-secondary); }
.v3-card b      { font-size: 21px; font-weight: 800; color: var(--v3-accent); }

.v3-card--tall { align-items: flex-start; }

/* Domains and learning formats are short labels; the reference fits five across.
   The default 240px track would break them onto a second row with one orphan. */
/* The fact and domain cards carry a sentence, not a heading — the reference
   sets them at normal weight. Only the campus and stage cards, where the first
   line really is a name, stay bold. */
.v3-cards--plain .v3-card strong { font-weight: 400; }

/* Cards that are three lines of text and no icon — the housing and living
   cards on the Costs tab. */
.v3-cards--text .v3-card { display: block; }
.v3-cards--text .v3-card strong { display: block; margin-bottom: 6px; }
.v3-cards--text .v3-card span { display: block; }
.v3-card__amount { color: var(--text-primary); margin-bottom: 4px; }

/* Title on the left, controls on the right, one line. */
.v3-sectionhead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}

.v3-sectionhead h2 { margin: 0; }
.v3-cards--plain .v3-card { align-items: center; gap: 18px; }

.v3-cards--tight { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; }
.v3-cards--tight .v3-card { padding: 14px; gap: 12px; }
.v3-cards--tight .v3-icon-circle--outline { width: 40px; height: 40px; }
.v3-cards--tight .v3-icon-circle--outline .v3-i { width: 20px; height: 20px; }

/* --------------------------------------------------------------- catalogue */

.v3-catalog {
    display: grid;
    grid-template-columns: 268px 1fr;
    gap: var(--v3-gap);
    align-items: start;
}

.v3-filters { padding: 20px 22px; }

.v3-filters__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}

.v3-filters__head h2 { margin: 0; }

.v3-linkbtn {
    background: none;
    border: 0;
    padding: 0;
    color: var(--v3-accent);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
}

.v3-fgroup { border-top: 1px solid var(--border); padding: 15px 0; }
.v3-fgroup:first-of-type { border-top: 0; padding-top: 0; }

.v3-fgroup__title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 10px;
}

/* The chevron is decoration, not a control: these groups are always open, and
   a filter that looks collapsible but is not would be worse than no chevron. */
.v3-fgroup__title .v3-i { width: 16px; height: 16px; color: var(--text-muted); }

.v3-check {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 5px 0;
    font-size: 14px;
    color: var(--text-secondary);
    cursor: pointer;
}

.v3-check input { width: 16px; height: 16px; accent-color: var(--v3-accent); }

.v3-selectwrap { position: relative; }

.v3-select {
    width: 100%;
    /* Room for the chevron drawn below — without it the text runs under the
       arrow on a long option like "Agriculture, Biology, Food Science…". */
    padding: 9px 36px 9px 11px;
    border: 1px solid var(--input-border);
    border-radius: var(--v3-radius-sm);
    background: var(--input-bg);
    color: var(--text-primary);
    font-size: 14px;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
}

/* Edge/IE still draw their own expand arrow next to ours. */
.v3-select::-ms-expand { display: none; }

.v3-selectwrap > .v3-i {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 16px;
    height: 16px;
    color: var(--text-muted);
    pointer-events: none;
}

.v3-fgroup small { display: block; margin-top: 7px; color: var(--text-muted); font-size: 12px; }

.v3-results__head {
    display: block;
    padding: 22px 26px 16px;
    border-bottom: 1px solid var(--border);
}

.v3-results__head h2 { margin: 0 0 6px; }
.v3-results__head span { font-size: 14px; color: var(--text-secondary); }

/* --------------------------------------------------------- programme row */

.v3-row {
    display: grid;
    /* Fixed columns, not fr-of-content. Each row is its own grid, so
       content-sized columns make every row a different shape and the "intake"
       and "application period" headings walk left and right down the list.
       Fixed widths are what make the list read as a table. */
    grid-template-columns: minmax(230px, 1fr) 168px 210px 132px;
    gap: 18px;
    align-items: center;
    padding: 18px 24px;
    border-top: 1px solid var(--border);
}

.v3-row__id { display: flex; align-items: center; gap: 14px; min-width: 0; }
.v3-row__id > div { min-width: 0; flex: 1 1 auto; }

.v3-row__logo {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    object-fit: contain;
    background: var(--white);
    border: 1px solid var(--border);
    flex: none;
}

.v3-row__name {
    font-size: 16px;
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 6px;
    line-height: 1.3;
}

.v3-tag {
    display: inline-block;
    font-size: 12px;
    color: var(--v3-accent);
    border: 1px solid var(--v3-accent);
    border-radius: 20px;
    padding: 2px 11px;
    margin: 2px 5px 2px 0;
}

.v3-row__cell { font-size: 13px; color: var(--text-secondary); border-left: 1px solid var(--border); padding-left: 18px; }
.v3-row__cell strong { display: block; font-size: 13px; color: var(--text-primary); margin-bottom: 4px; }
.v3-row__cell div { padding: 2px 0; display: flex; align-items: center; gap: 6px; }

.v3-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--accent-soft-green);
    background: rgba(16, 185, 129, 0.1);
    border-radius: 20px;
    padding: 3px 11px;
}

.v3-status .v3-i { width: 9px; height: 9px; fill: currentColor; }

/* ----------------------------------------------------------------- buttons */

.v3-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 11px 20px;
    border-radius: var(--v3-radius-sm);
    font-size: 14px;
    font-weight: 700;
    text-decoration: none;
    border: 1px solid transparent;
    cursor: pointer;
    white-space: nowrap;
}

.v3-btn--primary { background: var(--navy-900, #0a1628); color: #fff; }
.v3-btn--primary:hover { background: var(--navy-800, #10233f); color: #fff; }

/* In dark mode the navy fill sits almost on top of the surface colour, so a
   primary button reads as an outline and the active route filter stops looking
   selected. The accent carries the emphasis instead. */
:root[data-theme="dark"] .v3-btn--primary,
:root[data-theme="dark"] .v3-routefilter a.is-active {
    background: var(--v3-accent);
    border-color: var(--v3-accent);
    color: #0b1020;
}

:root[data-theme="dark"] .v3-btn--primary:hover { background: #9dbcff; color: #0b1020; }

.v3-btn--ghost {
    background: transparent;
    color: var(--text-primary);
    border-color: var(--border-strong);
}

.v3-btn--ghost:hover { background: var(--bg-surface-2); color: var(--text-primary); }

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

/* ------------------------------------------------------- programme header */

.v3-prog-head { padding: 24px 26px; display: flex; align-items: flex-start; gap: 20px; }
.v3-prog-head__main { flex: 1 1 auto; min-width: 0; }

.v3-prog-head h1 {
    font-size: 30px;
    font-weight: 800;
    color: var(--text-primary);
    margin: 0 0 12px;
    line-height: 1.2;
}

.v3-prog-head p { color: var(--text-secondary); font-size: 14px; line-height: 1.6; margin: 12px 0 0; max-width: 78ch; }

/* Two lines, then it stops. Clamped rather than truncated server-side because
   the cut then lands on a line boundary at whatever width the reader actually
   has, instead of at a character count guessed in PHP. The full text is never
   lost — it lives in Overview, which is one tab away. */
.v3-prog-head__summary {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.v3-chips { display: flex; flex-wrap: wrap; gap: 9px; }

.v3-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 13px;
    font-weight: 600;
    color: var(--v3-accent);
    border: 1px solid var(--v3-accent);
    border-radius: var(--v3-radius-sm);
    padding: 6px 13px;
}

.v3-chip--muted { color: var(--text-secondary); border-color: var(--border-strong); }
.v3-chip--status { color: var(--accent-soft-purple); border-color: var(--border-strong); }
.v3-chip--status::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }

/* ------------------------------------------------------- split with side-nav */

/* `stretch`, not `start`: in the reference the section list runs the full height
   of the content beside it. Left at `start` the aside is a short card with a
   column of empty page under it. */
.v3-split { display: grid; grid-template-columns: 280px 1fr; gap: var(--v3-gap); align-items: stretch; }

.v3-sidenav { padding: 20px 0; }
.v3-sidenav h2 { margin: 0 18px 12px; }

.v3-sidenav a {
    display: block;
    padding: 10px 18px;
    font-size: 14px;
    color: var(--text-secondary);
    text-decoration: none;
    border-left: 3px solid transparent;
}

.v3-sidenav a.is-active {
    color: var(--v3-accent);
    background: var(--v3-accent-weak);
    border-left-color: var(--v3-accent);
    font-weight: 600;
}

.v3-sidenav hr { border: 0; border-top: 1px solid var(--border); margin: 12px 18px; }

/* ------------------------------------------------------------------ journey */

.v3-journey {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 18px;
    position: relative;
    margin: 22px 0 8px;
}

/* Each stage is a link that behaves as a tab. Padding and a border make the
   selected state a shape rather than only a colour — the specification forbids
   colour as the sole carrier of an active state, and a border plus a filled dot
   survives both colour blindness and a monochrome print. */
.v3-stage {
    position: relative;
    display: block;
    padding: 12px 14px 14px;
    border: 1px solid transparent;
    border-radius: var(--v3-radius-sm);
    text-decoration: none;
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease;
}

.v3-stage:hover { background: var(--bg-surface-2); }

.v3-stage.is-selected {
    background: var(--v3-accent-weak);
    border-color: var(--v3-accent);
}

.v3-stage:focus-visible {
    outline: 2px solid var(--v3-accent);
    outline-offset: 2px;
}

.v3-stage__label { display: block; font-size: 14px; font-weight: 700; color: var(--v3-accent); margin-bottom: 12px; }

/* The selected stage's dot is filled, so the strip reads as a progress rail
   with a "you are here" marker and not just as five tinted boxes. */
.v3-stage.is-selected .v3-stage__dot { background: var(--v3-accent); }

.v3-stage__timing { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }

.v3-stage__items { display: block; }

/* The summary highlights. Same bullet the old <li> had — the element changed to
   a span because a link may not contain a list, but the reader should not be
   able to tell. */
.v3-stage__item {
    display: block;
    position: relative;
    padding-left: 15px;
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.5;
    margin-bottom: 7px;
}

.v3-stage__item::before {
    content: "";
    position: absolute;
    left: 0;
    top: 8px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--text-muted);
}

/* --------------------------------------------------------- stage detail panel */

.v3-stagepanel { margin-top: 8px; }
.v3-stagepanel:focus-visible { outline: 2px solid var(--v3-accent); outline-offset: 4px; border-radius: var(--v3-radius-sm); }
.v3-stagepanel h3 { margin-top: 18px; }

.v3-stagegroups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 14px;
    margin-top: 12px;
}

.v3-stagelist { list-style: none; margin: 0 0 12px; padding: 0; }

.v3-stagelist li {
    position: relative;
    padding-left: 15px;
    font-size: 13px;
    line-height: 1.55;
    color: var(--text-secondary);
    margin-bottom: 7px;
}

.v3-stagelist li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 8px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--v3-accent);
}

/* Dot and connector on one flex line. Drawing the rail per stage rather than as
   one absolute line across the grid is what keeps the line meeting the dot
   centres — an earlier version guessed the offsets as percentages and missed
   both ends. */
.v3-stage__rail { display: flex; align-items: center; margin-bottom: 14px; }

.v3-stage__rail::after {
    content: "";
    flex: 1 1 auto;
    height: 2px;
    background: var(--v3-accent);
    opacity: 0.35;
    /* Reach across the grid gap AND the two paddings that now sit between one
       stage's dot and the next: 14px of this stage's right padding, 18px of
       grid gap, 14px of the next stage's left padding. Without the full
       distance the line stops short of the dot it is pointing at. */
    margin-right: -46px;
}

.v3-stage:last-child .v3-stage__rail::after { display: none; }

.v3-stage__dot {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    border: 2px solid var(--v3-accent);
    background: var(--bg-surface);
    position: relative;
    z-index: 1;
    flex: none;
}

/* -------------------------------------------------------------- data table */

/* Horizontal rules only. The reference draws these tables as a bordered block
   with hairlines BETWEEN ROWS and nothing between columns — a full grid turns a
   five-row table into forty boxes, and the eye starts reading the lines instead
   of the values. */
.v3-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 14px;
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    overflow: hidden;
}

.v3-table th {
    text-align: left;
    font-weight: 500;
    font-size: 13px;
    color: var(--text-secondary);
    padding: 12px 18px;
    background: var(--bg-surface-2);
    border-bottom: 1px solid var(--border);
}

.v3-table td {
    padding: 13px 18px;
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    vertical-align: top;
}

.v3-table tr:last-child td { border-bottom: 0; }

.v3-table td:first-child { font-weight: 400; color: var(--text-primary); }

/* Where the first column is a NAME rather than a label — the accepted tests and
   qualifications — the reference sets it in bold. */
.v3-table--named td:first-child { font-weight: 600; }

.v3-scroll-x { overflow-x: auto; }

/* The reference rules off the last block of a long tab. */
.v3-rule { border: 0; border-top: 1px solid var(--border); margin: 26px 0 0; }

/* Five short scores side by side — the English-proficiency block. */
.v3-scorerow {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    overflow: hidden;
    margin: 14px 0 6px;
}

.v3-scorerow > div { padding: 14px 18px; }
.v3-scorerow > div + div { border-left: 1px solid var(--border); }
.v3-scorerow strong { display: block; color: var(--text-primary); font-weight: 600; margin-bottom: 6px; }
.v3-scorerow span { display: block; font-size: 14px; color: var(--text-secondary); }
.v3-scorerow small { display: block; font-size: 12px; color: var(--text-muted); margin-top: 4px; }

/* ---------------------------------------------------------------- timeline */

.v3-tl-controls { display: flex; justify-content: flex-end; margin: 12px 0 6px; }

.v3-tl-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    margin-bottom: 18px;
}

.v3-tl-head h2 { margin: 0; }

.v3-routefilter { display: flex; flex-wrap: wrap; gap: 8px; }

.v3-routefilter a {
    padding: 7px 15px;
    border-radius: var(--v3-radius-sm);
    border: 1px solid var(--border-strong);
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    text-decoration: none;
}

/* "All routes" is not a route: it stays neutral, and the reference fills it in
   the page's own dark navy. */
.v3-routefilter a.is-active {
    background: var(--navy-900, #0a1628);
    border-color: var(--navy-900, #0a1628);
    color: #fff;
}

/* A named route carries the SAME tone as the pills on the rows it filters —
   Route A blue, Route B violet. Outlined when it is an option, filled when it
   is the current view. Without this the filter and the rows look like two
   unrelated controls. */
.v3-routefilter--route-a    { border-color: var(--accent-soft-blue);   color: var(--accent-soft-blue); }
.v3-routefilter--route-b    { border-color: var(--accent-soft-purple); color: var(--accent-soft-purple); }
.v3-routefilter--external   { border-color: var(--accent-soft-orange); color: var(--accent-soft-orange); }
.v3-routefilter--embassy    { border-color: var(--accent-soft-gold);   color: var(--accent-soft-gold); }
.v3-routefilter--university { border-color: var(--accent-soft-green);  color: var(--accent-soft-green); }

.v3-routefilter--route-a.is-active    { background: var(--accent-soft-blue);   border-color: var(--accent-soft-blue); }
.v3-routefilter--route-b.is-active    { background: var(--accent-soft-purple); border-color: var(--accent-soft-purple); }
.v3-routefilter--external.is-active   { background: var(--accent-soft-orange); border-color: var(--accent-soft-orange); }
.v3-routefilter--embassy.is-active    { background: var(--accent-soft-gold);   border-color: var(--accent-soft-gold); }
.v3-routefilter--university.is-active { background: var(--accent-soft-green);  border-color: var(--accent-soft-green); }
.v3-routefilter a[class*="v3-routefilter--"].is-active { color: #fff; }

/* In dark mode the soft tones are pale, so white on them is unreadable —
   the same swap the primary button makes. */
:root[data-theme="dark"] .v3-routefilter a[class*="v3-routefilter--"].is-active { color: #0b1020; }

/* ------------------------------------------------------ document checklist */

/* A list an applicant works down, not a table. Each item is one document with
   everything that decides whether it will be accepted attached to it. The box
   is decorative — nothing here is stateful, and a checkbox the page cannot
   remember between visits is a promise it cannot keep — so it is aria-hidden
   and the status is carried by the badge in words. */
.v3-checklist { list-style: none; margin: 0 0 8px; padding: 0; display: grid; gap: 10px; }

.v3-checkitem {
    display: flex;
    gap: 14px;
    padding: 15px 17px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--v3-radius-sm);
    background: var(--bg-surface);
}

.v3-checkitem--req  { border-left-color: var(--v3-accent); }
.v3-checkitem--cond { border-left-color: var(--accent-soft-orange); }
.v3-checkitem--na   { border-left-color: var(--border-strong); opacity: .72; }

.v3-checkitem__box {
    flex: none;
    width: 20px;
    height: 20px;
    margin-top: 1px;
    border: 1.5px solid var(--border-strong);
    border-radius: 5px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}

.v3-checkitem__box .v3-i { width: 13px; height: 13px; }

.v3-checkitem__body { flex: 1 1 auto; min-width: 0; }

.v3-checkitem__head { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; margin-bottom: 5px; }
.v3-checkitem__head strong { font-size: 15px; font-weight: 700; color: var(--text-primary); }

.v3-checkitem p { margin: 0 0 5px; font-size: 13px; line-height: 1.55; color: var(--text-secondary); }
.v3-checkitem p:last-child { margin-bottom: 0; }

/* Submission method and restrictions read as annotations on the document, so
   they sit under it with their own glyph rather than as more body copy. */
.v3-checkitem__meta { display: flex; align-items: flex-start; gap: 7px; font-size: 12px; color: var(--text-muted); }
.v3-checkitem__meta .v3-i { width: 14px; height: 14px; flex: none; margin-top: 2px; }
.v3-checkitem__meta--warn { color: var(--accent-soft-orange); }

/* The status badge. It says the status in WORDS as well as in colour — the
   specification forbids colour as the only way to tell required from
   conditional, and a colour-blind reader gets nothing from the border alone. */
.v3-badge {
    display: inline-flex;
    align-items: center;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .02em;
    text-transform: uppercase;
    padding: 3px 8px;
    border-radius: 999px;
    border: 1px solid currentColor;
}

.v3-badge--req  { color: var(--v3-accent); }
.v3-badge--cond { color: var(--accent-soft-orange); }
.v3-badge--na   { color: var(--text-muted); }

.v3-timeline { position: relative; padding-left: 30px; }

.v3-timeline::before {
    content: "";
    position: absolute;
    left: 7px;
    top: 12px;
    bottom: 12px;
    width: 1px;
    background: var(--border-strong);
}

.v3-tl-row {
    display: grid;
    /* Same reasoning as .v3-row — the date column is fixed so the event titles
       form a single left edge down the rail. */
    grid-template-columns: 330px 1fr 112px;
    gap: 16px;
    align-items: center;
    padding: 13px 0;
    border-bottom: 1px solid var(--border);
    position: relative;
}

.v3-tl-row:last-child { border-bottom: 0; }

.v3-tl-row::before {
    content: "";
    position: absolute;
    left: -30px;
    top: 20px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    border: 1.5px solid var(--border-strong);
    background: var(--bg-surface);
}

.v3-tl-date  { font-size: 14px; color: var(--text-primary); font-weight: 400; }
/* Regular weight: the DATE is what the eye scans down this column, and
   bolding every title turns the whole page into emphasis. */
.v3-tl-event strong { display: block; font-size: 14px; font-weight: 400; color: var(--text-primary); }
.v3-tl-event small  { display: block; font-size: 13px; color: var(--text-muted); margin-top: 3px; }

.v3-pill {
    display: inline-block;
    justify-self: end;
    font-size: 12px;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: 20px;
    background: var(--bg-surface-2);
    color: var(--text-secondary);
    white-space: nowrap;
}

.v3-pill--route-a    { background: rgba(29, 78, 216, 0.12); color: var(--accent-soft-blue); }
.v3-pill--route-b    { background: rgba(126, 34, 206, 0.12); color: var(--accent-soft-purple); }
.v3-pill--external   { background: rgba(194, 65, 12, 0.12);  color: var(--accent-soft-orange); }
.v3-pill--embassy    { background: rgba(180, 83, 9, 0.12);   color: var(--accent-soft-gold); }
.v3-pill--university { background: rgba(4, 120, 87, 0.12);   color: var(--accent-soft-green); }

/* ------------------------------------------------------------------ notices */

.v3-notice {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 13px 16px;
    border-radius: var(--v3-radius-sm);
    background: var(--v3-accent-weak);
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1.55;
    margin: 16px 0;
}

.v3-notice .v3-i { color: var(--v3-accent); margin-top: 1px; }
.v3-notice--warn { background: rgba(245, 158, 11, 0.10); }
.v3-notice--warn .v3-i { color: var(--accent-soft-gold); }

.v3-footnote {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    padding: 14px 26px;
    border-top: 1px solid var(--border);
    font-size: 12px;
    color: var(--text-muted);
}

/* The reference writes the figure period and the ranking year as one sentence
   separated by a middle dot, not as two loose fragments. */
.v3-footnote--inline span + span::before { content: '·'; margin-right: 18px; }

/* Where the reference puts one note on the left and the update stamp on the
   right of the same line. */
.v3-footnote--split { justify-content: space-between; flex-wrap: nowrap; gap: 24px; }
.v3-footnote--split > span:last-child { text-align: right; flex: none; }

/* ---------------------------------------------------------------- sources */

/* Collapsed by default: the summary is the whole control when closed, so it
   carries the padding and the pointer, and the list below it only exists once
   the reader has asked for it. */
.v3-sources { font-size: 13px; color: var(--text-muted); }

.v3-sources > summary {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 26px;
    cursor: pointer;
    font-weight: 600;
    color: var(--text-secondary);
    list-style: none;          /* Firefox */
    user-select: none;
}

.v3-sources > summary::-webkit-details-marker { display: none; }

.v3-sources > summary:hover { color: var(--text-primary); }

/* The focus ring has to be visible on the summary itself — it is the only
   focusable thing in the component, and without it a keyboard user cannot see
   where they are. */
.v3-sources > summary:focus-visible {
    outline: 2px solid var(--v3-accent);
    outline-offset: -2px;
    border-radius: var(--v3-radius-sm);
}

.v3-sources > summary > span { flex: 1 1 auto; }

/* The caret is the open/closed state made visible; it must not be the ONLY
   signal, which is why the list itself appears and disappears with it. */
.v3-sources__caret { transition: transform .15s ease; flex: none; }
.v3-sources[open] > summary .v3-sources__caret { transform: rotate(180deg); }

.v3-sources__list {
    list-style: none;
    margin: 0;
    padding: 0 26px 18px;
    display: grid;
    gap: 10px;
}

.v3-sources__list li { line-height: 1.5; }

.v3-sources__list a {
    color: var(--v3-accent);
    overflow-wrap: break-word;
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
}

.v3-sources__list a .v3-i { width: 13px; height: 13px; flex: none; align-self: center; }
.v3-sources__list small { display: block; color: var(--text-muted); font-size: 12px; margin-top: 2px; }

/* ------------------------------------------------------------- empty state */

.v3-empty {
    text-align: center;
    padding: 46px 24px;
    color: var(--text-muted);
}

.v3-empty .v3-i { width: 30px; height: 30px; margin-bottom: 12px; color: var(--text-muted); }
.v3-empty p { font-size: 15px; font-weight: 600; color: var(--text-secondary); margin: 0 0 5px; }
.v3-empty span { font-size: 13px; }

/* ------------------------------------------------------- campuses & housing */

.v3-campus {
    display: grid;
    grid-template-columns: 180px 1fr auto;
    gap: 18px;
    align-items: center;
    padding: 16px 24px;
    border-top: 1px solid var(--border);
}

.v3-campus img { width: 100%; height: 104px; object-fit: cover; border-radius: var(--v3-radius-sm); }
.v3-campus strong { display: block; font-size: 16px; font-weight: 700; color: var(--text-primary); margin-bottom: 5px; }
.v3-campus p { font-size: 13px; color: var(--text-secondary); margin: 0; }
.v3-campus__actions { display: flex; flex-direction: column; gap: 8px; }

.v3-list-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 13px 0;
    border-bottom: 1px solid var(--border);
    font-size: 14px;
    color: var(--text-secondary);
}

.v3-list-row:last-child { border-bottom: 0; }

/* ---------------------------------------------------------------- currency */

.v3-currency { display: flex; gap: 8px; justify-content: flex-end; margin-bottom: 8px; }

/* In the USD view the converted figure leads and the published amount follows in
   brackets, one size down. The published amount is the FACT — it must stay
   legible, not shrink into a footnote — so it is only a step smaller and keeps a
   readable colour rather than the muted grey. */
.v3-amount { white-space: nowrap; }
.v3-amount__src { font-size: .88em; font-weight: 400; color: var(--text-secondary); }

.v3-fxnote { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 16px; }
.v3-fxnote .v3-i { width: 15px; height: 15px; flex: none; margin-top: 2px; }

/* ------------------------------------------------------- living estimate */

/* The living costs get a bordered block of their own so that nothing inside it
   can be read as a university invoice. The whole section is one visual unit
   that says "estimate" before any number in it is read. */
.v3-estimate {
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    padding: 16px 18px;
    margin-bottom: 8px;
}

.v3-estimate__lede {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin: 0 0 14px;
    font-size: 13px;
    line-height: 1.55;
    color: var(--text-secondary);
}

.v3-estimate__lede .v3-i { width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--text-muted); }

/* The monthly total is the number most readers came for, so it is the one thing
   in the block set at size. */
.v3-estimate__total {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
    margin: 14px 0 0;
    padding-top: 13px;
    border-top: 1px solid var(--border);
}

.v3-estimate__total strong { font-size: 14px; color: var(--text-primary); }
.v3-estimate__total > span { font-size: 19px; font-weight: 800; color: var(--v3-accent); }
.v3-estimate__total small { flex: 1 1 100%; font-size: 12px; color: var(--text-muted); }

.v3-estimate__link { margin: 13px 0 0; font-size: 13px; }

.v3-estimate__link a {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: var(--v3-accent);
    text-decoration: none;
}

.v3-estimate__link a:hover { text-decoration: underline; }
.v3-estimate__link .v3-i { width: 15px; height: 15px; }


/* ------------------------------------------------------- boxes and strips */

/* A bordered sub-panel INSIDE a panel — the reference uses one around the
   accommodation list and around each student-life group. */
.v3-box {
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    padding: 4px 18px;
    background: var(--bg-surface);
}

.v3-box__title {
    margin: 14px 0 6px;
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
}

.v3-box .v3-list-row:last-child { border-bottom: 0; }

/* Inside a themed student-life group the rows are a list of features, so they
   carry no dividers and no bold — the reference keeps the emphasis on the group
   title above them. */
.v3-box--plain .v3-list-row { border-bottom: 0; padding: 9px 0; }
.v3-box--plain .v3-list-row strong { font-weight: 400; }

/* The reference titles the two scholarship columns in accent blue — they are
   the two answers to one question, not two separate sections. */
.v3-panel .v3-box__title--accent,
.v3-panel .v3-h3--accent { color: var(--v3-accent); }

.v3-bullets { list-style: none; margin: 0 0 14px; padding: 0; }

.v3-bullets li {
    position: relative;
    padding-left: 18px;
    margin-bottom: 8px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-primary);
}

.v3-bullets li::before {
    content: "";
    position: absolute;
    left: 2px;
    top: 9px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--text-muted);
}

.v3-box__foot {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 0 14px;
}

/* The three themed cards side by side (Gap Term / Studio / Housing). */
.v3-groupgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
    gap: 16px;
    align-items: start;
    margin-top: 20px;
}

/* Two wide columns instead of three — the scholarships tab. */
.v3-groupgrid--2 { grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); }

/* The numbered journey across the top of Student Life. */
/* One bordered band, not four cards. The reference draws the journey as a
   single strip with chevron notches between the steps — four separate cards
   read as four unrelated facts rather than one sequence. */
.v3-strip {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 0;
    margin: 18px 0 4px;
    border: 1px solid var(--border);
    border-radius: var(--v3-radius-sm);
    background: var(--bg-surface);
    overflow: hidden;
}

.v3-strip__step {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 18px 20px;
}

/* The chevron the reference draws between the steps. Decorative, so it is a
   pseudo-element and never reaches the accessibility tree. */
/* A FULL-HEIGHT chevron between the steps, not a small arrowhead floating in
   the middle — in the reference the notch runs the whole height of the band and
   is what makes the four steps read as one ribbon moving left to right. The
   clip-path draws a thin outline, so it stays a hairline in both themes. */
.v3-strip__step + .v3-strip__step::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 26px;
    background: var(--border);
    clip-path: polygon(
        0 0,
        100% 50%,
        0 100%,
        0 calc(100% - 2px),
        calc(100% - 4px) 50%,
        0 2px
    );
}

.v3-strip__step + .v3-strip__step { padding-left: 34px; }

.v3-strip__n {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    border: 1.5px solid var(--v3-accent);
    font-size: 12px;
    font-weight: 700;
    color: var(--v3-accent);
    flex: none;
}

.v3-strip__step strong {
    display: block;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    line-height: 1.45;
}

.v3-strip__step span { font-size: 13px; color: var(--text-secondary); }

/* -------------------------------------------------------------- responsive */

@media (max-width: 1080px) {
    .v3-cols     { grid-template-columns: 1fr; }
    .v3-catalog,
    .v3-split    { grid-template-columns: 1fr; }
    .v3-sidenav  { display: none; }

    /* Stacked, the divider between the two fact columns becomes a stray line
       down the left of the second one. */
    .v3-factlist + .v3-factlist { border-left: 0; padding-left: 0; }
}

@media (max-width: 820px) {
    .v3-hero { flex-wrap: wrap; }
    /* Stacked, the photo sits on TOP of the card, so its rounded pair of
       corners moves from the left edge to the top edge. */
    .v3-hero__shot {
        width: 100%;
        height: 190px;
        min-height: 190px;
        border-radius: var(--v3-radius) var(--v3-radius) 0 0;
    }
    .v3-hero__side { width: 100%; justify-content: flex-start; text-align: left; }
    .v3-hero__name { font-size: 24px; }

    .v3-row {
        grid-template-columns: 1fr;
        gap: 12px;
    }
    .v3-row__cell { border-left: 0; padding-left: 0; border-top: 1px solid var(--border); padding-top: 12px; }
    .v3-row .v3-btn { width: 100%; }

    /* The journey stays horizontal and SCROLLS. Stacking it would put five
       full-height stage cards between the reader and the detail panel they
       select with them — the control would be taller than the content it
       controls. Scrolling keeps it one strip, which is also what it is: a row
       of tabs.

       Each stage gets a real minimum width so the strip overflows and scrolls
       instead of squeezing five stages into five unreadable columns. */
    .v3-journey {
        grid-auto-columns: minmax(210px, 1fr);
        overflow-x: auto;
        scroll-snap-type: x proximity;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 6px;
    }

    .v3-stage { scroll-snap-align: start; }

    /* The connector is drawn to the NEXT dot; while scrolling, the stage it
       points at is often off-screen, so the line is pointing at nothing. */
    .v3-stage__rail::after { display: none; }

    .v3-stagegroups { grid-template-columns: 1fr; }

    .v3-strip { grid-auto-flow: row; }
    .v3-strip__step + .v3-strip__step { padding-left: 20px; }
    .v3-strip__step + .v3-strip__step::before { display: none; }
    .v3-strip__step + .v3-strip__step { border-top: 1px solid var(--border); }

    .v3-tl-row { grid-template-columns: 1fr; gap: 6px; }
    .v3-tl-row::before { top: 16px; }

    .v3-campus { grid-template-columns: 1fr; }
    .v3-campus__actions { flex-direction: row; }

    .v3-stat { border-right: 0; border-bottom: 1px solid var(--border); }
    .v3-panel__body { padding: 18px; }

    /* WRAP, don't scroll. Seven programme tabs cannot fit one line on a phone,
       and a row that scrolls sideways hides the tabs past the edge with nothing
       to say they are there. Two visible lines beat one line and a secret. */
    .v3-tabs {
        flex-wrap: wrap;
        overflow-x: visible;
        gap: 2px 18px;
        padding: 4px 14px 0;
    }

    .v3-tab { padding: 10px 2px; font-size: 14px; }

    .v3-iconrow > div { padding: 10px 0; }
    .v3-iconrow > div + div { border-left: 0; border-top: 1px solid var(--border); }
    .v3-iconrow--boxed { padding: 4px 16px; }

    .v3-scorerow { grid-template-columns: 1fr; }
    .v3-scorerow > div + div { border-left: 0; border-top: 1px solid var(--border); }

    .v3-tl-row { grid-template-columns: 1fr; }
    .v3-hero__side { padding: 0 20px 20px; }

    /* An odd last cell alone in a two-column strip reads as a missing cell. */
    .v3-stats > .v3-stat:last-child:nth-child(odd) { grid-column: 1 / -1; }

    /* Let the table SCROLL at a legible width instead of squeezing four words
       onto four lines. Squeezing loses the notes column silently; scrolling at
       least keeps every column readable — and the line below says so, because a
       column cut off at the edge with no hint reads as a broken table. */
    .v3-scroll-x .v3-table { min-width: 520px; }

    .v3-scroll-x::after {
        content: "Swipe to see all columns";
        position: sticky;
        left: 0;
        display: block;
        width: max-content;
        padding: 7px 2px 0;
        font-size: 12px;
        color: var(--text-muted);
    }

    .v3-footnote--inline span + span::before { display: none; }
    .v3-sectionhead { align-items: flex-start; }
    .v3-currency { flex-wrap: wrap; }
}

/* Keyboard focus must stay visible — these pages are a lot of links in a row. */
.v3-page a:focus-visible,
.v3-page button:focus-visible,
.v3-page select:focus-visible,
.v3-page input:focus-visible {
    outline: 2px solid var(--v3-accent);
    outline-offset: 2px;
}
