* {
    box-sizing: border-box;
}

html.is-loaded {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    font-family: 'Public Sans', 'Public Sans fallback', system-ui, sans-serif;
    background: #fbf3e8;
    color: #4a2b30;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

a {
    color: #E80202;
    text-decoration: none;
    transition: color .2s ease;
}

a:hover {
    color: #E80202;
}

::selection {
    background: #E80202;
    color: #fff;
}

/* Paper texture. The cream ground read as flat vinyl; a fine turbulence
     grain gives it tooth without becoming a pattern you can name. Inline as a
     data URI so it costs no request, and pinned to the element (not scrolling)
     so it behaves like stock rather than wallpaper. */
.grain {
    position: relative;
}

.grain::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
    opacity: .32;
    mix-blend-mode: multiply;
}

.grain>* {
    position: relative;
}

/* On the drenched sections the same grain is lightened instead, or it just
     muddies the crimson. */
.grain--dark::before {
    mix-blend-mode: overlay;
    opacity: .22;
}

/* Motion budget: one entrance, one hover response, one line-draw. Nothing
     loops, nothing auto-advances, nothing moves while it is being read. */
@keyframes riseIn {
    from {
        opacity: 0;
        transform: translateY(12px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.reveal {
    opacity: 0;
}

.reveal.in {
    animation: riseIn .6s cubic-bezier(.22, .61, .36, 1) forwards;
}

/* Portraits do not "arrive" like a line of prose. They settle: a shallow
     scale from 97%, no vertical travel, slightly slower so a wall of 44 faces
     resolves calmly rather than marching. */
@keyframes plateIn {
    from {
        opacity: 0;
        transform: scale(.97);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.fac.reveal.in,
.fac.in,
.stagger.roster>.in,
.roster>li.reveal.in {
    animation: plateIn .7s cubic-bezier(.22, 1, .36, 1) forwards;
}


.navlink {
    position: relative;
}

.navlink::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -6px;
    height: 1.5px;
    width: 100%;
    background: #E80202;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .3s cubic-bezier(.22, .61, .36, 1);
}

.navlink:hover::after {
    transform: scaleX(1);
}

/* 3px, not 7px. A lift you notice rather than one that announces itself. */
.lift {
    transition: transform .35s var(--ease-soft), box-shadow .35s var(--ease-soft);
}

.lift:hover {
    transform: translateY(-3px);
}

/* The complication rows: the rule warms rather than the row jumping. */
.comp {
    transition: background .3s var(--ease-soft);
}

.comp:hover {
    background: rgba(255, 255, 255, 0.04);
}

.comp:hover .comp__icon {
    color: #E80202;
}

.comp__icon {
    color: #E80202;
    transition: color .3s var(--ease-soft);
}


.hero-cd__n {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-weight: 600;
    font-size: clamp(1.5rem, 2.6vw, 2rem);
    color: #fff;
    line-height: 1;
}

.hero-cd__u {
    color: rgba(255, 255, 255, .6);
    font-size: 0.75rem;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    font-weight: 600;
    margin-left: 6px;
}

/* The complication map. Points are HTML buttons over an SVG schematic:
     focusable, hit-testable and styleable in a way SVG children are not. */
/* The in-page jump previously cleared the 73px fixed nav only because the
     h2 happened to sit 104px in. Stated as a rule instead. */
#complications {
    scroll-margin-top: 92px;
}

/* Heading and its argument as one header line, rather than a heading alone
     against 600px of empty ground. Collapses to stacked below 900px. */
.sec-head {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    gap: clamp(20px, 4vw, 64px);
    align-items: end;
    margin: 0 0 clamp(40px, 4.6vw, 64px);
}

@media (max-width: 900px) {
    .sec-head {
        grid-template-columns: 1fr;
        gap: 16px;
        align-items: start;
    }
}

.cmap__stage {
    position: relative;
    width: min(100%, 460px);
    margin: 0 auto;
}

.cmap__art {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1045 / 1100;
    filter: drop-shadow(0 24px 60px rgba(20, 0, 4, .5));
}

/* The dot must land on the coordinate, not the button. translate(-50%)
     centres the whole flex row — dot plus label  so every point sat shoved
     left by half its label width. Offset by half the DOT instead, and flip
     the row past the midline so labels never run off the diagram. */
/* The point's box is exactly the dot, and the label hangs off it out of
     flow. This is the whole reason the dots were moving.

     They used to be a flex row [dot][label], anchored by translating
     `calc(-100% + 6.5px)` — and `-100%` is the POINT's width, which includes
     the label. So the dot's position was a function of its label's width: the
     two right-hand points sat 14.75px apart in their offsets purely because
     their captions are different lengths, and anything that changed a label's
     width moved its dot. The webfont swapping in after first paint does
     exactly that, as does browser zoom.

     A fixed 13x13 box centred on the anchor puts the dot on precisely the same
     coordinate as before, and nothing about the label can reach it. */
.cmap__pt {
    position: absolute;
    display: block;
    width: 13px;
    height: 13px;
    padding: 0;
    background: none;
    border: 0;
    transform: translate(-50%, -50%);
    cursor: pointer;
    color: rgba(255, 255, 255, .62);
    font: inherit;
    font-size: 0.78125rem;
    letter-spacing: .2px;
    transition: color .25s var(--ease-soft);
}

/* No pointer cursor where hover drives it: a hand cursor on something that
     does not answer a click is a promise the interface does not keep. Must
     come after the base rule — the same selector earlier simply lost. */
@media (hover: hover) {
    .cmap__pt {
        cursor: default;
    }
}

/* Both sides share one transform now; the modifier only says which way the
     label hangs. */
/* The visible dot stays 13px; a pseudo-element extends the hit area to
     25x25 so it clears WCAG 2.5.8's 24x24 minimum. Done this way rather than
     with padding on the button, which would shift the dot off the coordinate
     it is anchored to. -8px gives 29px against the 24px minimum; -6px measured
     23px once subpixel rounding was taken into account. */
.cmap__dot {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: #fff;
    border: 2px solid #E80202;
    box-shadow: 0 0 0 3px rgba(30, 2, 7, .5);
    transition: background .25s var(--ease-soft), border-color .25s var(--ease-soft);
}

/* The label can land anywhere on the illustration — over gold vessels, over
     bright red muscle — so it carries its own ground rather than relying on
     whatever happens to be behind it. Measured 3.87:1 without this. */
.cmap__dot::before {
    content: '';
    position: absolute;
    inset: -8px;
    border-radius: 50%;
}

/* Two labels can be visible at once — keyboard focus on one point while the
     mouse rests on another — and of the 42 focus/hover pairs exactly one
     (Acute Closure with Device Entrapment) overlaps. Rather than move a dot
     off the artery it names, the labels stack: the one being pointed at rises
     above, and the ground is fully opaque so the overlap reads as one label
     in front of another instead of two bleeding through each other. */
/* The one signature entrance on the site. The seven points arrive in
     anatomical order, top of the heart down to the apex — the same sequence
     the arrow keys walk and the list below reads. It teaches the order rather
     than decorating the arrival, it runs once, and it is over in 700ms.
     Everything else on the page is feedback, not choreography. */
/* Opacity only. This used to scale the dot from .4 to 1, which meant every
     dot changed size on the way in — the last thing on the diagram that was
     not static. */
@keyframes pointIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.cmap__dot {
    animation: pointIn .42s var(--ease-soft) both;
    animation-delay: calc(var(--i, 0) * 90ms + 240ms);
}

.cmap__pt {
    z-index: 1;
}

.cmap__pt[aria-pressed="true"] {
    z-index: 2;
}

.cmap__pt:hover,
.cmap__pt:focus-visible {
    z-index: 3;
}

/* Fades only — no slide. Revealing the label used to move it 4px right,
     which is the movement the client kept seeing on the dots: you cannot
     click one without hovering it first. Nothing on this diagram changes
     position in any state now; the dot's own scale and colour carry the
     feedback, and neither is layout. */
.cmap__label {
    position: absolute;
    top: 50%;
    translate: 0 -50%;
    left: calc(100% + 9px);
    white-space: nowrap;
    opacity: 0;
    padding: 3px 9px;
    border-radius: 999px;
    background: #1e0207;
    transition: opacity .25s var(--ease-soft);
}

.cmap__pt--right .cmap__label {
    left: auto;
    right: calc(100% + 9px);
}

.cmap__pt:hover,
.cmap__pt:focus-visible {
    color: #fff;
}

/* Colour only. The dot's geometry is identical in every state: its ring used
     to grow 3px -> 4px on hover and gain an 8px halo when selected, and a ring
     growing around a 13px dot reads as the dot itself moving.
     (The `transform: scale(1.25)` that used to be here was already dead: the
     `pointIn` entrance animation runs with `fill: both`, and an animation's
     filled value beats a normal declaration — the same trap that had the card
     hovers and `.lift` shipping inert.) */
.cmap__pt:hover .cmap__dot,
.cmap__pt:focus-visible .cmap__dot {
    background: #E80202;
}

.cmap__pt:hover .cmap__label,
.cmap__pt:focus-visible .cmap__label {
    opacity: 1;
}

.cmap__pt[aria-pressed="true"] .cmap__dot {
    background: #E80202;
    border-color: #fff;
}

.cmap__pt[aria-pressed="true"] {
    color: #E80202;
}

/* Deliberately NOT `[aria-pressed] .cmap__label { opacity: 1 }`. A pinned
     label is a tag that never goes away, and because showing one runs a 4px
     slide, moving the selection slid one label out while another slid in —
     which read as the dots and their text moving on every click. The selected
     point is marked by its own colour and ring instead, both of which are
     paint, not layout. */
.cmap__detail {
    margin: 22px auto 0;
    max-width: 30rem;
    text-align: center;
    transition: opacity .2s var(--ease-soft);
}

.cmap__detail.is-swapping {
    opacity: 0;
}

.cmap__tag {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-style: italic;
    color: #E80202;
    font-size: 0.90625rem;
    margin: 0 0 4px;
}

.cmap__title {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-weight: 600;
    font-size: 1.4375rem;
    color: #fff;
    margin: 0 0 8px;
    letter-spacing: -.1px;
}

.cmap__desc {
    color: rgba(255, 255, 255, .76);
    font-size: 0.9375rem;
    line-height: 1.6;
    margin: 0;
}

/* Below 620px the labels used to be display:none, which left seven
     anonymous 13px dots on a picture of a heart — unusable by touch and
     meaningless to look at. Under that width the points stop pretending to be
     hotspots and become a wrapped row of named chips beneath the illustration:
     same buttons, same handlers, same order, but each one says what it is and
     is a 34px tap target. The heart stays as the illustration it always was. */
@media (max-width: 620px) {
    .cmap__stage {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 8px;
        width: 100%;
    }

    .cmap__art {
        flex: 0 0 100%;
        max-width: 320px;
        margin: 0 auto 18px;
    }

    .cmap__pt {
        position: static;
        display: flex;
        align-items: center;
        gap: 7px;
        width: auto;
        height: auto;
        transform: none;
        white-space: normal;
        max-width: 100%;
        padding: 8px 13px;
        border-radius: 999px;
        gap: 7px;
        border: 1px solid rgba(247, 217, 138, .3);
        background: rgba(255, 255, 255, .04);
        color: rgba(255, 255, 255, .82);
    }

    .cmap__pt--right .cmap__label {
        transform: none;
    }

    .cmap__dot {
        position: relative;
        inset: auto;
        width: 13px;
        height: 13px;
        flex: none;
    }

    .cmap__label {
        position: static;
        translate: none;
        opacity: 1;
        background: none;
        padding: 0;
        white-space: normal;
    }

    .cmap__pt[aria-pressed="true"] {
        background: rgba(228, 184, 74, .16);
        border-color: #E80202;
        color: #E80202;
    }

    .cmap__dot {
        width: 8px;
        height: 8px;
    }

    .cmap__dot::before {
        display: none;
    }
}

/* Roster rows centre their last line. An auto-fit grid left the seventh
     leader orphaned hard against the left edge; flex-wrap with a fixed tile
     width centres whatever the final row holds. */
/* Ways in. Rules rather than cards: the section is a list of routes, and
     four boxed panels made it read as a fourth card grid on a page that
     already had three. */
.ways {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
}

.ways>li {
    border-left: 1px solid rgba(184, 17, 42, .18);
}

.ways>li:first-child {
    border-left: 0;
}

.ways__link {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 4px clamp(18px, 2.2vw, 30px);
    text-decoration: none;
    transition: transform .3s var(--ease-soft);
}

.ways>li:first-child .ways__link {
    padding-left: 0;
}

.ways__link:hover {
    transform: translateY(-3px);
}

.ways__title {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-weight: 600;
    font-size: clamp(1.25rem, 2.1vw, 1.5625rem);
    line-height: 1.18;
    letter-spacing: -.2px;
    color: #e80202;
}

.ways__text {
    font-size: 0.90625rem;
    line-height: 1.6;
    color: white;
    margin: 10px 0 14px;
    flex: 1;
}

/* #7a5c44 measured 3.02:1 here. The section sits on a gradient, so the same
     token passes in one position and fails in another  this one is dark
     enough to hold wherever the row lands. */
.ways__meta {
    font-size: 0.8125rem;
    color: white;
    margin: 0 0 16px;
}

.ways__meta:empty {
    display: none;
}

.ways__go {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: #E80202;
    font-weight: 600;
    font-size: 0.875rem;
}

/* The arrow slides; the row does not re-lay-out. This animated `gap`
     before — a layout property, with no transition on it at all, so it
     snapped a full reflow on every hover. */
.ways__go svg {
    width: 15px;
    height: 15px;
    transition: translate .45s cubic-bezier(.22, 1, .36, 1);
}

.ways__link:hover .ways__go svg {
    translate: 4px 0;
}

@media (prefers-reduced-motion: reduce) {
    .ways__go svg {
        transition: none;
    }

    .ways__link:hover .ways__go svg {
        translate: none;
    }
}

@media (max-width: 900px) {
    .ways {
        grid-template-columns: repeat(2, 1fr);
        row-gap: 30px;
    }

    .ways>li:nth-child(odd) {
        border-left: 0;
    }

    .ways>li:nth-child(odd) .ways__link {
        padding-left: 0;
    }
}

@media (max-width: 560px) {
    .ways {
        grid-template-columns: 1fr;
        row-gap: 0;
    }

    .ways>li {
        border-left: 0;
        border-top: 1px solid rgba(184, 17, 42, .18);
    }

    .ways>li:first-child {
        border-top: 0;
    }

    .ways__link {
        padding: 22px 0;
    }
}

/* Chairman. The portrait sits on the same crimson plate the roster uses, so
     the page reads as one system rather than a one-off treatment. */
/* The line the course is actually built on. Set as a pull-quote rather than
     another full-bleed band: it belongs to the chairman's welcome, and lifting
     it out of context would make it a slogan instead of something a person
     said. The rule and the indent do the work; nothing is louder than it needs
     to be. */
.second-patient {
    margin: clamp(26px, 3vw, 38px) 0 0;
    padding: clamp(20px, 2.4vw, 28px) 0 0;
    border-top: 1px solid rgba(184, 17, 42, .28);
}

.second-patient p {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-weight: 600;
    font-size: clamp(1.4375rem, 2.5vw, 1.9375rem);
    line-height: 1.24;
    letter-spacing: -0.3px;
    color: #E80202;
    margin: 0;
    text-wrap: balance;
}

.second-patient em {
    font-style: italic;
    font-weight: 400;
    color: #E80202;
}

.second-patient cite {
    display: block;
    margin: 12px 0 0;
    font-style: normal;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #7a5c44;
}

.chair {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: clamp(24px, 4vw, 48px);
    align-items: start;
}

.chair__plate {
    width: 200px;
    flex-shrink: 0;
}

@media (max-width: 720px) {
    .chair {
        grid-template-columns: 1fr;
    }

    .chair__plate {
        width: 170px;
    }
}

.roster {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    gap: clamp(14px, 1.8vw, 22px);
    list-style: none;
    margin: 0;
    padding: 0;
}

.roster>li {
    width: clamp(128px, 14vw, 168px);
}

/* The home strip is exactly seven people, and seven is prime: a width-based
     wrap put six in the first row and left the seventh alone underneath. An
     explicit column count makes the split deliberate at every size —
     7 across on desktop, 4+3 on tablet, pairs on phones — and 1fr columns
     mean no width arithmetic to fall out of step with the container. */
.roster__head {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-weight: 600;
    font-size: 1.1875rem;
    color: #E80202;
    margin: 0 0 16px;
    padding-bottom: 9px;
    border-bottom: 1px solid rgba(184, 17, 42, .22);
}

/* auto-fill, not a fixed 7 columns: the groups are 1, 4 and 2 people, and a
     fixed track count left the chairman's row stretched across seven slots. */
.roster--strip {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(128px, 168px));
    justify-items: center;
}

.roster--strip>li {
    width: 100%;
    max-width: 168px;
}

@media (max-width: 1099px) {
    .roster--strip {
        grid-template-columns: repeat(4, 1fr);
    }
}

@media (max-width: 699px) {
    .roster--strip {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Names run to two lines for some people and one for others; reserving the
     second line keeps every role and place on the same baseline across a row. */
/* The hero credit block. `width: fit-content` is what makes it read as one
     object: the rule then spans exactly the logos above it instead of the
     whole column, so the two rows are visibly tied together. */
.lockup {
    width: fit-content;
    max-width: 100%;
    margin: 0 0 clamp(26px, 3vw, 38px);
}

.lockup__lead {
    display: flex;
    align-items: center;
    gap: clamp(18px, 2.2vw, 30px);
    flex-wrap: wrap;
}

/* Height, not width, levels a row of logos — matching widths would shrink
     the badge-shaped mark beside the wide wordmark. */
.lockup__lead img {
    height: clamp(44px, 4.2vw, 68px);
    width: auto;
    display: block;
}

.lockup__bar {
    width: 1px;
    align-self: stretch;
    min-height: 46px;
    flex: none;
    background: linear-gradient(rgba(247, 217, 138, 0), rgba(247, 217, 138, .55), rgba(247, 217, 138, 0));
}

.lockup__with {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px clamp(14px, 1.6vw, 20px);
    margin-top: clamp(16px, 1.8vw, 22px);
    padding-top: clamp(14px, 1.6vw, 20px);
    border-top: 1px solid rgba(247, 217, 138, .24);
}

.lockup__label {
    color: rgba(255, 255, 255, 0.72);
    font-size: 0.6875rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    font-weight: 700;
}

.lockup__strip {
    display: inline-flex;
    align-items: center;
    gap: clamp(12px, 1.4vw, 18px);
    padding: 8px clamp(14px, 1.5vw, 18px);
    border-radius: 14px;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(255, 255, 255, 0.86));
    box-shadow: 0 8px 22px rgba(40, 0, 8, 0.26);
}

.lockup__strip img {
    height: clamp(26px, 2.4vw, 38px);
    width: auto;
    display: block;
}

.lockup__div {
    width: 1px;
    align-self: stretch;
    min-height: 26px;
    flex: none;
    background: rgba(58, 4, 16, 0.16);
}

@media (max-width: 620px) {
    .lockup__label {
        flex: 1 0 100%;
    }
}

.fac__name {
    min-height: 2.6em;
}

/* Same fix as the faculty page: with subgrid the three rows of a card align
     across their own row, so the two-line reservation is not needed and the
     card's internal gutters must not inherit the grid's card-separating gap. */
@supports (grid-template-rows: subgrid) {

    /* min-width: 0 is required, not cosmetic. A grid item defaults to
       min-width: auto, so once these became grid containers they refused to
       shrink below their longest name — at a 32px root font that pushed a
       137px track out to 213px and the page scrolled sideways. */
    .roster--strip>li {
        display: grid;
        grid-template-rows: subgrid;
        grid-row: span 3;
        row-gap: 0;
        min-width: 0;
    }

    .roster--strip>li>* {
        min-width: 0;
    }

    .roster--strip>li .fac__name {
        min-height: 0;
    }
}

/* Roster tiles, shared with the faculty page. */
.fac {
    margin: 0;
}

.fac__plate {
    position: relative;
    overflow: hidden;
    border-radius: 16px;
    aspect-ratio: 4 / 5;
    background: linear-gradient(165deg, #E80202, #E80202 62%, #5a0414);
    box-shadow: 0 10px 26px rgba(122, 5, 18, 0.16);
}

/* The portrait's alpha ramp dissolves it into the plate over the last 30%.
     On a dark suit that lands in crimson on its own; on a white coat it fades
     through a milky pink instead, because what is dissolving is light. This
     tints the dissolve zone back to the plate's own bottom tone so every
     portrait ends in the same material regardless of what the subject wore.
     It also covers the residual saturation step where the alpha finally dies:
     the image renders at saturate(.35) and the plate beneath is full crimson,
     so the two never meet in the same tone by themselves. */
.fac__plate::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 36%;
    pointer-events: none;
    background: linear-gradient(to top, rgba(90, 4, 20, .88) 0%,
            rgba(90, 4, 20, .46) 38%, rgba(90, 4, 20, 0) 100%);
}

.fac__plate img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top center;
    display: block;
    filter: saturate(.35) contrast(1.04);
    transition: filter .45s var(--ease-soft), transform .45s var(--ease-soft);
}

.fac:hover .fac__plate img {
    filter: saturate(1) contrast(1);
    transform: scale(1.03);
}

.fac__name {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-weight: 600;
    font-size: 0.96875rem;
    line-height: 1.3;
    color: #3a0410;
    margin: 12px 0 2px;
}

.fac__place {
    font-size: 0.78125rem;
    color: #5a3a3f;
    margin: 0;
    font-weight: 500;
}

@media (prefers-reduced-motion: reduce) {
    .reveal {
        opacity: 1 !important;
        animation: none !important;
    }
}

@media (max-width: 1000px) {
    [data-hero-grid] {
        grid-template-columns: 1fr !important;
        gap: 40px !important;
    }

    [data-two-col] {
        grid-template-columns: 1fr !important;
    }

    [data-footer-grid] {
        grid-template-columns: 1fr 1fr !important;
        gap: 30px !important;
    }
}

@media (max-width: 860px) {
    [data-desktop-nav] {
        display: none !important;
    }

    [data-mobile-btn] {
        display: flex !important;
    }
}

@media (max-width: 720px) {
    [data-three-col] {
        grid-template-columns: 1fr !important;
    }

    [data-four-col] {
        grid-template-columns: 1fr 1fr !important;
    }

    [data-footer-grid] {
        grid-template-columns: 1fr !important;
    }
}

/* Two columns at 390px gave a 15-character measure. One column below 560. */
@media (max-width: 560px) {
    [data-four-col] {
        grid-template-columns: 1fr !important;
    }

    [data-countdown] {
        grid-template-columns: repeat(3, 1fr) !important;
    }
}

/* --- design tokens ------------------------------------------------------ */
/* Generated from DESIGN.md at build time, so the documented palette and the
   shipped one cannot drift. The compiled markup still carries literal
   values inline; these exist for anything written from here on. */
:root {
    --color-contrast-crimson: #E80202;
    --color-oxblood: #E80202;
    --color-oxblood-deep: #47030d;
    --color-cath-black: #2a0208;
    --color-ceremonial-gold: #E80202;
    --color-lamp-gold: #E80202;
    --color-gold-deep: #cf9a2e;
    --color-gold-ink: white;
    --color-ground-deep: #3d0410;
    --color-oxblood-mid: #5a0414;
    --color-footer-black: #160104;
    --color-paper: #fbf3e8;
    --color-paper-lit: #fdf6ec;
    --color-paper-shade: #f7ead9;
    --color-ink: #3a0410;
    --color-ink-body: #4a2b30;
    --color-ink-muted: #5a3a3f;
    --color-ink-quiet: #7a5c44;
    --color-white: #ffffff;
    --radius-chip: 999px;
    --radius-sm: 8px;
    --radius-md: 14px;
    --radius-lg: 16px;
    --radius-xl: 24px;
    --radius-hero: 28px;
    --radius-panel: 32px;
    --radius-circle: 50%;
    --space-xs: 8px;
    --space-sm: 14px;
    --space-md: 22px;
    --space-lg: 30px;
    --space-xl: 44px;
    --space-section: clamp(56px, 7vw, 100px);
    --space-gutter: clamp(16px, 4vw, 52px);
}

/* --- hidden panels ----------------------------------------------------- */
/* `hidden` hides an element through a UA rule (`[hidden]{display:none}`), and
   an inline style outranks it. The mobile menu carries `display:flex` inline,
   so the attribute alone left a fixed, full-viewport, z-index:110 overlay
   sitting invisibly over the whole desktop page and eating every click  the
   hero's atlas buttons were being intercepted by the menu's Register link.
   This has to be !important to outrank that inline declaration. */
[data-panel][hidden] {
    display: none !important;
}

/* --- fixed nav legibility ---------------------------------------------- */
/* The nav is translucent (rgba(43,3,9,.55) -> .3) over a backdrop blur. That
   reads beautifully over the crimson hero, but the page below it is cream, and
   measured from rendered pixels the links fell to 2.17-2.49:1 there — WCAG AA
   wants 4.5:1, and even large text wants 3:1. The tint is deepened so the
   plate stays dark whatever passes under it; the gradient, the blur and the
   saturation boost are all kept, so the treatment still reads as glass.
   Re-measured after this change: 4.9-5.6:1 over cream, 8-9:1 over the hero.
   Inline styles outrank a stylesheet, hence !important. */
nav[style] {
    background: linear-gradient(135deg, rgba(43, 3, 9, 0.88), rgba(43, 3, 9, 0.64)) !important;
}

/* --- motion layer ------------------------------------------------------ */
/* One easing for the whole system. Ease-out-quint: decisive, no bounce. */
:root {
    --ease: cubic-bezier(.22, 1, .36, 1);
    --ease-soft: cubic-bezier(.22, .61, .36, 1);
}

/* Press feedback. There was none anywhere on the site: every control had a
   hover state and then nothing happened when you actually pressed it. 90ms,
   under the ~80-100ms threshold where feedback still reads as instant. */
a[class*="lift"],
button,
.fac,
[data-action] {
    -webkit-tap-highlight-color: transparent;
}

a[class*="lift"]:active,
button[data-action]:active {
    transform: translateY(-1px) scale(.985);
    transition-duration: .09s;
}

/* List choreography. Staggering siblings within one list is legitimate;
   the same fade on every section is the AI tell. Only real lists get this,
   and the stagger is capped so a 35-name roster does not take 2 seconds. */
/* ------------------------------------------------------------- scrollbar
   Styling ::-webkit-scrollbar turns it into a CLASSIC, space-taking scrollbar.
   That makes it a layout decision, not a cosmetic one: a page that styles it
   is 11px narrower than a page that does not, and every centred thing on it 
   including the fixed masthead's link row — sits 11px further left.

   These rules were pasted into seven page <style> blocks and missed by the
   eighth, so past-editions rendered with a 0px overlay scrollbar and its whole
   nav shifted right by 11px against every other page. The home page was a
   third value again at 10px. One declaration here, one width everywhere. */
::-webkit-scrollbar {
    width: 11px;
}

::-webkit-scrollbar-track {
    background: #f4e7d8;
}

::-webkit-scrollbar-thumb {
    background: linear-gradient(#E80202, #E80202);
    border-radius: 999px;
    border: 2px solid #f4e7d8;
}

/* ------------------------------------------------------------------- base
   These were duplicated verbatim in every page's own <style> block, which
   means a page that does not repeat them gets none of them: the new
   past-editions page fell back to the browser's default serif for its body,
   nav and footer, and every link kept its underline. Same failure as the
   riseIn keyframes below. Defining them once here makes them structural
   rather than something each new page has to remember.
   A page may still override any of it — page CSS is emitted after this. */
body {
    margin: 0;
    font-family: 'Public Sans', 'Public Sans fallback', system-ui, sans-serif;
    color: #4a2b30;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

a {
    color: #E80202;
    text-decoration: none;
    transition: color .2s ease;
}

a:hover {
    color: #E80202;
}

::selection {
    background: #E80202;
    color: #fff;
}

/* The keyframes live here, not only in each page's own <style>. `.stagger > *`
   sets opacity:0 and relies on `riseIn` to bring it back  and an animation
   naming a keyframe that does not exist on that page simply does nothing, so
   the opacity:0 sticks and the content ships invisible. That is how the new
   past-editions page shipped 29 photographs nobody could see. Defining them in
   the shared sheet means a page cannot forget them. */
@keyframes riseIn {
    from {
        opacity: 0;
        transform: translateY(12px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.stagger>* {
    opacity: 0;
    animation: riseIn .55s var(--ease) forwards;
    animation-delay: calc(var(--i, 0) * 45ms);
}

.stagger.in>* {
    opacity: 0;
}

/* ...but a wall of faces is not a list of sentences. The same 12px rise on
   every revealed thing — 34 elements on the home page, 44 on the faculty page 
   is the uniform reflex the brand register calls a tell. Portraits settle in
   place instead: a shallow scale, no vertical travel, a slightly longer curve
   so 44 faces resolve calmly rather than marching up the screen. */
/* The `to` is not optional. `.stagger > *` sets opacity:0, and with
   fill-mode forwards an animation with only a `from` settles on the
   element's own style — i.e. invisible. Written without it, 42 portraits
   finished their entrance and stayed blank. */
@keyframes plateIn {
    from {
        opacity: 0;
        transform: scale(.965);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.stagger.roster>* {
    animation-name: plateIn;
    animation-duration: .7s;
    animation-timing-function: cubic-bezier(.22, 1, .36, 1);
}

/* Hover: everything that responds does so on the same curve and duration, so
   the page feels like one material rather than a collection of components. */
a,
button,
.fac__plate img,
.comp,
.lift {
    transition-timing-function: var(--ease-soft);
}

/* The mobile menu appeared instantly — no transition at all. It now fades and
   settles 8px. `hidden` cannot be transitioned, so js/app.js drives an
   `is-open` class and only sets `hidden` after the exit finishes. */
[data-panel="menu"] {
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity .26s var(--ease-soft), transform .26s var(--ease-soft);
}

[data-panel="menu"].is-open {
    opacity: 1;
    transform: none;
}

/* The registration thank-you swapped in instantly. Exit is ~75% of entrance. */
[data-panel="register-success"] {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity .34s var(--ease), transform .34s var(--ease);
}

[data-panel="register-success"].is-open {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .stagger>* {
        opacity: 1 !important;
        animation: none !important;
    }

    [data-panel] {
        transition: none !important;
        transform: none !important;
    }

    a[class*="lift"]:active,
    button[data-action]:active {
        transform: none;
    }
}

/* --- notch and rounded corners ------------------------------------------ */
/* `env()` only resolves once the viewport opts in with viewport-fit=cover.
   Without both, content slides under the notch and the home indicator in
   landscape on every modern iPhone. `max()` keeps the existing gutter on
   devices that report no inset. */
body {
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
}

nav,
[data-panel="menu"] {
    padding-left: max(0px, env(safe-area-inset-left));
    padding-right: max(0px, env(safe-area-inset-right));
}

footer {
    padding-bottom: max(28px, env(safe-area-inset-bottom));
}

/* --- skip link -------------------------------------------------------- */
/* The page opens with a fixed nav and a full-height hero; without this a
   keyboard user tabs the whole navigation before reaching any content. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 200;
}

.skip-link:focus {
    left: 12px;
    top: 12px;
    width: auto;
    height: auto;
    padding: 12px 20px;
    background: #E80202;
    color: #E80202;
    border-radius: 0 0 14px 14px;
    font-weight: 700;
    font-size: 0.9375rem;
}

/* --- focus visibility -------------------------------------------------- */
/* The design styles hover but never focus. Every control here sits on either
   a crimson or a cream ground, so the ring is drawn in gold with a dark
   halo  legible on both without a second rule. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
#main:focus,
[data-panel="register-success"]:focus {
    outline: 3px solid #E80202;
    outline-offset: 3px;
    border-radius: 6px;
}

/* The form controls were the only interactive elements without the ring: the
   design gave them a 1px border tint that measured 1.43:1 against the ground,
   where WCAG 1.4.11 needs 3:1. The inline `outline:none` on each control is
   what suppressed it, so this has to out-specify that.
   The two programmatic targets are named individually rather than matched as
   `[tabindex="-1"]:focus`. That blanket selector also caught the complication
   map's points: they use a roving tabindex, so every point except the current
   one carries `tabindex="-1"`, and `:focus` matches on a plain MOUSE click
   where `:focus-visible` does not. Clicking a dot therefore drew a 3px ring
   with a 3px offset around the whole point — a ~136x33 gold rectangle beside
   the dot, including its invisible label box, which stayed after the pointer
   left. That is what read as the dot moving on click. The points are
   `<button>`s, so `button:focus-visible` still rings them for the keyboard.
   The thank-you panel takes focus programmatically and was otherwise painting
   Chrome's default BLUE ring on drenched crimson — the only fourth hue on the
   site, on the last screen a converting reader sees. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: 3px solid #E80202 !important;
    outline-offset: 2px !important;
}

/* An iframe is a tab stop the moment it exists, and it does not inherit the
   rule above. Once the map is loaded a keyboard reader lands on it with no
   indication of where they are.
   `:focus` is the wrong selector and silently never matches: focusing an
   iframe moves focus INTO its browsing context, so the element itself is not
   `:focus` even while `document.activeElement` returns it. `:focus-within`
   does match, cross-origin included. Inset so the ring is not clipped by the
   rounded card the frame sits in. */
iframe:focus,
iframe:focus-within,
iframe:focus-visible {
    outline: 3px solid #E80202;
    outline-offset: -3px;
}

/* --- map facade --------------------------------------------------------- */
/* The Google embed was 822KB across 37 third-party requests on contact.php —
   91% of the page weight, loaded before the reader had scrolled, for a page
   whose own content is 62KB. It also sets Google's cookies on arrival.
   The facade carries the address and a maps link with no third-party contact
   at all, and loads the embed only when someone asks for it. */
/* ------------------------------------------------------------ back to top
   Sits clear of the safe-area inset so it is not under a phone's home bar. It
   is `hidden` in the markup and revealed by script once the page has been
   scrolled, so with scripting off it never appears rather than appearing and
   doing nothing. */
.totop {
    position: fixed;
    z-index: 90;
    right: max(18px, env(safe-area-inset-right));
    bottom: max(18px, env(safe-area-inset-bottom));
    width: 48px;
    height: 48px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid rgba(255, 255, 255, .34);
    background: #E80202;
    color: white;
    box-shadow: 0 10px 26px rgba(58, 4, 16, .34);
    opacity: 0;
    translate: 0 12px;
    transition: opacity .35s var(--ease-soft), translate .45s var(--ease),
        box-shadow .3s var(--ease-soft);
}

.totop[hidden] {
    display: none;
}

.totop.is-on {
    opacity: 1;
    translate: 0 0;
}

.totop:hover {
    box-shadow: 0 16px 34px rgba(58, 4, 16, .44);
}

/* The icon set only has a right-pointing arrow; this is the same glyph turned
   to face up rather than a second nearly-identical path in the defs. */
.totop svg {
    width: 20px;
    height: 20px;
    rotate: -90deg;
}

@media (prefers-reduced-motion: reduce) {
    .totop {
        transition: opacity .2s var(--ease-soft);
        translate: none;
    }
}

/* ------------------------------------------------------------- arrow links
   Every link that ends in the arrow glyph slides it on hover. This was only
   wired up on the explore cards, so "Meet the faculty", "Read more about the
   course", "Venue & contact details" and every `.lift` button sat inert while
   the four cards beside them moved.

   `:has()` rather than a class on each link: the rule then applies to any link
   that ends in an arrow, including ones added later, and there is no markup to
   remember. `translate` is an independent property, so it cannot be clobbered
   by an entrance animation that fills `transform`  which is how `.lift` and
   the card hovers shipped dead. It is also not a layout property, so the row
   the link sits in does not reflow.

   `.ways__go` keeps its own rule; the values agree, so the two never disagree
   about how far the arrow travels. */
a:has(svg use[href="#ic-arrow"]) svg {
    transition: translate .45s var(--ease);
}

a:has(svg use[href="#ic-arrow"]):hover svg,
a:has(svg use[href="#ic-arrow"]):focus-visible svg {
    translate: 4px 0;
}

@media (prefers-reduced-motion: reduce) {
    a:has(svg use[href="#ic-arrow"]) svg {
        transition: none;
    }

    a:has(svg use[href="#ic-arrow"]):hover svg,
    a:has(svg use[href="#ic-arrow"]):focus-visible svg {
        translate: none;
    }
}

/* ------------------------------------------------------------------ footer
   The footer links carried a fixed alpha and no hover state, so nothing on the
   surface said they were live until the cursor changed. They now lift to full
   white, which also puts them clear of the skyline behind them. */
.foot-link {
    color: rgba(255, 255, 255, .72);
    transition: color .25s var(--ease-soft);
}

.foot-link:hover,
.foot-link:focus-visible {
    color: #fff;
}

.foot-link--sm {
    color: rgba(255, 255, 255, .78);
}

.foot-link--sm:hover,
.foot-link--sm:focus-visible {
    color: #fff;
}

/* inline-flex, not inline-block: the build appends an external-link marker to
   every off-site link, and against a block image it dropped onto its own line
   under the logo. */
.foot-pco {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    transition: opacity .25s var(--ease-soft);
    opacity: .9;
}

.foot-pco:hover,
.foot-pco:focus-visible {
    opacity: 1;
}

.foot-pco img {
    width: 150px;
    height: auto;
    display: block;
}

/* ---------------------------------------------------------------- skyline
   Chennai, as a watermark along the bottom of every dark header and the
   footer. tools/make_skyline.py strips the client's white card to alpha,
   colours the ink gold and bakes the upward fade into the alpha channel, so
   this is an ordinary background image.

   It began as a CSS `mask-image`, which is tidier  the tint would come from
   the page — but a mask is a cross-origin local read, so it is blocked when
   the built HTML is opened straight off disk. Reviewing that way is exactly
   what the client does, and the skyline was simply invisible to them. A
   background image has no such restriction and needs no vendor prefixes.

   `aspect-ratio` matches the artwork exactly (2200x421); the band is anchored
   to the bottom, so any mismatch would crop the temple's tower first. It is
   decorative, so it is aria-hidden and never a tab stop. */
.skyline {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    width: 100%;
    aspect-ratio: 2200 / 421;
    pointer-events: none;
    background-image: url(../assets/skyline-chennai.png);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    background-position: bottom center;
    opacity: .30;
}

/* The footer ground is nearly black, so the same strength that reads as a
   watermark on the hero's crimson disappears against it. */
.skyline--footer {
    opacity: .24;
}

/* --------------------------------------------------------------- masthead
   Solid brand material rather than glass. The bar is the crimson the site is
   built from, so the header belongs to the page instead of floating over it,
   and there is no backdrop-filter repainting on every scroll frame. */
.mast {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 100;
    background: black;
    border-bottom: 1px solid rgba(247, 217, 138, .26);
    box-shadow: 0 6px 28px rgba(28, 0, 6, .34);
}

.mast__strip {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    height: 32px;
    overflow: hidden;
    border-bottom: 1px solid rgba(255, 255, 255, .08);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: #E80202;
    transition: height .45s cubic-bezier(.22, 1, .36, 1), opacity .3s ease;
}

.mast__sep {
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: rgba(228, 184, 74, .6);
}

.mast__where {
    color: rgba(255, 255, 255, .66);
}

/* Compacted state: the dates have been read by the time you are scrolling. */
.mast.is-scrolled .mast__strip {
    height: 0;
    opacity: 0;
    border-bottom-color: transparent;
}

.mast__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 11px clamp(16px, 4vw, 52px);
    transition: padding .45s cubic-bezier(.22, 1, .36, 1);
}

.mast.is-scrolled .mast__bar {
    padding-top: 8px;
    padding-bottom: 8px;
}

.mast__brand {
    display: flex;
    align-items: center;
    flex: none;
    padding: 6px 11px;
    border-radius: 14px;
    background: linear-gradient(135deg, rgba(255, 255, 255, .95), rgba(255, 255, 255, .78));
}

.mast__brand img {
    height: 31px;
    width: auto;
    display: block;
}

.mast__links {
    display: flex;
    align-items: center;
    gap: 30px;
}

/* The link underline is drawn from the centre out, and the active page keeps
   it drawn. transform only — animating width would reflow the whole row. */
.mast__link {
    position: relative;
    color: rgba(255, 255, 255, .86);
    font-weight: 600;
    font-size: 0.90625rem;
    letter-spacing: .01em;
    padding: 6px 0;
}

.mast__link::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1.5px;
    background: #E80202;
    transform: scaleX(0);
    transition: transform .4s var(--ease);
}

.mast__link:hover {
    color: #fff;
}

.mast__link:hover::after {
    transform: scaleX(1);
}

/* While the pointer (or focus) is anywhere in the item — including down inside
   the open panel — the parent stays lit and its rule stays drawn. Otherwise the
   heading you opened goes dark the moment you move onto its own sub-items and
   nothing on screen says which section you are in. */
.mast__item:hover>.mast__link,
.mast__item:focus-within>.mast__link {
    color: #E80202;
}

.mast__item:hover>.mast__link::after,
.mast__item:focus-within>.mast__link::after {
    transform: scaleX(1);
}

/* The active item keeps the SAME weight as the rest. At 700 against 600 its
   label is wider, so every other link in the row moved as you navigated 
   up to 2.2px of drift between pages, which reads as the whole nav twitching.
   Gold plus the drawn rule already say which page you are on; the weight
   change only ever cost layout stability. */
.mast__link.is-on {
    color: #E80202;
}

.mast__link.is-on::after {
    transform: scaleX(1);
}

/* Deliberately not a pill: a squared plate reads as masthead furniture rather
   than a floating SaaS button, and it distinguishes the persistent header CTA
   from the in-page ones. */
/* Section menu on the faculty item. Hover OR focus-within, so it is reachable
   with a keyboard; a hover-only menu simply does not exist for a tab user.
   Opacity + visibility rather than display:none, so the open and close can be
   eased and the links stay out of the tab order while it is shut. */
/* The item's own hover area is extended downwards over the gap between the
   link and the panel. Without it the pointer crosses 16px of dead space on the
   way to the menu, :hover is lost, and the menu shuts before you arrive —
   which is exactly what happened with only a 14px bridge on the panel. */
.mast__item {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding-bottom: 18px;
    margin-bottom: -18px;
}

.mast__caret {
    display: none;
    color: rgba(255, 255, 255, .62);
    line-height: 0;
    transition: rotate .3s var(--ease), color .3s var(--ease-soft);
}

.mast__caret svg {
    width: 13px;
    height: 13px;
    display: block;
}

.mast__item[data-has-menu="1"] .mast__caret {
    display: block;
}

.mast__item:hover .mast__caret,
.mast__item:focus-within .mast__caret {
    rotate: 180deg;
    color: #E80202;
}

.mast__menu {
    position: absolute;
    top: 100%;
    left: 50%;
    translate: -50% 0;
    margin-top: 12px;
    min-width: 17rem;
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    border-radius: 14px;
    background: #fbf3e8;
    border: 1px solid rgba(184, 17, 42, .16);
    box-shadow: 0 18px 44px rgba(28, 0, 6, .34);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .26s var(--ease-soft), translate .26s var(--ease), visibility .26s;
}

.mast__item[data-has-menu=""] .mast__menu {
    display: none;
}

.mast__item:hover .mast__menu,
.mast__item:focus-within .mast__menu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    translate: -50% 4px;
}

/* The gap between the link and the panel would drop the hover halfway across
   it; this bridges it so the menu does not flicker shut on the way down. */
.mast__menu::before {
    content: '';
    position: absolute;
    left: -12px;
    right: -12px;
    top: -22px;
    height: 22px;
}

.mast__menu-link {
    padding: 9px 12px;
    border-radius: 9px;
    color: #5a3a3f;
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.3;
}

.mast__menu-link:hover,
.mast__menu-link:focus-visible {
    background: rgba(184, 17, 42, .09);
    color: #E80202;
}

/* Mobile menu sub-items. */
.mast__sub {
    color: rgba(255, 255, 255, .72);
    font-size: 1rem;
    font-weight: 600;
    padding: 9px 0 9px 18px;
    border-left: 1px solid rgba(247, 217, 138, .3);
    margin-left: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .mast__menu {
        transition: none;
    }
}

.mast__cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 11px 20px;
    border-radius: 10px;
    font-weight: 700;
    font-size: 0.90625rem;
    color: #ffffff;
    background: #e80202;
    box-shadow: 0 6px 18px rgba(228, 184, 74, .32);
    transition: transform .4s var(--ease), box-shadow .4s var(--ease-soft);
}

.mast__cta svg {
    width: 16px;
    height: 16px;
}

.mast__cta:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 26px rgba(228, 184, 74, .42);
}

.mast__burger {
    display: none;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 12px;
    cursor: pointer;
    border: 1px solid rgba(255, 255, 255, .24);
    background: rgba(255, 255, 255, .1);
    color: #fff;
}

@media (max-width: 860px) {
    .mast__strip {
        font-size: 0.6875rem;
        letter-spacing: .08em;
        gap: 10px;
    }
}

@media (prefers-reduced-motion: reduce) {

    .mast__strip,
    .mast__bar,
    .mast__link::after,
    .mast__cta {
        transition: none;
    }
}

/* ------------------------------------------------------------------ cards
   One interaction for every card family on the site: it rises on hover and
   falls back, and the surface trades fill for outline as it does. Two skins
   because the site has two materials — paper cards on the light sections,
   outline cards on the crimson ones.

   Only transform, box-shadow, background and border move; no layout property
   is animated, so a grid of twenty of these does not reflow on hover. The
   fall is slower than the rise, which is what stops it feeling springy. */
.card {
    position: relative;
    border-radius: 24px;
    transition: translate .5s cubic-bezier(.22, 1, .36, 1),
        box-shadow .5s cubic-bezier(.22, 1, .36, 1),
        background-color .4s ease, border-color .4s ease;
}

.card--paper {
    background: linear-gradient(155deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, .6));
    border: 1px solid rgba(255, 255, 255, .85);
    box-shadow: 0 16px 42px rgba(122, 5, 18, .1);
}

.card--paper:hover,
.card--paper:focus-within {
    translate: 0 -6px;
    border-color: rgba(184, 17, 42, .34);
    box-shadow: 0 28px 62px rgba(122, 5, 18, .17);
}

.card--outline {
    background: rgba(255, 255, 255, .045);
    border: 1px solid rgba(255, 255, 255, .17);
    box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}

.card--outline:hover,
.card--outline:focus-within {
    translate: 0 -6px;
    background: rgba(255, 255, 255, .105);
    border-color: rgba(247, 217, 138, .46);
    box-shadow: 0 26px 56px rgba(20, 0, 4, .34);
}

/* The rise is the whole effect, so on reduced motion the surface still
   answers — it just does not move. */
@media (prefers-reduced-motion: reduce) {
    .card {
        transition: background-color .2s ease, border-color .2s ease;
    }

    .card--paper:hover,
    .card--paper:focus-within,
    .card--outline:hover,
    .card--outline:focus-within {
        translate: none;
    }
}

/* An "announced soon" panel: copy left at a readable measure, status right. */
.note {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: clamp(20px, 3vw, 48px);
    flex-wrap: wrap;
}

.note__text {
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-size: clamp(1.125rem, 1.7vw, 1.375rem);
    line-height: 1.6;
    color: #4a2b30;
    margin: 0;
    flex: 1 1 28rem;
    min-width: 0;
    max-width: 46rem;
    text-wrap: pretty;
}

.note .chip-soon {
    flex: none;
    align-self: center;
}

@media (max-width: 700px) {
    .note {
        align-items: flex-start;
    }

    .note .chip-soon {
        align-self: flex-start;
    }
}

/* Pending status. Reads as a state, not a button, so nobody clicks it. */
.chip-soon {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: .01em;
    color: #E80202;
    background: rgba(184, 17, 42, .09);
    border: 1px solid rgba(184, 17, 42, .22);
}

.chip-soon::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #E80202;
    flex: none;
}

.mapf {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    padding: 28px;
    text-align: center;
}

.mapf__where {
    margin: 0;
    font-family: 'Spectral', 'Spectral fallback', Georgia, serif;
    font-size: 1.25rem;
    line-height: 1.4;
    color: #3a0410;
}

.mapf__where span {
    display: block;
    margin-top: 6px;
    font-family: 'Public Sans',
        'Public Sans fallback', system-ui, sans-serif;
    font-size: 0.9375rem;
    color: #5a3a3f;
}

.mapf__acts {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    justify-content: center;
    align-items: center;
}

.mapf__load {
    padding: 12px 22px;
    border-radius: 999px;
    border: 0;
    cursor: pointer;
    background: #E80202;
    color: #fff;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 700;
    transition: background .2s var(--ease-soft, ease);
}

.mapf__load:hover {
    background: #E80202;
}

.mapf__acts a {
    font-size: 0.9375rem;
    font-weight: 600;
}

.mapf__note {
    margin: 0;
    max-width: 34ch;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: #7a5c44;
}

.mapf iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* The always-on embed. It fills the card it sits in; the overlay link sits
   above it so "Open in Google Maps" survives the iframe swallowing clicks. */
.mapf__frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Top-right, not bottom-left: Google's own attribution and logo sit along the
   bottom edge of the embed and must not be covered. */
.mapf__open {
    position: absolute;
    right: 14px;
    top: 14px;
    z-index: 2;
    padding: 10px 18px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .96);
    color: #E80202;
    font-size: 0.875rem;
    font-weight: 700;
    box-shadow: 0 6px 18px rgba(58, 4, 16, .22);
}

.mapf__open:hover {
    background: #fff;
}

@media (pointer: coarse) {
    .mapf__open {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }
}

@media (pointer: coarse) {
    .mapf__load {
        min-height: 44px;
    }
}

/* --- metric-matched font fallbacks -------------------------------------- */
/* The entire layout shift on this site was the font swap. Measured on a
   throttled connection: CLS 0.1397 with fonts, 0 with fonts blocked, and
   unchanged with images blocked. Spectral sets a line 152px tall where Georgia
   sets 114px at the same size, so every block below the h1 moved when the web
   font arrived.
   These faces re-describe the local fallbacks with Spectral's and Public
   Sans's own metrics, so the fallback occupies the same space and the swap
   costs nothing. Values were tuned against measured block heights, not
   guessed. */
@font-face {
    font-family: 'Spectral fallback';
    src: local('Georgia'), local('Times New Roman'), local('Times');
    size-adjust: 100.25%;
    ascent-override: 107.9%;
    descent-override: 43.7%;
    line-gap-override: 0%;
}

@font-face {
    font-family: 'Public Sans fallback';
    src: local('Arial'), local('Helvetica Neue'), local('Helvetica');
    size-adjust: 104.31%;
    ascent-override: 88.5%;
    descent-override: 24.6%;
    line-gap-override: 0%;
}

/* --- touch input -------------------------------------------------------- */
/* Screen width does not tell you the input method: a laptop can have a touch
   screen and a tablet can have a mouse. `pointer: coarse` asks the real
   question. Targets that clear WCAG 2.5.8 via the spacing exception are still
   uncomfortable under a thumb, so on a coarse pointer they get the full 44px
   the platform guidelines ask for. */
@media (pointer: coarse) {
    .cmap__pt {
        min-height: 44px;
        padding-top: 10px;
        padding-bottom: 10px;
    }

    .faq__item summary {
        padding-top: 26px;
        padding-bottom: 26px;
    }

    footer a,
    .ways__go,
    a.navlink {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    .fac-find__clear {
        min-height: 40px;
    }
}

/* Hover is not available on touch, so nothing may depend on it. The map
   previews on hover and selects on tap; this makes the tap state visible
   rather than leaving touch users with only the pressed dot to go on. */
@media (hover: none) {
    .cmap__pt:active .cmap__dot {
        background: #E80202;
        transform: scale(1.2);
    }

    .lift:active {
        transform: translateY(-1px) scale(.99);
    }
}

/* --- words longer than their container ---------------------------------- */
/* At a large root font a single word can be wider than the column holding it —
   "complications" measured 376px inside a 288px box at a 24px root, and no
   amount of wrapping helps because there is nowhere to break. `break-word`
   only splits a word that genuinely does not fit, so ordinary prose is
   untouched; `hyphens: auto` makes the split read as typesetting rather than
   as damage. This is what the overflow at 320px was, not a layout bug. */
h1,
h2,
h3,
h4,
p,
li,
dd,
figcaption,
summary,
.fac__name,
.ways__title {
    overflow-wrap: break-word;
    hyphens: auto;
}

/* --- long unbreakable tokens -------------------------------------------- */
/* An email address, an Instagram handle and a URL have no spaces, so on a
   narrow card they cannot wrap and simply push out of it  taking the
   external-link glyph beside them past the viewport edge. Only applied where
   such tokens actually live, not globally, since `anywhere` would let ordinary
   prose break mid-word. */
a[href^="mailto:"],
a[href^="tel:"],
a[href*="instagram"],
.fac-find__count,
.faq__a {
    overflow-wrap: anywhere;
}

/* --- grid overflow ------------------------------------------------------ */
/* Grid and flex items default to `min-width: auto`, which means an item is
   never allowed to be narrower than its own content  so one wide child can
   push a track past its container and the whole page sideways. Invisible at a
   16px root; at a 24px root the contact map's track resolved to 434px inside a
   358px column. This is the standard fix and it costs nothing. */
/* Three fixed columns cannot hold their own content once the reader turns the
   font up: the tier cards fell to 154px wide holding text needing 257px. An
   auto-fit track with a rem minimum collapses the row when the type outgrows
   it, instead of squeezing. */
[data-three-col] {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)) !important;
}

[data-two-col]>*,
[data-three-col]>*,
[data-four-col]>*,
[data-hero-grid]>*,
[data-footer-grid]>*,
.sec-head>*,
.chair>*,
.ways>*,
.roster>*,
.comp>*,
.fac-find__row>*,
.hero-cd>* {
    min-width: 0;
}

/* --- links that leave the site ----------------------------------------- */
/* The cue is an icon AND text: an icon alone is not a signal for a reader who
   cannot see it, and text alone is noise for a reader who can. */
.ext-mark {
    width: .78em;
    height: .78em;
    margin-left: .34em;
    vertical-align: -.04em;
    opacity: .75;
    flex-shrink: 0;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* --- placeholders ------------------------------------------------------ */
/* Measured from rendered pixels at .45 alpha: 3.30 / 3.43 / 3.37:1 on the
   three fields that carry guidance, against a 4.5:1 floor. These are not
   decorative  they hold the only format hint each field has. */
::placeholder {
    color: rgba(255, 255, 255, .72) !important;
    opacity: 1;
}

/* --- text wrapping ----------------------------------------------------- */
h1,
h2,
h3 {
    text-wrap: balance;
}

p {
    text-wrap: pretty;
}

/* --- reduced motion ---------------------------------------------------- */
/* The design silences the marquee and the ECG trace. These are the rest:
   the floating atlas card, the pulsing hero bloom, the spinning dashed rings
   and the reveal animation all run indefinitely or move a long distance. */
@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        /* Smooth scrolling is precisely the vestibular trigger this query exists
       for, and it was still on: every in-page anchor and the skip link glided
       the whole document. It is not covered by animation or transition. */
        scroll-behavior: auto !important;
    }

    html {
        scroll-behavior: auto !important;
    }

    .reveal {
        opacity: 1 !important;
    }
}