/* North East Residences — "The Monograph"
   Blue and white only. Sharp corners. Hairlines only on tables and sheets.
   Order: tokens → fonts → base → type → layout → components → chrome → home → residence → a11y → motion → print */

/* ---------- 1. Tokens ---------- */
:root {
  --navy-900: #0A1530;
  --navy: #0F1F3D;
  --navy-700: #172B52;
  --blue: #2C55C7;
  --blue-soft: #B9C8EE;
  --ink: #141A24;
  --muted: #5F6877;
  --line: #E3E7EE;
  --line-strong: #6F7D98;
  --line-dark: rgba(255, 255, 255, .18);
  --mat-dark: rgba(255, 255, 255, .1);
  --on-navy-muted: rgba(255, 255, 255, .72);
  --surface: #EEF1F6;
  --white: #FFFFFF;
  --wa: #25D366;
  /* Mzansi accents: the flag's green, gold and red for the mark and a few deliberate touches, in two strengths:
     light on navy, deep on white (the deep ones pass 4.5:1 for text). */
  --green: #1F8F4E; --gold: #D99A06; --red: #C8242B;
  --green-ink: #156A3A; --gold-ink: #8A6100;
  --green-l: #46C46F; --gold-l: #F2B705; --red-l: #F0605A;
  --mk-g: var(--green-l); --mk-y: var(--gold-l); --mk-r: var(--red-l);

  --sp-1: 8px; --sp-2: 16px; --sp-3: 24px; --sp-4: 32px; --sp-5: 48px; --sp-6: 64px; --sp-7: 96px; --sp-8: 136px; --sp-9: 200px;
  --s-tight: var(--sp-5); --s: 64px; --s-tall: 88px;
  --max: 1320px; --gutter: 20px; --col-gap: 24px; --header: 64px;
  /* One numeral gutter for every numbered list (How to apply, Health & safety). */
  --num-col: 48px;

  --ease: cubic-bezier(.2, .7, .2, 1);
}
/* Desktop section spacing is 112px: between two white sections the gap is then 224px, generous without reading as dead space. */
@media (min-width: 900px) { :root { --gutter: 48px; --col-gap: 32px; --s-tight: 80px; --s: 112px; --s-tall: var(--sp-9); --header: 76px; --num-col: 64px; } }
@media (min-width: 1280px) { :root { --gutter: 60px; } }

/* ---------- 2. Fonts ---------- */
@font-face { font-family: "Newsreader Fallback"; src: local("Times New Roman"), local("Times"); size-adjust: 104%; ascent-override: 92%; descent-override: 24%; line-gap-override: 0%; }
@font-face { font-family: "Schibsted Fallback"; src: local("Helvetica Neue"), local("Arial"); size-adjust: 97%; }
:root { --serif: "Newsreader", "Newsreader Fallback", Georgia, serif; --sans: "Schibsted Grotesk", "Schibsted Fallback", system-ui, sans-serif; }

/* ---------- 3. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { font-size: 106.25%; -webkit-text-size-adjust: 100%; scroll-padding-top: 88px; scroll-padding-bottom: 92px; overflow-x: clip; }
@media (min-width: 900px) { html { font-size: 112.5%; } }
body { margin: 0; font: 400 1rem/1.6 var(--sans); color: var(--ink); background: var(--white); -webkit-font-smoothing: antialiased; overflow-x: clip; padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
@media (min-width: 1000px) { body { padding-bottom: 0; } }
img, svg, video, iframe { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; }
p { margin: 0; }
p, li, dd, .lead, .summary { text-wrap: pretty; }
h1, h2, h3, h4 { margin: 0; font-family: var(--serif); font-weight: 400; font-optical-sizing: auto; text-wrap: balance; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; cursor: pointer; border-radius: 0; text-align: inherit; }
table { border-collapse: collapse; width: 100%; }
caption { text-align: left; }
th, td { text-align: left; font-weight: inherit; padding: 0; }
dl, dd, figure { margin: 0; }
hr { border: 0; border-top: 1px solid var(--line); margin: 0; }
b, strong { font-weight: 600; }
[hidden] { display: none !important; }
::selection { background: var(--blue-soft); color: var(--navy); }

/* ---------- 4. Type scale ---------- */
h1 { font-size: 3.2rem; line-height: .96; letter-spacing: -.025em; }
h2 { font-size: 2.1rem; line-height: 1.02; letter-spacing: -.02em; }
h3 { font-size: 1.35rem; line-height: 1.1; letter-spacing: -.01em; }
.lead { font-size: 1.1rem; line-height: 1.5; max-width: 48ch; }
.prose { max-width: 62ch; }
.label { font: 600 .78rem/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; }
.num { font-variant-numeric: tabular-nums lining-nums; }
@media (min-width: 900px) {
  h1 { font-size: clamp(4.5rem, 8.4vw, 7.5rem); }
  /* 3rem: the longest heading line in the 4/12 heading column ("Unlimited Wi-Fi") fits at 1440 without a two-tier h2. */
  h2 { font-size: 3rem; }
  h3 { font-size: 1.6rem; }
  .lead { font-size: 1.15rem; }
}

/* ---------- 5. Layout ---------- */
.wrap { max-width: var(--max); margin-inline: auto; padding-left: max(var(--gutter), env(safe-area-inset-left, 0px)); padding-right: max(var(--gutter), env(safe-area-inset-right, 0px)); }
.section { padding-block: var(--s); }
.section.tall { padding-block: var(--s-tall); }
.band { background: var(--surface); }
.field { background: var(--navy); color: #fff; }
/* minmax(0, 1fr): the column may shrink below its min-content, so a long tab label never pushes the section past a 320px viewport. */
.split { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 40px; }
.split > .head .lead, .enq-grid > .head .lead { margin-top: 20px; }
.head .tag + h2 { margin-top: 12px; }
/* minmax(0, 1fr) below 900px: a single implicit auto track would let a 340px placeholder print (plus its mat) push
   the residence masthead figure past the phone gutter; the 12-column template takes over from 900px. */
.grid12 { display: grid; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) {
  .grid12 { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--col-gap); }
  /* One axis for every two-column section (Who can stay, How to apply, Wi-Fi, Safety, About, Enquire, Contact):
     a 4/12 heading column and an 8/12 content column, so the right edge never shifts while scrolling. */
  .split { grid-template-columns: 4fr 8fr; column-gap: 64px; align-items: start; }
  .split > .head.sticky { position: sticky; top: 120px; }
  .split > .head .lead, .enq-grid > .head .lead { margin-top: 24px; }
}

/* ---------- 6. Components ---------- */
/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 52px; padding: 0 26px; border: 1px solid var(--navy); background: var(--navy); color: #fff; font: 500 1rem/1.2 var(--sans); letter-spacing: .01em; text-align: center; text-decoration: none; cursor: pointer; border-radius: 0; transition: background-color .16s, color .16s, border-color .16s; }
.btn:hover { background: var(--navy-700); border-color: var(--navy-700); }
.btn.line { background: transparent; color: var(--navy); }
.btn.line:hover { background: var(--navy); color: #fff; }
.btn svg { width: 18px; height: 18px; flex: none; }
.wa { color: var(--wa); }
.field .btn, .plaque .btn, .menu .btn, footer .btn { background: #fff; border-color: #fff; color: var(--navy); }
.field .btn:hover, .plaque .btn:hover, .menu .btn:hover, footer .btn:hover { background: var(--blue-soft); border-color: var(--blue-soft); color: var(--navy); }
.field .btn.line, .plaque .btn.line, .menu .btn.line, footer .btn.line { background: transparent; border-color: #fff; color: #fff; }
.field .btn.line:hover, .plaque .btn.line:hover, .menu .btn.line:hover, footer .btn.line:hover { background: #fff; color: var(--navy); }

/* Typographic arrows: the glyph alone nudges on hover. The arrow carries its own word space, so it sits
   the same distance from its label inside flex rows (where whitespace collapses) as in running text. */
.arr { display: inline-block; margin-left: .28em; transition: transform .2s var(--ease); }
.arr-l { margin-left: 0; margin-right: .28em; }
/* Inside a button the flex gap alone spaces the arrow, so it sits as close to its label as in a text link. */
.btn .arr { margin-left: 0; }
a:hover .arr, a:focus-visible .arr, button:hover .arr, .res:hover .arr, .res:focus-visible .arr { transform: translateX(3px); }
a:hover .arr-l, a:focus-visible .arr-l { transform: translateX(-3px); }

/* Text links: inline-block, so a label that wraps keeps its arrow with the last word ("Plan →"); the 10px block
   padding on the 1.6 line-height makes a 47px tap target with the text where a centred 44px row would put it. */
.link { display: inline-block; padding-block: 10px; font-weight: 500; color: inherit; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; transition: color .16s; }
.link:hover, .link:focus-visible { color: var(--blue); }
.field .link, .plaque .link, footer .link, .menu .link { color: #fff; }
.field .link:hover, .plaque .link:hover, footer .link:hover, .menu .link:hover, .field .link:focus-visible { color: #fff; text-decoration-thickness: 2px; }
.link svg { display: inline-block; width: 20px; height: 20px; vertical-align: middle; margin-right: 8px; }
/* Inline links in a step (the portal address, "NSFAS — Lease"): 12px of vertical padding on an inline box is not laid out,
   so the line stays where it is, but the tap target grows from the 21px line to 45px. */
.step p a { color: var(--blue); text-underline-offset: 3px; padding-block: 12px; }

/* Drawn underline (nav, index names, directory names) */
.u { background-image: linear-gradient(currentColor, currentColor); background-repeat: no-repeat; background-size: 0 1px; background-position: 0 100%; padding-bottom: 2px; transition: background-size .2s var(--ease); }
a:hover > .u, a:focus-visible > .u, a:hover .u, a:focus-visible .u, .res:hover .u, .res:focus-visible .u { background-size: 100% 1px; }

/* Focus */
:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
.field :focus-visible, .plaque :focus-visible, .menu :focus-visible, footer :focus-visible, .topbar:not(.is-scrolled) :focus-visible { outline-color: #fff; }

/* Segmented control */
.seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 1px; background: var(--line-strong); border: 1px solid var(--line-strong); }
.seg button { min-width: 0; min-height: 48px; padding: 8px 12px; background: #fff; color: var(--ink); font: 500 1rem/1.2 var(--sans); text-align: center; transition: background-color .16s, color .16s; }
.seg button:hover { background: var(--surface); }
.seg button[aria-selected="true"], .seg button[aria-pressed="true"] { background: var(--navy); color: #fff; font-weight: 600; }
/* Keyboard focus is an inset ring, so the 1px-gap grid never clips it; white on the navy selected tab. */
.seg button:focus-visible { position: relative; z-index: 1; outline-offset: -3px; outline-color: var(--blue); }
.seg button[aria-selected="true"]:focus-visible, .seg button[aria-pressed="true"]:focus-visible { outline-color: #fff; }
.seg.grid2 { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 899px) {
  /* minmax(0, 1fr) lets "NSFAS — Apply" wrap to two lines inside its 48px button at 320px rather than widen the grid. */
  .seg { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .seg.odd button:first-child { grid-column: 1 / -1; }
}
/* The map modes carry campus names; below 1280px the residence column is too narrow for three abreast. */
@media (max-width: 1279px) {
  #map-modes { grid-auto-flow: row; grid-template-columns: 1fr 1fr; }
  #map-modes.odd button:first-child { grid-column: 1 / -1; }
}
/* On phones narrower than 440px even "To Howard College" wraps beside "The residence", so each mode is its own 48px row. */
@media (max-width: 439px) {
  #map-modes { grid-template-columns: minmax(0, 1fr); }
  #map-modes.odd button:first-child { grid-column: auto; }
}

/* Tables: 1px ink top rule, hairlines between rows */
.table { border-top: 1px solid var(--ink); }
.table caption { padding-bottom: 16px; color: var(--muted); }
.table th, .table td { padding: 14px 0; border-bottom: 1px solid var(--line); vertical-align: baseline; }
.table th { padding-right: 24px; }
.table td { text-align: right; white-space: nowrap; }
.table td.val { font: 500 1.35rem/1 var(--serif); font-variant-numeric: tabular-nums lining-nums; }
.table td.role { color: var(--muted); white-space: normal; }
/* Stacked rows (label, then value) where the serif value column would squeeze the labels onto several lines:
   the promise table on phones, and every table below 360px. */
@media (max-width: 599px) {
  .table.promises tr, .table.promises th, .table.promises td { display: block; }
  .table.promises th, .table.promises td { text-align: left; white-space: normal; }
  .table.promises th { padding: 12px 0 0; border-bottom: 0; }
  .table.promises td { padding: 6px 0 12px; }
}
@media (max-width: 359px) {
  .table tr, .table th, .table td { display: block; }
  .table th, .table td { text-align: left; white-space: normal; }
  .table th { padding: 12px 0 0; border-bottom: 0; }
  .table td { padding: 6px 0 12px; }
}

/* Figures. A placeholder crop is a small print mounted on a mat: shown at its native size, never
   upscaled, centred in a plate of fixed proportion so four plates make one even row. When the HD
   pipeline fills media.js the plate becomes a 16:9 navy mat that shows the WHOLE photograph
   (object-fit: contain): nothing is cropped away, and a 3:2 photo simply gets a little mat either side.
   The user asked for every picture to be seen in full, so no plate crops a photo. */
figure.photo { position: relative; }
figure.photo img { display: block; max-width: 100%; height: auto; border: 1px solid var(--line); transition: filter .25s; }
/* Only the placeholder crops are toned down; pipeline photographs are already graded by tools/enhance.swift and are shown as delivered. */
figure.photo.is-placeholder img { filter: saturate(.92) contrast(1.04); }
.plate { display: grid; place-items: center; width: 100%; padding: 12px; aspect-ratio: 7 / 4; background: var(--surface); transition: background-color .25s; }
.plate img { max-height: 100%; }
.field .plate, .plaque .plate { background: var(--mat-dark); }
.field .plate img, .plaque .plate img { border-color: var(--line-dark); }
.plate.has-media { position: relative; padding: 0; aspect-ratio: 16 / 9; background: var(--navy); }
/* Absolutely placed so the photo never stretches the plate: every plate in a row stays 16:9 whatever the photo's own ratio. */
.plate.has-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; border: 0; }
/* Browsers without aspect-ratio (Safari < 15): the padding hack keeps the 16:9 plate under its absolute photo, and the map gets a fixed height. */
@supports not (aspect-ratio: 1) {
  .plate.has-media { height: 0; padding-top: 56.25%; }
  .map { height: 300px; }
}
.plate.has-media.is-small { padding: 12px; background: var(--surface); }
.field .plate.has-media.is-small { background: var(--mat-dark); }
.plate.has-media.is-small img { position: static; width: auto; height: auto; object-fit: initial; border: 1px solid var(--line); }
@media (min-width: 700px) {
  .plate { padding: 16px; aspect-ratio: 5 / 3; }
  /* HD plates stay 16:9 at every width (the photos are 16:9 and 3:2), so the whole picture is always on screen. */
  .all-hd .plate.has-media, .gallery .plate.has-media { aspect-ratio: 16 / 9; }
  .plate.has-media.is-small { padding: 16px; aspect-ratio: 5 / 3; }
}
/* In the 4-up directory the plates (209–258px) are narrower than the 340px print, so the mat is dropped and the print
   uses the plate's full width: still a downscale, never an upscale. The masthead keeps its mat, where the print is shown at native size. */
@media (min-width: 1100px) {
  .cells .plate.is-placeholder { padding: 0; }
}

/* Notes, labels, placeholders */
.note { max-width: 58ch; color: var(--muted); }
.note b { color: var(--ink); }
.placeholder { color: var(--blue); border-bottom: 1px dashed var(--blue); }
.tag { color: var(--muted); }
.tag.c-green::before, .tag.c-gold::before, .tag.c-red::before { content: ""; display: inline-block; width: 9px; height: 9px; margin-right: 8px; border-radius: 50%; vertical-align: 1px; }
.tag.c-green::before { background: var(--green); } .tag.c-gold::before { background: var(--gold); } .tag.c-red::before { background: var(--red); }

/* ---------- 7. Chrome: header, menu, sticky bar, footer ---------- */
.skip { position: absolute; top: 8px; left: 8px; z-index: 100; padding: 12px 16px; background: #fff; color: var(--navy); font-weight: 500; text-decoration: none; transform: translateY(-300%); }
.skip:focus { transform: none; outline: 2px solid var(--blue); outline-offset: 2px; }

.topbar { position: fixed; top: 0; inset-inline: 0; min-height: var(--header); z-index: 50; color: #fff; transition: background-color .18s, color .18s, box-shadow .18s; }
.topbar .wrap { min-height: var(--header); display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.topbar.is-scrolled { background: #fff; color: var(--ink); box-shadow: 0 1px 0 var(--line); }
.logo { display: inline-flex; align-items: center; gap: 12px; min-height: 44px; color: inherit; text-decoration: none; }
.logo .mark { width: 44px; height: 31px; flex: none; }
.mark .m-g { fill: var(--mk-g); } .mark .m-y { fill: var(--mk-y); } .mark .m-r { fill: var(--mk-r); }
.topbar.is-scrolled { --mk-g: var(--green); --mk-y: var(--gold); --mk-r: var(--red); }   /* deep colours on the white bar */
.wm { display: grid; line-height: 1; }
.wm-1 { font: 500 1.45rem/1 var(--serif); letter-spacing: -.01em; }
.wm-2 { font: 600 .62rem/1 var(--sans); letter-spacing: .32em; text-transform: uppercase; margin-top: 5px; }
.nav { display: none; gap: 2px; }
.nav a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 11px; font: 500 1rem/1 var(--sans); letter-spacing: .01em; text-decoration: none; white-space: nowrap; }
.top-actions { display: flex; align-items: center; gap: 8px; }
.topbar .btn.line { display: none; min-height: 44px; padding: 0 20px; border-color: currentColor; color: inherit; background: transparent; }
.topbar:not(.is-scrolled) .btn.line:hover { background: #fff; color: var(--navy); border-color: #fff; }
.topbar.is-scrolled .btn.line:hover { background: var(--navy); color: #fff; border-color: var(--navy); }
.menu-btn { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; margin-right: -12px; font: 500 1rem/1 var(--sans); letter-spacing: .01em; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; }
/* The header's own breakpoints are in em (62.5em = 1000px, 80em = 1280px at the 16px base), so at 200% text zoom the nav
   hands back to the phone menu before it overflows; app.js initMenu matches the 80em query. Every other breakpoint stays in px. */
@media (min-width: 62.5em) { .topbar .btn.line { display: inline-flex; } }
@media (min-width: 80em) { .nav { display: flex; } .menu-btn { display: none; } }   /* 80em = 1280px at the 16px base: seven links need it; app.js initMenu matches this query */

/* Phone menu (dialog) */
.menu { position: fixed; inset: 0; z-index: 70; background: var(--navy-900); color: #fff; overflow-y: auto; -webkit-overflow-scrolling: touch; opacity: 0; transition: opacity .24s; }
.menu.is-open { opacity: 1; }
.menu-inner { display: flex; flex-direction: column; min-height: 100%; padding-top: 12px; padding-bottom: calc(32px + env(safe-area-inset-bottom)); }
.menu-top { display: flex; align-items: center; justify-content: space-between; min-height: 52px; }
.menu-close { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; margin-right: -12px; font: 500 1rem/1 var(--sans); text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; }
/* Sized so the two actions sit on the first screen of a 390×700 in-app browser (TikTok, Instagram). */
.menu-nav { display: grid; gap: 6px; margin-top: 32px; }
.menu-nav a { display: inline-flex; align-items: center; min-height: 44px; font: 400 1.9rem/1 var(--serif); letter-spacing: -.02em; text-decoration: none; justify-self: start; }
.menu-actions { display: grid; gap: 12px; margin-top: 32px; }
/* Short or very narrow screens (320×568) tighten the menu so both actions stay on the first screen:
   8 + 44 + 16 + 8×44 + 16 + 116 = 552px, with every link still a 44px row. */
@media (max-height: 640px), (max-width: 349px) {
  .menu-inner { padding-top: 8px; }
  .menu-top { min-height: 44px; }
  .menu-nav { gap: 0; margin-top: 16px; }
  .menu-nav a { font-size: 1.5rem; }
  .menu-actions { margin-top: 16px; }
}
.menu-label { color: var(--blue-soft); margin-top: 36px; }
.menu-res { display: grid; grid-template-columns: 1fr 1fr; margin-top: 8px; }
.menu-res a { display: flex; align-items: center; min-height: 44px; font-size: 1.15rem; font-weight: 500; text-decoration: none; }
body.menu-open .sticky-bar { display: none; }

/* Sticky bar (phones) */
/* An opaque plate: the border-top already separates it from the page, so nothing should ghost through under the labels. */
/* visibility: hidden while slid away, so its two buttons leave the tab order; the flip waits for the slide-out and is immediate on the way in. */
.sticky-bar { position: fixed; inset-inline: 0; bottom: 0; z-index: 60; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding-top: 12px; padding-right: max(var(--gutter), env(safe-area-inset-right, 0px)); padding-bottom: calc(12px + env(safe-area-inset-bottom)); padding-left: max(var(--gutter), env(safe-area-inset-left, 0px)); background: #fff; border-top: 1px solid var(--line); transform: translateY(100%); visibility: hidden; transition: transform .25s var(--ease), visibility 0s linear .25s; }
.sticky-bar.is-on { transform: none; visibility: visible; transition-delay: 0s, 0s; }
/* Both labels keep the 17px body size down to 320px: the width is recovered from padding, gap and the icon instead. */
.sticky-bar .btn { min-height: 48px; padding: 0 8px; gap: 6px; letter-spacing: 0; white-space: nowrap; }
.sticky-bar .btn.line { background: #fff; }
.sticky-bar .btn svg { width: 16px; height: 16px; }
@media (min-width: 1000px) { .sticky-bar { display: none; } html { scroll-padding-bottom: 0; } }

/* Footer */
footer { background: var(--navy-900); color: #fff; padding: 56px 0 28px; }
.foot { display: grid; gap: 32px; }
.foot-res { display: grid; grid-template-columns: 1fr 1fr; }
.foot .label { color: var(--blue-soft); margin-bottom: 8px; }
.foot .row { display: flex; align-items: center; min-height: 44px; text-decoration: none; }
.foot .row:hover, .foot .row:focus-visible { text-decoration: underline; text-underline-offset: 4px; }
.social { display: flex; gap: 4px; margin: 12px 0 0 -12px; }
.social a { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; }
.social svg { width: 20px; height: 20px; fill: #fff; }
/* The fine print belongs to the brand column: under the logo on desktop; on phones the column dissolves
   (display: contents) so the fine print can close the footer under a hairline, after the link columns. */
.foot-brand { display: contents; }
.fine { order: 10; margin-top: 8px; padding-top: 24px; border-top: 1px solid var(--line-dark); color: var(--on-navy-muted); max-width: 40ch; }
@media (min-width: 900px) {
  footer { padding: 72px 0 32px; }
  .foot { grid-template-columns: 1.4fr 1fr 1fr; gap: 64px; }
  .foot-res { grid-template-columns: 1fr; }
  .foot-brand { display: block; }
  .fine { margin-top: 32px; padding-top: 0; border-top: 0; }
}

/* ---------- 8. Home ---------- */
/* Hero: a three-slide opening (welcome over the traditional-wedding photo, the residences, life at res) with arrows,
   dots, swipe and a 7-second auto-advance (app.js initSlider). Slides stack in one grid cell, so the hero is as tall as
   its tallest slide and nothing below it moves when a slide changes. */
.hero { position: relative; overflow: hidden; padding: 0; display: flex; flex-direction: column; }
.hero > .slides { order: 1; } .hero > .slider-bar { order: 2; width: 100%; }   /* controls are first in the markup (reading order) but shown under the slides; width: 100% because a .wrap's auto margins would otherwise shrink it inside the flex column */
.tricolour { position: absolute; top: 0; left: 0; right: 0; height: 4px; z-index: 3; background: linear-gradient(90deg, var(--green-l) 0 33.34%, var(--gold-l) 33.34% 66.67%, var(--red-l) 66.67%); }
.slides { display: grid; touch-action: pan-y pinch-zoom; }
.slide { grid-area: 1 / 1; position: relative; display: flex; flex-direction: column; padding: 96px 0 32px; visibility: hidden; opacity: 0; transform: translateX(24px); transition: opacity .55s var(--ease), transform .55s var(--ease), visibility 0s .55s; }
.slide.is-active { visibility: visible; opacity: 1; transform: none; transition: opacity .55s var(--ease), transform .55s var(--ease); }
.slide > .wrap { position: relative; z-index: 2; display: flex; flex-direction: column; width: 100%; flex: 1; }
/* The shorter slides sit in the middle of the cell; slide 1 keeps its perks pinned to the foot. */
.slide-res > .wrap, .slide-events > .wrap { justify-content: center; }
.slide-welcome { overflow: hidden; }   /* the photo bleeds 16px past the slide so its blurred edge never shows; clip it here */
/* Slide 1 sits on the traditional-wedding photo, softened and washed in navy so the type stays crisp. */
.slide-bg { position: absolute; inset: -16px; z-index: 0; background: url("img/hero/wedding.jpg") center 45% / cover no-repeat; filter: blur(4px) saturate(1.12); }
.slide-welcome::after { content: ""; position: absolute; inset: 0; z-index: 1; background: linear-gradient(180deg, rgba(10, 21, 48, .60) 0%, rgba(15, 31, 61, .78) 55%, rgba(15, 31, 61, .96) 100%); }
.hero-top { position: relative; }
/* The ghosted mark is a desktop-only detail: on phones the headline stays clean. */
.hero-mark { display: none; }
/* The eyebrow is the site's key trust claim, so it is set at body size (17px on phones), with tracking; "residences · Durban"
   stays together, so on phones it breaks after "student" and ends on a full line. */
.hero .label { font-size: 1rem; letter-spacing: .1em; line-height: 1.4; color: var(--on-navy-muted); }
/* At 320px the tracking is eased so "NSFAS-accredited student" still fills one line; the hero buttons give up 10px of side padding. */
@media (max-width: 349px) { .hero .label { font-size: .94rem; letter-spacing: .06em; } .hero-actions .btn { padding-inline: 16px; } .slide h2 { font-size: 1.8rem; } }
.nb { white-space: nowrap; }
.hero .hero-h1 { margin-top: 20px; max-width: 12ch; font-size: 3.2rem; line-height: .96; letter-spacing: -.025em; }   /* the slide headline keeps the H1 scale; the page's real H1 is visually hidden */
.hero-actions { display: grid; gap: 12px; margin-top: 24px; justify-items: start; }
.hero-actions .btn { width: 100%; }
/* The perks strip: the four things every residence gives you, gold icons on navy, then who may stay. */
.perks-strip { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line-dark); }
.perks { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px 20px; }
@media (min-width: 360px) { .perks { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; } }
.perks li { display: flex; gap: 12px; align-items: flex-start; font: 500 1rem/1.35 var(--sans); }
.perks svg { flex: none; width: 24px; height: 24px; color: var(--gold-l); }
.open-to { margin-top: 24px; color: var(--on-navy-muted); font-size: .95rem; }
.open-to::before { content: ""; display: inline-block; width: 10px; height: 10px; margin-right: 10px; border-radius: 50%; vertical-align: middle; background: var(--gold-l); }
/* Slides 2 and 3 */
.slide h2 { margin-top: 14px; font-size: 2rem; line-height: 1.05; max-width: 16ch; }
.slide .lead { margin-top: 16px; max-width: 48ch; color: var(--on-navy-muted); }
.slide .link { margin-top: 24px; align-self: flex-start; color: #fff; }
.slide-res-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; margin-top: 28px; }
.mini { display: flex; flex-direction: column; color: inherit; text-decoration: none; }
.mini .plate.has-media { background: var(--mat-dark); }
.mini-name { margin-top: 12px; font: 400 1.35rem/1.1 var(--serif); }
.inst { display: block; margin-top: 6px; font: 600 .76rem/1.45 var(--sans); letter-spacing: .08em; text-transform: uppercase; color: var(--gold-l); }
.mini:hover .mini-name, .mini:focus-visible .mini-name { text-decoration: underline; text-underline-offset: 4px; }
/* In the small slide-2 cards a campus name may be wider than the card, so there it may break; the directory keeps names whole. */
.mini .inst { font-size: .7rem; letter-spacing: .06em; }
.mini .inst .nb { white-space: normal; }
.slide-events-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-top: 28px; }
.slide-events-grid .ev { margin: 0; }
/* Teaser tiles are square crops that lead to the whole photos in Events. */
.slide-events-grid .ev-photo { aspect-ratio: 1 / 1; }
.slide-events-grid .ev-photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; aspect-ratio: auto !important; }
.slide-events-grid figcaption { margin-top: 8px; display: block; font-size: .85rem; color: var(--on-navy-muted); }
.slide-events-grid figcaption b { font-weight: 500; }
.slide-events-grid figcaption span { display: none; }   /* the teaser shows titles only; the dots and blurbs live in the Events section */
/* Controls: arrows either side of the dots and a pause button, in one row under the slides. */
.slider-bar { position: relative; z-index: 2; padding-block: 8px 28px; }
.slider-controls { display: flex; align-items: center; gap: 8px; }
.slider-arrow { width: 44px; height: 44px; border: 1px solid rgba(255, 255, 255, .55); background: transparent; color: #fff; border-radius: 0; font: 400 1.2rem/1 var(--sans); cursor: pointer; transition: background-color .16s, color .16s; }
.slider-arrow:hover, .slider-arrow:focus-visible { background: #fff; color: var(--navy); }
.slider-dots { display: flex; }
.slider-dots button { width: 44px; height: 44px; padding: 0; border: 0; background: transparent; cursor: pointer; display: grid; place-items: center; }
.slider-dots span { display: block; width: 10px; height: 10px; border-radius: 50%; background: rgba(255, 255, 255, .42); transition: background-color .2s, transform .2s; }
.slider-dots [aria-selected="true"] span { background: var(--gold-l); transform: scale(1.3); box-shadow: 0 0 0 2px rgba(255, 255, 255, .9); }
.slider-pause { order: 4; margin-left: auto; margin-right: -8px; min-height: 44px; padding: 0 8px; border: 0; background: transparent; color: var(--on-navy-muted); font: 500 .9rem/1 var(--sans); text-decoration: underline; text-underline-offset: 4px; cursor: pointer; }
html:not(.js) .slider-bar { display: none; }   /* without scripts there is one static slide and nothing to control */
.slider-pause:hover, .slider-pause:focus-visible { color: #fff; }
@media (min-width: 600px) {
  .hero-actions { display: flex; align-items: center; gap: 24px; }
  .hero-actions .btn { width: auto; }
  .slider-controls { gap: 12px; }
}
@media (min-width: 900px) {
  .slide { min-height: min(calc(100svh - 96px), 820px); padding: 140px 0 40px; }
  .hero-top { padding-bottom: 56px; }
  .hero .hero-h1 { margin-top: 24px; font-size: clamp(4.5rem, 8.4vw, 7.5rem); }
  .hero-actions { margin-top: 40px; }
  .perks-strip { margin-top: auto; }
  .perks { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .slide h2 { font-size: clamp(2.4rem, 4vw, 3.4rem); }
  .slide-res-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 32px; margin-top: 40px; }
  .mini-name { font-size: 1.6rem; }
  .slide-events-grid { gap: 24px; max-width: 760px; margin-top: 40px; }
  .slider-bar { padding-bottom: 40px; }
  /* From 900px the arrows sit mid-height on each side of the opening (sasco.africa style), inside the gutter so they
     never cover the text column; the dots and Pause stay in the row below. The bar stops being the positioning box. */
  .hero > .slider-bar { position: static; }   /* so the arrows position against the hero itself */
  .slider-arrow { position: absolute; top: calc(50% - 44px); transform: translateY(-50%); z-index: 3; width: 40px; height: 40px; background: rgba(10, 21, 48, .45); border-color: rgba(255, 255, 255, .7); }
  .slider-arrow[data-dir="-1"] { left: 4px; }
  .slider-arrow[data-dir="1"] { right: 4px; }
}
@media (min-width: 1280px) {
  .slider-arrow { width: 44px; height: 44px; }
  .slider-arrow[data-dir="-1"] { left: 8px; }
  .slider-arrow[data-dir="1"] { right: 8px; }
}
@media (min-width: 1000px) {
  /* Whole mark inside the gutter, its base on the CTA row's baseline, clear of the 12ch headline at every width. */
  .hero-mark { display: block; position: absolute; right: 0; bottom: 56px; width: min(360px, 25vw); height: auto; fill: #fff; opacity: .13; pointer-events: none; }
  .hero-mark .m-g, .hero-mark .m-y, .hero-mark .m-r { fill: #fff; }   /* the watermark stays one colour */
}

/* The residences: a ruled sheet of mounted plates */
.sheet { margin-top: 32px; border-top: 1px solid var(--ink); }
.cells { display: grid; }
.res { display: flex; flex-direction: column; align-items: flex-start; padding: 22px 0 24px; border-bottom: 1px solid var(--line); color: inherit; text-decoration: none; }
/* The placeholder prints keep their mat on phones too, so the four plates share one size and edge (the crops are
   340px wide and would otherwise sit at slightly different widths and offsets in the column). */
.res .name { display: block; margin-top: 18px; font: 400 1.6rem/1.05 var(--serif); letter-spacing: -.01em; }
.res .inst { margin-top: 8px; color: var(--green-ink); }
.res .summary { display: block; margin-top: 8px; color: var(--muted); max-width: 28ch; }
.res .more { display: inline-block; margin-top: auto; padding-top: 18px; font-weight: 500; transition: color .16s; }
.res:hover .is-placeholder img, .res:focus-visible .is-placeholder img { filter: saturate(1) contrast(1.04); }
.res:hover .plate, .res:focus-visible .plate { background: var(--line); }
.res:hover .plate.has-media, .res:focus-visible .plate.has-media { background: var(--navy-700); }
.res:hover .more, .res:focus-visible .more { color: var(--blue); }
.pull-row { display: grid; margin-top: 40px; }
.pull { font: 400 1.35rem/1.3 var(--serif); letter-spacing: -.01em; max-width: 34ch; }
@media (min-width: 700px) {
  .sheet { margin-top: 48px; }
  /* The cells bleed into the gutter by their own padding, never past the viewport edge (the gutter is 20px until 900px). */
  .cells { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-inline: calc(-1 * min(28px, var(--gutter))); }
  .res { padding: 32px min(28px, var(--gutter)) 40px; border-bottom: 0; }
  .res:nth-child(even) { border-left: 1px solid var(--line); }
  .res:nth-child(n+3) { border-top: 1px solid var(--line); }
}
@media (min-width: 900px) {
  /* The same 4/12 + 8/12 geometry as .split, so the quote starts on the content axis every other section uses. */
  .pull-row { grid-template-columns: 4fr 8fr; column-gap: 64px; margin-top: 72px; }
  .pull { grid-column: 2; font-size: 1.6rem; }
}
@media (min-width: 1100px) {
  .cells { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .res:nth-child(n+3) { border-top: 0; }
  .res + .res { border-left: 1px solid var(--line); }
  .res .name { margin-top: 22px; font-size: 1.9rem; }
}

/* Who can stay: a ledger without rules */
/* Events: a loose two-column masonry (CSS columns) so each photo keeps its own shape, with air between them;
   captions carry a dot that cycles green, gold, red. */
.events-grid { columns: 1; column-gap: 32px; }
.ev { break-inside: avoid; margin: 0 0 36px; }
.ev:last-child { margin-bottom: 0; }
.ev-photo { background: var(--navy); }
.ev-photo img { display: block; width: 100%; height: auto; border: 0; }
/* On phones a tall portrait photo is capped at 60% of the screen, whole and centred, so the section does not run for screens. */
@media (max-width: 699px) { .ev-photo { background: transparent; } .ev-photo img { width: auto; max-width: 100%; max-height: 60svh; margin-inline: auto; } }
.ev figcaption, .ev-video figcaption { margin-top: 12px; display: grid; grid-template-columns: 14px minmax(0, 1fr); column-gap: 10px; align-items: baseline; }
.ev figcaption b, .ev-video figcaption b { font: 500 1.05rem/1.3 var(--sans); }
.ev figcaption span:not(.ev-dot), .ev-video figcaption span:not(.ev-dot) { grid-column: 2; margin-top: 4px; color: var(--muted); font-size: .95rem; }
.ev-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--green); transform: translateY(1px); }
.events-grid .ev:nth-of-type(3n+2) .ev-dot { background: var(--gold); }
.events-grid .ev:nth-of-type(3n) .ev-dot { background: var(--red); }
.ev-videos-title { margin-top: 40px; font-size: 1.5rem; }
.ev-videos { display: grid; gap: 24px; margin-top: 16px; }
.ev-video { margin: 0; }
.ev-video video { display: block; width: 100%; aspect-ratio: 16 / 9; background: var(--navy); object-fit: contain; }
.ev-videos .ev-video:nth-of-type(3n+2) .ev-dot { background: var(--gold); }
.ev-videos .ev-video:nth-of-type(3n) .ev-dot { background: var(--red); }
@media (min-width: 700px) { .events-grid { columns: 2; } .ev-videos { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.who-rows { display: grid; gap: 40px; }
.who-row h3 { margin-top: 12px; font-size: 1.5rem; }
.who-row p:not(.label) { margin-top: 12px; }
.who-row .link { margin-top: 6px; }
/* On phones the big row is a clear step below the 2.1rem section heading, not a rival to it. */
.who-row.big h3 { font-size: 1.7rem; letter-spacing: -.015em; line-height: 1.05; }
.who-row.big p:not(.label) { font-size: 1.1rem; line-height: 1.5; max-width: 48ch; }
.who-pair { display: grid; gap: 40px; }
@media (min-width: 900px) {
  /* The shared 8/12 content column keeps all three headings on one line at 1440 ("Bring proof of your bursary").
     The NSFAS heading shares the section heading's line, so it stays a clear step below the 3rem h2 (1.9rem, the residence-name size). */
  .who-rows { gap: 48px; }
  .who-row h3 { font-size: 1.6rem; }
  .who-row.big h3 { font-size: 1.9rem; }
  .who-row.big p:not(.label) { font-size: 1.15rem; }
  .who-pair { grid-template-columns: 1fr 1fr; gap: 48px; }
}

/* How to apply: numbered margin grid */
#apply { padding-bottom: 0; }
#apply .seg { margin-top: 32px; }
.steps { display: grid; gap: 24px; }
.step { display: grid; grid-template-columns: var(--num-col) 1fr; }
.step .n, .numbered .n { color: var(--blue); padding-top: 6px; }
.step > div > b { display: block; font-size: 1.1rem; line-height: 1.3; }
.step p { margin-top: 6px; color: var(--muted); max-width: 58ch; }
.step p b { color: var(--ink); }
[role="tabpanel"]:focus-visible { outline-offset: 12px; }
@media (min-width: 900px) {
  .steps { gap: 32px; }
  .step > div > b { font-size: 1.12rem; }
}

/* Plaque: the move-in codes on navy, with the two notes set beside them */
.plaque { margin-top: 48px; padding: 56px 0 64px; }
.plaque-grid { display: grid; row-gap: 40px; }
.codes { border-top: 1px solid var(--line-dark); }
.codes caption { padding-bottom: 24px; }
.codes caption .h { display: block; font: 400 1.6rem/1.1 var(--serif); letter-spacing: -.01em; }
.codes caption .sub { display: block; margin-top: 8px; color: var(--on-navy-muted); }
.codes th, .codes td { padding: 20px 0; border-bottom: 1px solid var(--line-dark); vertical-align: baseline; }
.codes th { padding-right: 16px; }
/* Like .cname: 4px of side padding pulled back by a negative margin gives "APS" a 44px tap target with its text on the column edge. */
.codes th a { display: inline-flex; align-items: center; min-width: 44px; min-height: 44px; padding-inline: 4px; margin-inline: -4px; font: 400 1.6rem/1.1 var(--serif); letter-spacing: -.01em; color: #fff; text-decoration: none; }
.codes th a:hover, .codes th a:focus-visible { text-decoration: underline; text-underline-offset: 6px; }
.codes td { text-align: right; font: 500 2.2rem/1 var(--serif); font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
.plaque .btn { margin-top: 32px; }
.plaque-notes { display: grid; gap: 20px; align-content: start; }
.plaque-notes .note { color: var(--on-navy-muted); max-width: 44ch; }
.plaque-notes .note b { color: #fff; }
@media (max-width: 899px) {
  /* Stacked rows on phones: name above its code. Explicit ARIA roles in the markup keep the table semantics. */
  .codes, .codes caption, .codes tbody, .codes tr, .codes th, .codes td { display: block; }
  .codes { border-top: 0; }
  .codes tbody { border-top: 1px solid var(--line-dark); }
  /* The name link is a 44px tap target, so the row padding above it is trimmed to keep the rhythm. */
  .codes tr { padding: 6px 0 18px; border-bottom: 1px solid var(--line-dark); }
  .codes th, .codes td { padding: 0; border: 0; text-align: left; }
  .codes th a { font-size: 1.15rem; font-family: var(--sans); font-weight: 500; letter-spacing: 0; }
  .codes td { margin-top: -4px; }
  /* Every primary button on a phone spans its column; the plaque's is no exception. */
  .plaque .btn { width: 100%; }
}
@media (min-width: 900px) {
  .plaque { margin-top: 96px; padding: 112px 0; }
  .plaque-grid { grid-template-columns: 8fr 4fr; column-gap: 64px; align-items: start; }
  /* The title outranks the row names (1.9rem) and stands up to the codes. */
  .codes caption .h { font-size: 2.1rem; }
  .codes th, .codes td { padding: 32px 0; }
  .codes th a { font-size: 1.9rem; }
  .codes td { font-size: clamp(2.6rem, 4.2vw, 4rem); }
  /* The notes start level with the first residence name: 150px clears the caption (2.1rem title, sub line, 24px), the
     row's 32px top padding and the name link's centring inside its 44px tap target. */
  .plaque-notes { align-self: start; padding-top: 150px; gap: 28px; }
}

/* Wi-Fi & transport: wide prose */
.prose-lead { display: grid; gap: 28px; font-size: 1.1rem; line-height: 1.55; max-width: 58ch; }
@media (min-width: 900px) { .prose-lead { gap: 32px; font-size: 1.15rem; } }

/* Health & safety: numbered two-column list (the numerals share .step's colour and gutter) */
.numbered li { display: grid; grid-template-columns: var(--num-col) 1fr; padding: 7px 0; break-inside: avoid; }
#safety .link { margin-top: 32px; }
@media (min-width: 900px) { .numbered { columns: 2; column-gap: 48px; } }

/* About: prose, then two tables */
#about-text { display: grid; gap: 20px; }
#about-text p:first-child { font-size: 1.1rem; line-height: 1.5; }
.about-tables { display: grid; gap: 40px; margin-top: 48px; }
#docs { margin-top: 16px; }
@media (min-width: 900px) {
  #about-text p:first-child { font-size: 1.15rem; }
  /* The same 4/12 + 8/12 column lines as the split above, so Leadership starts under the heading and the promise
     table under the prose. align-items: start keeps both tables on the same row rhythm (a stretched table spreads its rows). */
  .about-tables { grid-template-columns: 4fr 8fr; gap: 64px; margin-top: 72px; align-items: start; }
}

/* Enquire: boxed white fields on the surface band */
.enq-grid { display: grid; row-gap: 40px; }
.form-grid { display: grid; gap: 20px; }
.f label { display: block; font: 500 1rem/1.3 var(--sans); margin-bottom: 8px; }
.f input:not([type="checkbox"]), .f select, .f textarea { display: block; width: 100%; min-height: 52px; padding: 12px 16px; font: 400 1rem/1.4 var(--sans); color: var(--ink); background: #fff; border: 1px solid var(--line-strong); border-radius: 0; appearance: none; -webkit-appearance: none; }
.f select { padding-right: 48px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230F1F3D' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; background-size: 20px 20px; }
.f textarea { resize: vertical; min-height: 112px; }
.f input:focus, .f select:focus, .f textarea:focus { border-color: var(--navy); outline: none; }
.f input:focus-visible, .f select:focus-visible, .f textarea:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.f input[aria-invalid="true"], .f select[aria-invalid="true"], .f textarea[aria-invalid="true"] { border: 2px solid var(--blue); padding: 11px 15px; }
.f .help { margin-top: 8px; color: var(--muted); }
.f .err { margin-top: 8px; color: var(--blue); font-weight: 500; }
.f .consent { display: flex; gap: 14px; align-items: flex-start; cursor: pointer; font: 400 1rem/1.5 var(--sans); margin: 0; }
.consent input { width: 24px; height: 24px; margin: 2px 0 0; flex: none; accent-color: var(--navy); }
.consent.is-bad input { outline: 2px solid var(--blue); outline-offset: 2px; }
.form-actions { display: grid; gap: 12px; margin-top: 32px; }
.form-hint, .confirm { color: var(--muted); }
.confirm { max-width: 48ch; }
.confirm .link { color: var(--ink); }
input:-webkit-autofill, select:-webkit-autofill, textarea:-webkit-autofill { -webkit-box-shadow: 0 0 0 1000px #fff inset; -webkit-text-fill-color: var(--ink); }
@media (min-width: 700px) {
  .form-grid { grid-template-columns: 1fr 1fr; gap: 24px 32px; }
  .form-grid .full { grid-column: 1 / -1; }
  .form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; }
  .form-hint { flex-basis: 100%; }
}
@media (min-width: 900px) {
  .enq-grid { grid-template-columns: 4fr 8fr; column-gap: 64px; align-items: start; }
  .form-hint { flex-basis: auto; margin-left: 8px; }
}

/* Contact: Nik large on the left, the directory on the right */
#contact .split { margin-top: 40px; }
.contact-name { margin-top: 12px; font: 400 2.8rem/1 var(--serif); letter-spacing: -.02em; }
.contact-lines { display: grid; margin-top: 16px; justify-items: start; }
/* Icon rows (contact lines, Follow, manager links): the icon is aligned to the first line, so when a label wraps at 320px
   ("Instagram / @handle") it stays on the first line instead of floating between the two. The symmetric padding makes a
   one-line row exactly 44px with its text where a centred row would put it (1.6 is the body line-height). */
.contact-lines a, .follow-links a, .mgr-links a { display: inline-flex; align-items: flex-start; gap: 10px; padding-block: max(0px, calc((44px - 1.6em) / 2)); text-decoration: none; }
.contact-lines a:hover, .contact-lines a:focus-visible, .follow-links a:hover, .follow-links a:focus-visible, .mgr-links a:hover, .mgr-links a:focus-visible { text-decoration: underline; text-underline-offset: 4px; }
.contact-lines svg, .follow-links svg, .mgr-links svg { flex: none; width: 20px; height: 20px; margin-top: calc((1.6em - 20px) / 2); }
.contact-lines a { font-size: 1.15rem; }
.hours { margin-top: 8px; color: var(--muted); }
.follow { margin-top: 32px; }
.follow-links { display: grid; justify-items: start; margin-top: 4px; }
.follow-links svg { fill: currentColor; }
.dir-label { margin-bottom: 16px; color: var(--muted); }
.directory { border-top: 1px solid var(--ink); }
/* The name is a 44px tap target; its extra height is absorbed by the row's top padding. */
.dir-row { display: grid; gap: 10px; padding: 12px 0 20px; border-bottom: 1px solid var(--line); }
.dir-row .name a { display: inline-flex; align-items: center; min-height: 44px; font: 400 1.6rem/1.05 var(--serif); letter-spacing: -.01em; text-decoration: none; }
.mgr + .mgr { margin-top: 16px; }
.mgr .who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 10px; }
.mgr .who b { font-weight: 500; }
.mgr .role { color: var(--muted); }
.mgr-links { display: flex; flex-wrap: wrap; gap: 0 20px; }
.mgr-links a { gap: 8px; }
.mgr-links svg { width: 18px; height: 18px; margin-top: calc((1.6em - 18px) / 2); }
@media (min-width: 700px) {
  .dir-row { grid-template-columns: 1fr 1fr; gap: 24px; padding: 20px 0 28px; }
  .dir-row > div:last-child { padding-top: 8px; }
}
@media (min-width: 900px) {
  #contact .split { margin-top: 56px; }
  .contact-name { font-size: 4.2rem; }
}

/* ---------- 9. Residence page ---------- */
.res-head { position: relative; overflow: hidden; padding: 104px 0 48px; }
.res-head .back { display: inline-flex; align-items: center; min-height: 44px; color: #fff; font-weight: 500; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; }
.res-head .back:hover, .res-head .back:focus-visible { text-decoration-thickness: 2px; }
.res-head .label { margin-top: 24px; color: var(--blue-soft); }
.res-head h1 { margin-top: 12px; font-size: 3rem; }
.res-head .summary { margin-top: 20px; font-size: 1.25rem; line-height: 1.4; color: rgba(255, 255, 255, .8); max-width: 34ch; }
.res-head figure.photo { margin-top: 36px; }
@media (min-width: 900px) {
  .res-head { padding: 140px 0 64px; }
  .res-head .grid12 { align-items: end; }
  .res-head-text { grid-column: 1 / 8; }
  .res-head h1 { font-size: clamp(3.6rem, 6.5vw, 6rem); }
  .res-head figure.photo { grid-column: 8 / -1; margin-top: 0; }
  /* A placeholder print is mounted at its own size, in a 16px mat, set to the right edge of the masthead;
     an HD photograph (.has-media) fills the whole column. */
  .res-head .plate:not(.has-media) { width: auto; justify-self: end; aspect-ratio: auto; padding: 16px; }
  .res-head .plate:not(.has-media) img { max-height: none; }
}

/* Gallery (HD). The main photo now sits on the content grid: the navy mat runs edge to edge, but the photo is padded in to
   the page column (the same gutter and max width as .wrap). It keeps its own proportions and is never cropped; when it would
   be taller than 820px it is capped and centred on the mat, so the whole picture is always in view. */
.gallery-main { width: 100%; background: var(--navy); padding-inline: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))); }
.gallery-main img { display: block; width: 100%; height: auto; max-height: 820px; object-fit: contain; margin-inline: auto; border: 0; }
/* guardFigures() flags a hand-dropped small file; it is then shown as a mounted print, not stretched. */
.gallery-main.is-small { display: grid; place-items: center; padding: 16px; background: var(--surface); }
.gallery-main.is-small img { width: auto; height: auto; aspect-ratio: auto; max-height: 70vh; object-fit: initial; border: 1px solid var(--line); }
.thumbs { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 16px; }
.thumbs button { width: 96px; height: 54px; min-width: 44px; min-height: 44px; padding: 0; border: 1px solid var(--line); background: var(--navy); }
.thumbs button img { width: 100%; height: 100%; object-fit: contain; border: 0; }
/* The pressed state is an inset ring, so the outline stays free for :focus-visible. */
.thumbs button[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--blue); }   /* an outer ring: the inset one hid under the photo */
/* Phones: four thumbnails to a row, each a 16:9 cell of the column (the buttons keep their 44px minimum). */
@media (max-width: 699px) {
  .thumbs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
  .thumbs button { width: auto; height: auto; aspect-ratio: 16 / 9; }
}

/* Key facts */
.keyfacts-wrap { padding-block: 40px 0; }
.keyfacts { display: grid; border-top: 1px solid var(--ink); }
.keyfacts > div { padding: 20px 0; border-bottom: 1px solid var(--line); }
.keyfacts dt { color: var(--muted); }
.keyfacts dd { margin-top: 8px; }
.keyfacts .code { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
/* The code scales with narrow phones so it and the Copy button share one line down to 360px. */
.keyfacts .code b { font: 500 clamp(1.7rem, 9.2vw, 2.2rem)/1 var(--serif); font-variant-numeric: tabular-nums lining-nums; color: var(--blue); white-space: nowrap; }
.copy { display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px; border: 1px solid var(--line-strong); font: 500 1rem/1 var(--sans); color: var(--ink); background: #fff; transition: background-color .16s, color .16s; }
.copy:hover { background: var(--navy); color: #fff; border-color: var(--navy); }
@media (min-width: 900px) {
  /* One continuous rule under the row (per-cell rules would break at the column gaps). */
  .keyfacts { grid-template-columns: max-content minmax(0, 1fr) minmax(0, 1.5fr); column-gap: 40px; border-bottom: 1px solid var(--line); }
  .keyfacts > div { padding: 28px 0; border-bottom: 0; }
  .keyfacts .code b { font-size: 2.8rem; }
}
/* From 1200px the address column is the body's 5/12 column: its left edge is 7/12 of the row (less the 80px body gap)
   plus the two 40px gaps, exactly where the Stay card and the Health & safety column start beneath it. Between 900 and
   1199px the 1.5fr template above holds, where a fixed 5/12 column would squeeze the first two cells. */
@media (min-width: 1200px) {
  .keyfacts { grid-template-columns: max-content minmax(0, 1fr) calc((100% - 80px) * 5 / 12); }
}

/* Body: one column in DOM order on phones (the lists, the map, safety, then the Stay card closing the page);
   on desktop two spreads on the 7/5 grid, [lists | Stay card] over [map | safety], so no column is ever empty. */
.res-body { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 56px; padding-block: 24px var(--s); }
.aside-box { background: var(--surface); padding: 32px 24px; }
.aside-box h2 { font-size: 1.6rem; }
.aside-box .lines { display: grid; gap: 10px; margin-top: 16px; }
.aside-box .btn { width: 100%; margin-top: 12px; padding-inline: 16px; }
.aside-box .lines + .btn { margin-top: 24px; }
.aside-box hr { margin: 24px 0; border-color: var(--line-strong); opacity: .35; }
.aside-box .tag { margin-bottom: 6px; }
.aside-box .mgr + .mgr { margin-top: 12px; }
.aside-box .gen { margin-top: 20px; color: var(--muted); }
/* The inline phone number gets a 44px hit area without changing the line it sits on. */
.aside-box .gen a { display: inline-flex; align-items: center; min-height: 44px; margin-block: -12px; text-decoration: none; }
.aside-box .gen a:hover { text-decoration: underline; }
.aside-links { display: grid; justify-items: start; margin-top: 12px; }
.res-main > section + section { margin-top: 56px; }
.res-body section h2 { font-size: 1.9rem; margin-bottom: 20px; }
.incl { display: grid; gap: 8px; }
/* Amenity labels are long: one per line on phones; from 600px they flow down two columns (not paired by row),
   so a three-line label never leaves a hole beside a one-line one. */
.amen { display: grid; grid-template-columns: 1fr; gap: 0 24px; }
.amen li { display: flex; gap: 12px; align-items: flex-start; padding: 8px 0; break-inside: avoid; }
.amen svg { width: 20px; height: 20px; flex: none; color: var(--navy); margin-top: 3px; }
.map { width: 100%; aspect-ratio: 4 / 3; margin-top: 20px; border: 1px solid var(--line); background: var(--surface); filter: grayscale(.25); }
/* Shown instead of the map where the embed cannot load (the shared preview, or no network): address plus a pointer to the link below. */
.map-fallback { margin-top: 20px; padding: 20px; border: 1px solid var(--line); background: var(--surface); font-size: .95rem; }
.map-fallback p { margin: 0; }
.map-fallback p + p { margin-top: 8px; color: var(--muted); }
#map-open { margin-top: 12px; }
.videos { display: grid; gap: 16px; }
.videos video { width: 100%; aspect-ratio: 16 / 9; background: var(--navy); object-fit: contain; }
.notfound-list { display: grid; margin-top: 32px; justify-items: start; }
.notfound-list a { display: inline-flex; align-items: center; min-height: 44px; font-size: 1.25rem; text-decoration: none; }
.notfound-list a:hover { text-decoration: underline; text-underline-offset: 4px; }
.notfound .link { margin-top: 24px; }
/* Narrow phones: a tighter card so both manager rows (phone · WhatsApp) fit on one line each. */
@media (max-width: 359px) {
  .aside-box { padding: 28px 20px; }
  .aside-box .mgr-links { gap: 0 14px; }
}
@media (min-width: 600px) { .amen { display: block; columns: 2; column-gap: 24px; } }
@media (min-width: 700px) {
  .incl { grid-template-columns: 1fr 1fr; column-gap: 32px; }
  .map { aspect-ratio: 16 / 9; }
  .aside-box { padding: 32px; }
}
@media (min-width: 1000px) {
  .res-body { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); column-gap: 80px; row-gap: 72px; }
  .res-main { grid-column: 1; grid-row: 1; }
  .aside { grid-column: 2; grid-row: 1; }
  .res-map { grid-column: 1; grid-row: 2; }
  .res-safety { grid-column: 2; grid-row: 2; }
}
/* The card sticks inside its own grid area (.aside stretches to the first spread, so the card never rides over the
   safety column below). It is up to ~880px tall, so it only sticks where the whole card fits below the header; on a
   900px-tall window it scrolls with the page instead of hiding its office hours and links off-screen. */
@media (min-width: 1000px) and (min-height: 1000px) {
  .aside-box { position: sticky; top: 112px; }
}

/* ---------- 10. Utilities / a11y ---------- */
.visually-hidden { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ---------- 11. Motion (whitelist) ---------- */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  .slide-welcome > .wrap { animation: rise .6s var(--ease) both; }
  @keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
  html { scroll-behavior: auto; }
}

/* ---------- 12. Print ---------- */
/* Chrome, media and the map drop out; the navy bands print as ink on white; external links print their address. */
@media print {
  .topbar, .sticky-bar, .menu, .skip, .hero-mark, .videos, .map, #map-modes, .slider-bar, .slide-bg, .tricolour, .slide:not(.is-active) { display: none !important; } .slide-welcome::after { display: none !important; }
  body { padding-bottom: 0; }
  .field, .plaque, footer, .band { color: var(--ink) !important; background: none !important; }
  .field *, .plaque *, footer * { color: var(--ink) !important; }
  .codes, .codes th, .codes td { border-color: var(--line) !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: var(--muted); }
  .btn { border: 1px solid var(--ink) !important; }
}
