/* The footer lives in ONE file, loaded by every page — see chrome-footer.css.
   Do not restate footer rules here: keeping the design in two places is exactly
   how the homepage and the subpages drifted apart. */
@import url("chrome-footer.css");
@import url("chrome-menu.css");
@import url("viewport.css");

/* ============================================================================
   Joy Group — shared subpage chrome + page basics.
   Subpages are plain HTML (no dc runtime): they link the design system's
   /_ds/.../styles.css (tokens) plus this file, and /site/chrome.js injects the
   header, menu drawer, footer and floating call button. Class names, markup and
   the rules below are copied from Joy Group Home.dc.html so the chrome renders
   pixel-identical; when editing chrome there, mirror the change here.
   ========================================================================== */

/* ---------- Base (mirrors the homepage <style> base) ---------- */
html { scroll-behavior: smooth; }
body { margin: 0; background: var(--bg-page); color: var(--text-primary); font-family: var(--font-ui); }
a { color: var(--text-link); }
a:hover { color: var(--text-link-hover); }
[id] { scroll-margin-top: 104px; }

:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; border-radius: 2px; }

/* ---------- Language switch (no runtime: CSS shows one language) ----------
   html[data-lang] is set pre-paint by the inline snippet in each page's head;
   with JS off, the :not() default shows Albanian only. */
html:not([data-lang="en"]) .L-en { display: none !important; }
html[data-lang="en"] .L-al { display: none !important; }

/* ---------- Header (always-ink on subpages; no scroll transparency) ---------- */
/* With viewport-fit=cover the page extends under the status bar, so the fixed
   bar grows by the inset and pads its content down past it. The ink ground
   therefore reaches the true top edge instead of leaving a strip of Safari
   background above it. --sa-t is 0 everywhere without a notch. */
#site-header { position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  height: calc(var(--header-h) + var(--sa-t)); padding-top: var(--sa-t); box-sizing: border-box; display: flex; align-items: center; background: var(--ink-black); transition: opacity 420ms var(--ease-standard) 300ms; }
html.menu-open #site-header { opacity: 0; pointer-events: none; transition: opacity 420ms var(--ease-standard); }
#site-header .bar { max-width: var(--container); width: 100%; margin: 0 auto; padding: 0 var(--gutter-lg); display: flex; align-items: center; justify-content: space-between; gap: var(--space-8); }
#site-header .logo { display: block; border: none; flex: 0 0 auto; width: 208px; height: 36px; }
#site-header .logo img { width: 208px; display: block; position: relative; top: 50%; transform: translateY(-50%); }
#dc-menu-btn { appearance: none; background: none; border: none; cursor: pointer; display: flex; align-items: center; justify-content: flex-end; min-width: 44px; min-height: 44px; padding: var(--space-2) var(--space-1); }
#dc-menu-btn .lines { display: flex; flex-direction: column; gap: 5px; }
#dc-menu-btn .lines span { display: block; width: 24px; height: 1.5px; background: var(--crisp-white); }

/* Content clears the solid fixed bar */
main { display: block; padding-top: calc(var(--header-h) + var(--sa-t)); }
@media (hover: hover) {

}

/* ---------- .joy-btn — reusable CTA primitive (verbatim from the homepage) ---------- */
.joy-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 13px 26px;
  font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: 12px;
  letter-spacing: 0.14em; text-transform: uppercase; line-height: 1;
  background: transparent; border: var(--hairline) solid transparent;
  border-radius: 999px; text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard);
}
/* [hidden] has to be restated HERE. The browser's own rule is [hidden]{display:
   none}, a bare attribute selector, and .joy-btn sets display:inline-flex — a
   class wins, so a .joy-btn marked hidden in script stayed visible. Caught on the
   news Load More, which showed with nothing left to load; it would bite any
   .joy-btn ever hidden. */
.joy-btn[hidden] { display: none; }
.joy-btn--on-dark  { color: var(--crisp-white); border-color: var(--white-a62); }
.joy-btn--on-light { color: var(--ink-black);  border-color: var(--rule); }
.joy-btn__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 0; margin-left: 0; opacity: 0; overflow: hidden; font-size: 1.05em;
  transform: translateX(-6px);
  transition: width var(--dur-base) var(--ease-standard),
              margin-left var(--dur-base) var(--ease-standard),
              opacity var(--dur-base) var(--ease-standard),
              transform var(--dur-base) var(--ease-standard);
}
.joy-btn:hover, .joy-btn:focus-visible {
  background: var(--ink-black); color: var(--crisp-white); border-color: var(--ink-black);
  outline: none;
}
.joy-btn:hover .joy-btn__icon, .joy-btn:focus-visible .joy-btn__icon {
  width: 0.95em; margin-left: 7px; opacity: 1; transform: translateX(0);
}
.joy-btn--on-dark:hover, .joy-btn--on-dark:focus-visible {
  background: var(--crisp-white); color: var(--ink-black); border-color: var(--crisp-white);
}
/* No filled pill variant, deliberately. One was tried for the two-destination
   Residences cards and rejected: on this site a filled pill IS the hover state, so
   holding that fill at rest read as pressed or already-chosen, and with no arrow out
   it looked disabled. Two pills side by side are identical; ORDER carries priority. */
@media (hover: hover) {

}

/* ---------- Page content: quiet placeholder scaffolding ---------- */
.page-hero { background: var(--bg-page); padding: var(--space-16) 0 var(--space-10); }
/* One container class for every section (the footer carries its own copy). */
.shell { max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter-lg); }
.page-kicker { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--kicker-size); letter-spacing: var(--kicker-track); text-transform: uppercase; color: var(--graphite); }
.page-hero h1 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-2-size); line-height: var(--text-display-2-lh); letter-spacing: var(--text-display-2-ls); color: var(--text-primary); text-wrap: pretty; }
.page-lead { margin: var(--space-4) 0 0; max-width: 54ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.page-note { margin: var(--space-5) 0 0; font-family: var(--font-ui); font-size: var(--text-caption-size); letter-spacing: 0.08em; text-transform: uppercase; color: var(--soft-stone); }
.page-photo-band { padding: var(--space-10) 0 var(--space-16); background: var(--bg-page); }
.page-photo-band img { display: block; width: 100%; height: 520px; object-fit: cover; }
.page-cta-row { margin-top: var(--space-5); }

/* ---------- News: index featured card + snap carousel, article prose ----------
   Carousel = the homepage values-track mechanics (native CSS scroll-snap, no JS)
   restyled for the light ground: cards are image-over-text, 2.5 in view. */
.news-featured { display: block; text-decoration: none; color: inherit; border: none; }
.news-featured .frame { overflow: hidden; }
.news-featured img { display: block; width: 100%; height: 560px; object-fit: cover; transition: transform var(--dur-image) var(--ease-standard); }
.news-featured:hover img { transform: scale(1.03); }
.news-featured .below { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-6); margin-top: var(--space-4); }
.news-date { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--text-label-size); letter-spacing: var(--text-label-ls); text-transform: uppercase; color: var(--graphite); }
.news-featured h2 { margin: var(--space-2) 0 0; max-width: 24ch; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.news-featured:hover .joy-btn { background: var(--ink-black); color: var(--crisp-white); border-color: var(--ink-black); }
.news-featured:hover .joy-btn__icon { width: 0.95em; margin-left: 7px; opacity: 1; transform: translateX(0); }
.news-track-section { padding: var(--space-12) 0 var(--space-16); }
.news-track { display: flex; gap: var(--space-3); overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; -ms-overflow-style: none; }
.news-track::-webkit-scrollbar { display: none; }
.news-card-x { flex: 0 0 calc((100% - 2 * var(--space-3)) / 2.5); scroll-snap-align: start; scroll-snap-stop: always; display: block; text-decoration: none; color: inherit; border: none; }
.news-card-x .frame { overflow: hidden; }
.news-card-x img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; transition: transform var(--dur-image) var(--ease-standard); }
.news-card-x:hover img { transform: scale(1.04); }
.news-card-x h3 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-5-size); line-height: var(--text-display-5-lh); color: var(--text-primary); text-wrap: pretty; }
.news-card-x .news-date { margin-top: var(--space-3); }
/* News cards wear the .joy-btn pill like every other card; the bare "Read →" span it
   replaces is gone. The card is the link, so the pill is a <span>. */
.news-cta-pill { margin-top: var(--space-2); align-self: flex-start; }
.news-card-x:hover .joy-btn { background: var(--ink-black); color: var(--crisp-white); border-color: var(--ink-black); }
.news-card-x:hover .joy-btn__icon { width: 0.95em; margin-left: 7px; opacity: 1; transform: translateX(0); }
.article-hero { background: var(--bg-page); padding: var(--space-16) 0 var(--space-8); }
.article-hero h1 { margin: var(--space-2) 0 0; max-width: 22ch; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-2-lh); letter-spacing: var(--text-display-2-ls); color: var(--text-primary); text-wrap: pretty; }
.article-figure { padding: 0 0 var(--space-10); }
.article-figure img { display: block; width: 100%; height: 560px; object-fit: cover; }
.article-body { padding: 0 0 var(--space-16); }
.article-body .prose { max-width: 62ch; }
.article-body p { margin: 0 0 var(--space-4); font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.article-back { margin-top: var(--space-6); }

/* ---------- Contact: get-in-touch form + office details + map slot ----------
   Form inputs on the tokens: hairline borders, near-square corners, no shadows;
   focus and error states resolve to ink (status by weight, never hue). */
.contact-grid { display: grid; grid-template-columns: 7fr 5fr; gap: var(--space-10); align-items: start; padding: var(--space-4) 0 var(--space-16); }
.contact-form { display: flex; flex-direction: column; gap: var(--space-4); }
.contact-field { display: flex; flex-direction: column; gap: 8px; }
.contact-field label { font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--text-label-size); letter-spacing: var(--text-label-ls); text-transform: uppercase; color: var(--graphite); }
.contact-field input, .contact-field textarea { appearance: none; background: transparent; border: var(--hairline) solid var(--rule); border-radius: 2px; padding: 14px 16px; font-family: var(--font-ui); font-size: var(--text-body-size); color: var(--text-primary); transition: border-color var(--dur-base) var(--ease-standard); }
.contact-field textarea { min-height: 160px; resize: vertical; }
.contact-field input:focus, .contact-field textarea:focus { outline: none; border-color: var(--ink-black); }
.contact-field.is-err input, .contact-field.is-err textarea { border-color: var(--ink-black); border-width: 2px; padding: 13px 15px; }
.contact-field .field-err { display: none; font-family: var(--font-ui); font-size: var(--text-caption-size); color: var(--text-secondary); }
.contact-field.is-err .field-err { display: block; }
.contact-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.contact-submit-row { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-2); flex-wrap: wrap; }
#contact-status { margin: 0; font-family: var(--font-ui); font-size: var(--text-body-sm-size); line-height: var(--text-body-sm-lh); color: var(--text-secondary); max-width: 44ch; text-wrap: pretty; }
#contact-status a { color: var(--text-primary); }
.contact-aside { display: flex; flex-direction: column; gap: var(--space-6); }
.contact-office h2 { margin: 0 0 var(--space-3); font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-5-size); line-height: var(--text-display-5-lh); color: var(--text-primary); }
.contact-office .lines { display: flex; flex-direction: column; }
.contact-office .lines a, .contact-office .lines span { font-family: var(--font-ui); font-size: var(--text-body-size); color: var(--text-secondary); text-decoration: none; border: none; padding: 6px 0; }
.contact-office .lines a:hover { color: var(--text-primary); }
.contact-map { aspect-ratio: 4 / 3; background: var(--bg-alt); display: flex; align-items: center; justify-content: center; }
.contact-map span { font-family: var(--font-ui); font-size: var(--text-caption-size); letter-spacing: 0.08em; text-transform: uppercase; color: var(--soft-stone); }

/* ---------- About: full-bleed opening band ----------
   Sits below the always-ink header (main clears it), so the bar frames the
   photograph rather than floating over it — the subpage nav has no transparent
   state by design. Capped so the statement row below still clears the fold on a
   13" laptop: 46vh of a 700px viewport is 322px, leaving ~300px for the row. */
.page-bleed { position: relative; display: block; width: 100%; height: clamp(320px, 46vh, 520px); overflow: hidden; }
.page-bleed img, .page-bleed video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ---------- The first screen (About + the three company pages) ----------
   Image + statement are locked to exactly one viewport minus the solid header, so
   nothing below them is visible on load. Meraas does this with an 85vh banner and a
   header floating over it; ours sits below the ink bar, so the budget is
   100vh - header and the image takes whatever the statement leaves. Flex rather
   than a hardcoded height: the statement's height moves with viewport width and
   with Albanian running longer, and the image absorbs the difference.
   The image is positioned absolutely inside its flex cell so its height is always
   definite — `height: 100%` against a flex-grown parent is circular otherwise.
   One implementation, four pages: About proved it, Construction/Management/Travel
   reuse it verbatim so the group reads as one hand. */
.page-opening { display: flex; flex-direction: column; min-height: calc(100vh - var(--header-h)); }
.page-opening .page-bleed { flex: 1 1 auto; height: auto; min-height: 280px; position: relative; }
.page-opening .page-bleed img, .page-opening .page-bleed video { position: absolute; inset: 0; }
.page-opening .page-statement { flex: 0 0 auto; }

/* ---------- Company pages: the tall hero variant (trial, 2026-08-17) ----------
   Meraas's model rather than About's: the hero is sized on its own terms (~80vh,
   capped) and the statement lives BELOW the fold instead of being squeezed into
   the same screen. Scoped to [data-page="companies"] so /about/'s approved
   one-viewport opening is untouched.
   Hierarchy is deliberately the inverse of Meraas's: their overlay is a small
   category label, ours is the company NAME, so it outranks the headline under it
   (72 vs 46 on desktop, 36 vs 28 on a phone). */
/* The opening runs 120px PAST the fold on purpose: the next section then never
   pops up the moment you scroll — you travel through white first. The copy sits
   tight under the hero (flex-start, 40px gap) and ALL the leftover space falls
   below it, so the white is spent on the scroll rather than split around the
   text. The +120 is what guarantees the travel at any screen height. */
/* The travel past the fold is ALWAYS (the statement's bottom padding - 40px). That
   subtraction is the whole mechanism: the copy ends 40px above the fold no matter the
   window, so the first screen shows the hero, the heading and the body and nothing
   else. Which also means the gap below can be tightened for free — take the same
   amount off the padding AND off the travel and they cancel, leaving the hero band,
   the heading and the copy on exactly the pixels they were on. Measured, not assumed:
   pulling 32px out of both left bleed 611, h1 768, copy-bottom 860 unchanged, and
   moved the next section up by exactly 32.
   About and the company pages carry different travel because their gap is built
   differently — see the padding rule below. */
[data-page="about"] .page-opening,
[data-page="companies"] .page-opening { display: flex; flex-direction: column;
  min-height: calc(100vh - var(--header-h) + var(--space-16) - 40px); }
[data-page="companies"] .page-statement,
[data-page="about"] .page-statement { flex: 0 0 auto; display: block; }
[data-page="companies"] .page-statement > .shell,
[data-page="about"] .page-statement > .shell { width: 100%; }
/* Meraas's actual model, which is NOT a vh figure: the band's height is its own
   width divided by the image ratio, hard-capped. 80vh tied the hero to the window,
   so it grew with every extra pixel of screen and the headline could never surface;
   this stops at the cap and hands the rest of the screen back to the copy.
   Three terms: width/1.78 like theirs, a 58vh ceiling so short laptops still show
   the whole 4-line body, and a 500px absolute cap for tall displays. */
/* The hero FLEXES to fill whatever the statement leaves, so the white under the
   copy is a constant 40px at every screen height instead of a leftover that grows
   with the window. The statement's 160px bottom padding is what sets it:
   visible air = padding - 120 (the past-the-fold travel). */
/* 1000, not 800. The opening is pinned to a full viewport so the next section stays
   below the fold, and the photograph is the flex item that absorbs that height — so
   whatever the photograph is NOT allowed to take becomes dead white under the copy
   instead. At 800 the cap was reached at about 1090px of viewport height, and every
   pixel of screen above that landed in the gap: 239px of white at 1200 tall, 479 at
   1440, 639 at 1600, with the copy floating 391px above the fold instead of 40.
   1000 lets the image swallow the whole surplus up to ~1290px tall — so 1200-high
   screens are fully solved — and halves what is left beyond that (479 -> 279 at 1440).
   A deliberate compromise: removing the cap entirely would fix 1440 too, at the cost
   of a 1150px photograph. */
[data-page="companies"] .page-opening .page-bleed,
[data-page="about"] .page-opening .page-bleed { flex: 1 1 auto; height: auto; aspect-ratio: auto; max-height: 1000px; min-height: 320px; }
/* The name sits over the BODY column, not above the headline: two Begum blocks
   sharing a left edge stack into a pile, and at 72px the name was competing with
   the headline instead of leading it. Aligned to column 7 it lines up exactly
   with the paragraph beneath, and the page reads name (right) → headline (left)
   → body (right). */
[data-page="companies"] .bleed-title .shell { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); }
/* 5.56vw, not 5: the middle term is what actually decides the size at the design
   width, and 5vw resolves to exactly 72px at 1440 — so raising the ceiling alone
   changed nothing. 5.56vw lands on 80 at 1440 and holds there above it. */
/* The token itself, not a bespoke vw. --text-display-2-size and
   --text-display-3-size are both on the desktop fluid scale and shrink by the same
   ratio, so display-2/display-3 is 1.43 at EVERY width by construction. A hand-rolled
   5.56vw only matched at 1440 and drifted everywhere else — at 820 it fell to 45.6
   against a 44.6 section heading, i.e. the name over the photograph had been overtaken
   by the headings below it. */
[data-page="companies"] .bleed-title h1 { grid-column: 7 / span 6; font-size: var(--text-display-2-size); line-height: 1.04; letter-spacing: var(--text-display-2-ls);
  white-space: nowrap; }
/* Below ~1366 the name at display-2 is wider than its six columns, so it needs the
   whole shell or nowrap would push it out of the page. Right-aligned, so it keeps
   the right-edge anchor it has on wide screens and grows leftward into the space
   instead of wrapping. One line at every width, which is the rule. */
@media (max-width: 1365px) {
  [data-page="companies"] .bleed-title h1 { grid-column: 1 / span 12; text-align: right; }
}
/* Two lines, never three: the headline column widens to 6 so "Building Strong
   Foundations / for Every Project" breaks once. */
[data-page="companies"] .page-statement .col-h { grid-column: 1 / span 6; }
/* Opt-in wider body: gives the paragraph a 7th column so it holds FOUR lines on
   narrower desktops instead of spilling to five — which would eat into the hero,
   since the hero flexes around the copy. It costs the headline one column, so it
   is only put on pages whose headline still fits (Management, Travel). Construction's
   headline needs all six and keeps the even split. */
[data-page="companies"] .page-statement .row.wide-copy .col-h { grid-column: 1 / span 5; }
[data-page="companies"] .page-statement .row.wide-copy .col-p { grid-column: 6 / span 7; }
/* The statement steps back so the name can lead, and gains air above it: the gap
   is now the break between two screens, not a gutter inside one. */
/* 40px hero-to-copy, close to Meraas's 30; the bottom is the leftover travel. */
/* About takes the hero SIZE and SPACING only — no name over the image; that
   overlay stays a What We Do device. */
/* --space-16 of gap to the next section on both, which is the same rhythm every other
   major section break uses. It was 160 + 0 on About and 160 + 128 on the company pages
   — those stack the statement's padding on top of the next section's OWN padding, so
   they were running at 296px against About's 168.
   The company pages split it in half rather than zeroing the far side, because their
   next section is a MIST band: its top padding is inside the tint, and removing it
   would put the heading flush against the band's edge. Half here, half there, so the
   white gap and the band's inner air stay in proportion and the total still lands on
   --space-16 at every width. */
[data-page="about"] .page-statement,
[data-page="companies"] .page-statement { padding: var(--space-5) 0 var(--space-16); }
/* The 72 > 46 > 40 > 36 ladder is GONE. It existed because the section headings
   below tied with the statement when both were display-3 (56) — but the fix was
   four sizes where two would do, three of them (46, 40, 36) inside a 10px band and
   two of them off the token scale entirely.
   The name over the photograph now carries the separation on its own at 80 — a
   token, and 1.43x above 56, which is the same hero-to-section ratio Meraas uses
   (50/35). Everything below it drops to the site-wide roles, so the statement and
   the section headings tie at 56 exactly as the homepage's seven sections tie with
   each other. Nothing here overrides them any more; they inherit.
   .mach-card h2 joins the card tier at 28 (--text-display-5-size). */
[data-page="companies"] .mach-card h2 { font-size: var(--text-display-5-size); }

@media (min-width: 769px) {
  .page-statement .col-h > h1:first-child,
  .page-statement .col-h > h2:first-child { margin-top: calc(12px - 0.357em); }
}

/* ---------- Statement row (heading left, paragraph right) ---------- */
/* On About the figure row sits inside this section's right column, so the bottom
   padding is the break to what follows rather than a gap inside the block. */
.page-statement { padding: var(--space-12) 0; }
.page-statement .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: start; }
.page-statement .col-h { grid-column: 1 / span 5; }
/* The company name over the opening photograph — the page's h1, white, sitting on
   the shell's own gutter so it shares its left edge with the statement below.
   Begum, never Pinyon: that face belongs to the homepage hero alone. The eased
   scrim is the values-card gradient, dark only where the type sits, so the
   photograph itself stays full colour. */
.bleed-title { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; padding: 16% 0 var(--space-5);
  background-image: linear-gradient(180deg, rgba(20,20,20,0) 0%, rgba(20,20,20,0.04) 24%, rgba(20,20,20,0.13) 46%, rgba(20,20,20,0.26) 68%, rgba(20,20,20,0.40) 88%, rgba(20,20,20,0.48) 100%); }
/* Sized against Meraas's own hero label (32px desktop, 28px phone) rather than as
   a second display heading: the overlay NAMES the page, the statement below is
   still the biggest type on it. */
.bleed-title h1 { margin: 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: clamp(1.75rem, 2.6vw, 38px); line-height: 1.1; letter-spacing: var(--text-display-4-ls); color: var(--crisp-white); }

/* The --space-2 top margin exists to sit the headline under a KICKER. About has one;
   the company statements do not, so there the margin only pushed the headline out of
   line with the body column beside it — measured 23px of ink offset.
   :first-child is what tells them apart: About's h1 follows its kicker, a company h2
   opens its column. The replacement is optical, not zero — Begum's cap sits 0.357em
   below its line-box top against roughly 12px for the body's Neue Haas, so the two
   first lines start on the same pixel at every desktop width. Below 769 the columns
   stack and there is nothing to align. */
.page-statement h1, .page-statement h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.page-statement .col-p { grid-column: 7 / span 6; }
/* :not(.page-note) — this rule is more specific than .page-note, so without the
   exclusion the scaffolding note inherits body-lg graphite and loses its margin. */
.page-statement .col-p > p:not(.page-note) { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }

/* ---------- About: founder's quote (oversized ink card, image beside) ----------
   Early on the page (it leads toward the mission), on a light ground: the ink
   lives in a card, never as a full-bleed band this high (see ground rules). */
.quote-band { padding: 0 0 var(--space-12); }
/* gap: 0 — the ink card and the photograph MEET. This row holds only those two, so
   zeroing the gap costs nothing else: side by side they share an edge, and stacked on
   phones they share a horizontal one. The 7/5 split still reads, it just spans the
   full width now (card 793 rather than 780 at 1440). */
.quote-band .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: 0; align-items: stretch; }
.q-card { grid-column: 1 / span 7; background: var(--bg-inverse); color: var(--text-inverse); padding: var(--space-10) var(--space-8); display: flex; align-items: center; min-height: 480px; }
.q-card blockquote { margin: 0; }
/* --q-fs so the attribution's gap can be solved against the QUOTE's size; every
   breakpoint that changes the quote font changes this one property instead. */
/* display-4, not display-3: the quote is a SUB-section, not a section headline —
   at 56 it tied with every section heading on the page and read as the loudest
   thing in the composition. The attribution's gap follows automatically: its
   margin is solved against --q-fs, so 40px type still lands 40px optically. */
.q-card { --q-fs: var(--text-display-4-size); }
.q-card .quote { margin: 0; max-width: 20ch; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--q-fs); line-height: 1.18; letter-spacing: var(--text-display-3-ls); color: var(--crisp-white); text-wrap: pretty; }
/* body-sm, not the 11px label size: the attribution was reading as a footnote
   under a 56px quote (user: too small). Tracking eased to suit the larger size. */
/* 40px TO THE EYE, not 40px in the box. Between the quote's last baseline and the
   attribution's cap-height sit two invisible bands: the quote's half-leading plus
   unused descender space, and the attribution's own leading above its caps. Measured,
   that dead space is 22px at 56px type, 21 at 53, 19 at 45, 15 at 29 — it tracks the
   quote's font size, so it cannot be a constant. Fitted, it is 0.259 * font + 7.5px,
   which makes the margin that lands 40px optically 32.5px - 0.259 * font.
   Verified against all four: 18/19/21/25px of margin for a measured 40px gap. */
.q-card .quote-attr { margin: calc(32.5px - 0.259 * var(--q-fs)) 0 0; font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--kicker-size); letter-spacing: var(--kicker-track); text-transform: uppercase; color: var(--white-a62); }
.q-img { grid-column: 8 / span 5; }
/* The image is absolutely positioned so its intrinsic 2:3 height cannot set the
   grid row: without this the portrait inflates the card to ~920px and the quote
   floats in a tower of ink. The card's min-height governs; the image follows. */
.q-img .frame { position: relative; height: 100%; overflow: hidden; }
.q-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* ---------- About: "We work as one" ----------
   A subsection of the statement, not a sibling: no kicker, display-4 heading, and
   the block sits at column 3 — the same centre-push the homepage gives its group
   and companies headings. The paragraph stacks beneath, sharing the left edge. */
.one-group { padding: 0 0 var(--space-12); }
.one-group .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: start; }
/* Heading starts at column 3 — the same centre-push the homepage gives its "What
   we do" heading — with the paragraph stacked beneath it, sharing that left edge.
   The h2 -> body gap is the grid's own 32px row gap (the standard rhythm), so the
   paragraph carries no top margin: adding one doubles it to 64px. */
.one-group .col-h { grid-column: 3 / span 8; }
.one-group h2 { margin: 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.one-group .col-p { grid-column: 3 / span 7; }
.one-group .col-p > p { margin: 0; max-width: 62ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
/* Inline company links in body copy: ink against the graphite prose, hover
   underline. border: none kills the DS's automatic anchor border. */
.body-link { color: var(--text-primary); border: none; text-decoration: none; transition: color var(--dur-fast) var(--ease-standard); }
.body-link:hover, .body-link:focus-visible { color: var(--text-primary); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

/* ---------- About: Joy Residences (the end product) ----------
   One-sided mist panel (the Meraas device; its single use on this page): the
   panel runs from the left viewport edge to 58% and the photograph overlaps its
   edge. Copy sits on the panel, the image bridges panel and white. */
.res-feature { position: relative; padding: var(--space-12) 0; }
.res-feature::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 58%; background: var(--bg-alt); }
.res-feature .shell { position: relative; }
.res-feature .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: center; }
.res-copy { grid-column: 1 / span 5; }
.res-copy h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.res-copy > p:not(.page-kicker) { margin: var(--space-4) 0 0; max-width: 40ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.res-img { grid-column: 7 / span 6; }
.res-img .frame { overflow: hidden; }
.res-img img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }

/* ==========================================================================
   JOY RESIDENCES SUBPAGES — a different opening on purpose
   ==========================================================================
   The company pages share About's one-viewport opening: photograph and statement
   squeezed onto the same screen. The project pages deliberately do not. A project
   is sold on the place itself, so the film gets the WHOLE first screen and the
   words start underneath it.
   The ink header stays put over it — this is the distinction from the homepage,
   whose bar is transparent over its hero and only turns ink after it. Here the
   black bar is present from the first pixel, so a project page never reads as
   another homepage.
   svh, not vh: on iOS `vh` is the URL-bar-hidden viewport, so a box sized in vh
   lays itself out for a screen taller than it occupies. The subtraction mirrors
   main's own padding-top exactly — header height plus the top safe-area inset —
   so the hero ends on the fold rather than 76px past it. */
[data-page="residences"] .page-bleed {
  height: calc(100svh - var(--header-h) - var(--sa-t));
  max-height: none; }

/* ---- The hero title block: kicker, name, status - all bottom LEFT ----
   The company pages hang their name off column 7 so it sits over the statement's
   body copy on the screen below. There is no statement on this screen, so that
   anchor means nothing here: the block goes to the shell's left edge and reads
   top-down as brand, project, state.
   Left also keeps it clear of the floating contact button, which lives bottom
   RIGHT. That mattered when the name was right-aligned on a full-screen hero:
   measured, they overlapped at 820 and 390 and were within 65px at 1024.
   1.5x display-2, per the brief - 120px at 1440 and up, and the multiplier rides
   the token rather than a fresh clamp, so the name keeps the same relationship to
   every other size on the site at every width.
   No `white-space: nowrap`. At this size "The Apartments" is ~840px, which is one
   line in a 1520px shell without being told to be; forcing it on a phone would
   push the name off the screen instead of letting it break. */
[data-page="residences"] .bleed-title .shell { display: block; }
/* --hero-name-scale, not a fixed multiplier: "The Apartments" is 899px against "The
   Villas" at 547 when both sit at 120px - 59% of the shell against 36% - so one size
   cannot serve both. The longer name steps down rather than the shorter one growing,
   which keeps the pair reading as siblings. Measured, 1.2 puts it at 719px / 47% and,
   at 820 where the shell is tightest, ends 115px clear of the floating contact
   button; at 1.5 it overran that button by 28px. */
/* Off the bottom edge. The shared .bleed-title stands 40px clear, which is right
   for a half-height hero carrying one line; this block is three deep and sits at
   the bottom of a FULL screen, so it needs to stop looking like it is resting on
   the fold. Scoped - the company pages keep their 40. */
/* 1.2 and 0.96 are the old 1.5 and 1.2 taken down by a fifth, and the fifth is not
   a taste call - it is what makes the name in CAPITALS occupy the same width it did
   in mixed case. Measured in Begum Light: "THE VILLAS" runs 5.453px of width per px
   of type against "The Villas" at 4.56, and "THE APARTMENTS" 8.639 against 7.488. At
   0.8x the caps line lands within a pixel or two of the footprint it replaced, on
   both names, so nothing downstream had to move. The stroke, meanwhile, is a fifth
   thinner - which is the whole point: capitals put the same weight of ink on the
   screen with none of the relief that ascenders and descenders give a mixed line. */
[data-page="residences"] .bleed-title { --hero-name-scale: 1.2; padding-bottom: var(--space-8); }
[data-page="residences"] .bleed-title.hero-long { --hero-name-scale: 0.96; }
[data-page="residences"] .bleed-title h1 {
  font-size: calc(var(--text-display-2-size) * var(--hero-name-scale));
  line-height: 1.0;
  /* POSITIVE tracking, where every other display line on this site is negative.
     -0.018em is right for mixed case, where ascenders and descenders already break
     the line up; set solid, capitals close their counters and read as a wall. The
     site's own uppercase - kickers, labels, the status below - has been tracked out
     all along for exactly this reason. 0.03em is the display-scale equivalent: far
     tighter than a 12px label needs, enough to let the letters breathe at 96. */
  letter-spacing: 0.03em; text-transform: uppercase;
  text-align: left; text-wrap: balance;
  /* Neither the site's 0.357em cap correction NOR the 0.0583em this rule used to
     carry. Both were measured against a MIXED line, whose topmost ink is the
     ascender of the l in "Villas", not the cap. In capitals the tallest thing on
     the line is the cap itself, which sits lower. Measured on canvas at 100px,
     Begum Light, line-height 1.0: font ascent 96, descent 34, half-lead -15, so the
     baseline is 81 below the box top; caps reach 12.5 below it, lowercase-with-
     ascender only 6. Hence 0.125em. Left at 0.0583 the name rode 6px too high into
     the kicker. 12px is the stand-off every other kicker on the site keeps, and
     expressing the correction in em holds it at 96px and at the phone's 47px. */
  margin-top: calc(12px - 0.125em); }
[data-page="residences"] .hero-kicker,
[data-page="residences"] .hero-status {
  margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--kicker-size); letter-spacing: var(--kicker-track);
  text-transform: uppercase; color: var(--crisp-white); }
/* The brand line outranks the status below it, and needs to hold its own against a
   photograph rather than a flat ground. 17px and --fw-bold (700), which is a REAL
   face - HaasGrotDisp-75Bold ships with the system - not a browser-synthesised
   bold. 700 is as heavy as this family goes here; past it the browser fakes it. */
[data-page="residences"] .hero-kicker {
  font-size: 17px; font-weight: var(--fw-bold); }
/* The status reads quieter than the brand above it: same size and tracking, less
   light. Weight and wording carry status on this site, never colour. */
/* The gap under the name is set from the BASELINE. In capitals that is finally the
   whole story: "THE APARTMENTS" has no descender at all, so the p that used to hang
   23px into this gap - and forced the two pages to be solved separately - is gone.
   Both names now end on the baseline and the same 29px reads on both.
   The status is 15px on both pages, so the distance to it should match in ABSOLUTE
   terms rather than scale with a name that changed size. The box ends 0.19em under
   the baseline, so padding = target - 0.19em, solved at each name's own size:
     "THE VILLAS"     29px at 96   -> 0.302em - 0.19 = 0.112em
     "THE APARTMENTS" 29px at 76.8 -> 0.378em - 0.19 = 0.188em
   The two em figures differ only because the sizes do; the read gap is identical,
   which it never quite was before. */
[data-page="residences"] .bleed-title h1 { padding-bottom: 0.112em; }
[data-page="residences"] .bleed-title.hero-long h1 { padding-bottom: 0.188em; }
/* And the TOP of the block has to line up too, which it did not. Both pages already
   ended identically - status 64px off the fold, baseline 118 - because everything
   below the name is solved in absolute terms. Above it nothing was: the kicker sits
   on top of the name, so a name a quarter smaller pulled the kicker 13px down the
   screen and the two heroes started at different heights.
   The make-up is the difference in CAP HEIGHT between the two names, which is the
   only thing that changed: 0.685em of cap x the 1.25 ratio between the scales, so
   0.685 x 0.25 = 0.171em of this page's own size. Added to the shared correction
   (12px - 0.125em) that comes to 12px + 0.046em. Written as a ratio rather than the
   13px it works out to at desktop, because both scales are multiples of one token -
   the ratio holds at the phone's size and at any other, the pixel figure would not.
   The name's own cap top still sits lower, and has to: it is a smaller name, by
   instruction. What matches now is where the block starts and where it ends.
   769px AND UP ONLY, and that is not caution - below it the correction does harm.
   On a phone "THE APARTMENTS" wraps to two lines and the Villas' status wraps to
   two, so the blocks are different shapes there and the Apartments kicker already
   sits ABOVE the Villas' rather than below it. Adding the make-up pushed it further
   past: 16px adrift where it had been 10. The two phone heroes cannot be aligned by
   this mechanism, because on a phone they are not the same composition. */
@media (min-width: 769px) {
  [data-page="residences"] .bleed-title.hero-long h1 { margin-top: calc(12px + 0.046em); }
}
[data-page="residences"] .hero-status { margin-top: 0; color: var(--white-a62); }
/* The separator the status pair asked for. A hairline, drawn as a box rather than a
   border so it can be sized to the cap height instead of the line box: 0.86em is the
   uppercase cap of this face at this size, so the rule starts and stops with the
   letters beside it rather than overshooting them. */
[data-page="residences"] .hero-sep { display: inline-block; width: var(--hairline);
  height: 0.86em; margin: 0 var(--space-2); vertical-align: -0.06em;
  background: var(--white-a62); }

/* The introduction is media-first, the way a project page should be: the film on
   the left, the words beside it. It reuses .duo — the site's neutral white
   two-column device — and only swaps the columns, so there is no second
   implementation of a layout the site already has. White, deliberately: the mist
   chapter follows immediately, and two tinted sections in a row flatten into one. */
/* grid-row: 1 on BOTH is load-bearing, not decoration. Auto-placement is sparse by
   default: the copy comes first in the DOM and takes columns 8-12 of row 1, which
   moves the cursor past column 1, so the image - asking for columns 1-6 - is put on
   row 2 and lands 537px BELOW the copy instead of beside it. Naming the row is what
   holds a flipped pair on one line. Exactly the trap the values page hit. */
/* ---- Location on the project pages ----
   The map keeps /contact/'s arrangement: a square-ish block with the details beside
   it. A full-width band was tried and taken back out - it turned the captions into
   corner labels on either side of a stripe, and read as a strip of map rather than
   as a place. Same markup and same grid as contact; three things differ.
   1. Cream, and SYMMETRIC. The base rule is `var(--space-16) 0 0` - no bottom padding
      - because on /contact/ this section is white and the next one supplies the gap.
      As a tint that is wrong: 128 above and nothing below reads as crowded onto what
      follows, and the text rides the bottom edge of its own colour. 128 both sides.
   2. More air between the map and the words: 64px against contact's 40.
   3. The slot's tint was mixed to sit on WHITE, and on cream it lands within a few
      points of its ground - the reserved area read as an empty stripe. Same colour,
      more of it; over #F0F0EF this resolves to about #D1D1D0. Moot once the embed
      drops in. */
/* Location and the close carry NO residences override any more - both take the base
   rule, which is white, and that is the change. The cream band used to be three
   sections deep (location + features + close) and ran from the ink card all the way
   to a filler element; it is now the features alone, with the page finishing on white
   exactly the way the company pages finish.
   Nothing had to be written to do it. Base .offices is `padding: var(--space-16) 0 0`
   - top only, no bottom - which existed because on /contact/ it is already white, and
   is precisely right here: the close below pays its own 128 on top, and an .offices
   bottom padding would have stacked with it to 256. Base .page-close is
   `var(--space-16) 0 var(--page-end)`, so the white run before the footer comes from
   the close itself and .mist-to-ink is no longer needed on these two pages. */
[data-page="residences"] .offices-row { column-gap: var(--space-8); }
[data-page="residences"] .offices-map { background: rgba(199, 199, 197, 0.75); }
/* Two lines, not one long one: at the info column's full width the sentence runs as
   a single line. 34ch caps it at roughly 45 characters. Scoped `> p.office-body`
   because a bare descendant p rule here would also catch .page-kicker - the trap
   this stylesheet has fallen into before. */
[data-page="residences"] .office-block > p.office-body { max-width: 34ch; }

/* ---- Features and amenities: alternating half-and-half rows, Meraas's device ----
   Measured on theirs: the row splits 50/50, the copy sits on a SOLID WHITE panel,
   the halves swap sides row to row, and consecutive rows butt together with no gap
   so the white panels tile against the tint. All of that is the effect - the white
   is what breaks the cream up, so the section can be tinted without going flat.
   grid-column AND grid-row named explicitly on both cells. Auto-placement is sparse:
   the flipped row would put its second cell on a new line, which is exactly the trap
   the values page and the .duo pair each hit once already.
   No top padding: this section and the location above it are the same cream, so they
   read as ONE band with two chapters. The location's 128px bottom padding is the gap
   between them, and the band stays symmetric overall - 128 above the first heading,
   128 below the last row. */
/* SYMMETRIC now, and it has to be. The 0 on top was correct only while location sat
   directly above it inside the same tint, already paying 128 below its own content -
   two paddings inside one band would have stacked. The features are a band on their
   own now, opening straight out of white, so they pay their own 128 at the top. A
   band that is even is the whole rule here; taking space off one side only is the
   defect that has been caught on this site twice. */
[data-page="residences"] .feat { background: var(--bg-alt); padding: var(--space-16) 0; }
.feat-rows { margin-top: var(--space-8); }
.feat-row { display: grid; grid-template-columns: 1fr 1fr;
  min-height: clamp(360px, 33vw, 540px); }
.feat-img { grid-column: 1; grid-row: 1; position: relative; overflow: hidden; }
.feat-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.feat-img .ph { position: absolute; inset: 0; }
.feat-copy { grid-column: 2; grid-row: 1; background: var(--crisp-white);
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--space-8); }
.feat-row.is-flipped .feat-img { grid-column: 2; }
.feat-row.is-flipped .feat-copy { grid-column: 1; }
/* The white panels' headings are set in CAPITALS, the same treatment the project
   names take in the hero above. .feat lives on these two pages and nowhere else, so
   this reaches exactly six headings.
   Tracking positive, as capitals always need, but only 0.05em - deliberately far
   short of the 0.14em the kicker above them carries. The house tracks uppercase
   harder the smaller it gets (0.22em at 11px, 0.16 at 15, 0.03 at the hero's 96),
   and 28px sits near the label end of that curve; taken there, these would read as
   another kicker rather than as the heading under one. Half the kicker's figure
   opens the counters and keeps the two ranks apart. text-transform rather than
   capitals in the markup, so the Albanian keeps its sentence case in the source and
   a screen reader still says "Amenities", not "A M E N I T I E S". */
.feat-copy h3 { margin: 0; font-family: var(--font-display); font-weight: var(--fw-display-light);
  /* Size is the plain token here and 1.5x of it from 769 up - see the note in the
     min-width: 769px block. Tracking stays 0.05em at every size: letter-spacing in em
     is size-invariant, so the capitals stay exactly as open as they were. */
  font-size: var(--text-display-5-size); line-height: var(--text-display-5-lh);
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-primary); }
.feat-copy > p { margin: var(--space-3) 0 0; max-width: 40ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.75;
  color: var(--text-secondary); text-wrap: pretty; }

[data-page="residences"] .duo-copy { grid-column: 8 / span 5; grid-row: 1; }
[data-page="residences"] .duo-img  { grid-column: 1 / span 6; grid-row: 1; }

/* ---------- Milestone timeline (driven by site/timeline.js) ----------
   Base rules for the section: head, rail + scroll fill, sticky year column and
   reveal. The compact variant below overrides the layout while the pinned
   rebuild awaits approval. */
.tl { padding: 0 0 var(--space-16); }
.tl-head { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); padding-bottom: var(--space-10); }
.tl-head-inner { grid-column: 1 / span 6; }
.tl-head h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }

.tl-wrap { position: relative; --tl-rail-inset: calc((100% - 11 * var(--grid-gap)) / 12 * 2 + 2 * var(--grid-gap)); }
/* graphite, not --rule: soft-stone on white is 1.69:1, under the 3:1 non-text
   floor. The mask opens at 2% so the first dot gets a drawn rail, and closes at
   94% to clear the last. */
.tl-rail { position: absolute; top: 0; bottom: 0; right: var(--tl-rail-inset); width: var(--hairline); background: var(--graphite);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 2%, #000 94%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 2%, #000 94%, transparent 100%); }
/* The fill is DELIBERATELY wider than the rail it runs down. Both were a hairline,
   so scrolling only swapped graphite for ink at the same 1px weight and the progress
   barely registered. Growing the fill on its short axis makes the drawn part read as
   a bolder line tracking down a lighter one, which is the whole point of the gesture.
   Negative insets rather than a width: the fill is absolutely positioned inside the
   rail, so -0.5px each side centres 2px on a 1px rail without touching the transform
   (which is already carrying the scale) or the rail's own hairline weight.
   1px of growth each side = a 3px fill on a 1px rail. A whole pixel on purpose:
   2.5 would render soft on a 1x display, where 3 stays crisp. One number to tune. */
.tl-wrap { --tl-fill-grow: 1px; }
.tl-fill { position: absolute; inset: 0 calc(-1 * var(--tl-fill-grow)); background: var(--ink-black);
  transform: scaleY(var(--tl-p, 0)); transform-origin: top center; }

.tl-list { list-style: none; margin: 0; padding: 0; }
.tl-item { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); padding-bottom: var(--space-16); }
.tl-item:last-child { padding-bottom: var(--space-10); }
/* grid-row: 1 on both cells is load-bearing: the year is first in the DOM but
   sits in a later column, and sparse auto-flow would push the body to row 2. */
.tl-year { grid-column: 8 / span 3; grid-row: 1; position: sticky; top: calc(var(--header-h) + 56px); align-self: start; text-align: right; padding-right: var(--space-3); }
.tl-dot { position: absolute; right: -4px; top: 50%; transform: translateY(-50%); width: 9px; height: 9px; border-radius: 50%;
  background: var(--graphite); transition: background-color var(--dur-slow) var(--ease-standard); }
/* Resting colour is graphite (5.32:1), not soft-stone: .is-in is added only by
   timeline.js, so with JS off this rule is what the years keep forever. */
.tl-year .y { display: block; font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-2-size); line-height: 1; letter-spacing: var(--text-display-2-ls);
  color: var(--graphite); transition: color var(--dur-slow) var(--ease-standard); }
.tl-item.is-in .tl-year .y { color: var(--text-primary); }
.tl-item.is-in .tl-dot { background: var(--ink-black); }

.tl-body { grid-column: 1 / span 6; grid-row: 1; }
.tl-meta { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--text-label-size); letter-spacing: var(--text-label-ls); text-transform: uppercase; color: var(--graphite); }
/* Linked company names: ink against the graphite label, underline on hover only.
   border: none kills the DS's automatic anchor border. */
.tl-meta a { color: var(--text-primary); border: none; text-decoration: none; transition: color var(--dur-fast) var(--ease-standard); }
.tl-meta a:hover, .tl-meta a:focus-visible { color: var(--text-primary); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.tl-body h3 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-5-size); line-height: var(--text-display-5-lh); letter-spacing: var(--text-display-5-ls); color: var(--text-primary); text-wrap: pretty; }
/* :not(.tl-meta) — the descendant rule would out-specify the label otherwise. */
.tl-body > p:not(.tl-meta) { margin: var(--space-3) 0 0; max-width: 52ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
/* .tl-ready is added by the script, so with JS off nothing is hidden. */
.tl-ready .tl-body { opacity: 0; transform: translateY(14px);
  transition: opacity var(--dur-reveal) var(--ease-standard), transform var(--dur-reveal) var(--ease-standard); }
.tl-ready .tl-item.is-in .tl-body { opacity: 1; transform: none; }

/* ---------- Timeline: interim compact treatment ----------
   The page-length timeline was rejected (too long, too heavy). A pinned
   in-section rebuild to the user's spec (line at ~60%, motif to its right,
   year + title + short body on the left) is awaiting approval; until then the
   "compact" rules below render it as a tight dated list. */
/* No rail and no dots: with the rows this tight a spine has nothing to span, and
   the section becomes a dated list rather than a scroll device. */
[data-tl-variant="compact"] .tl-rail, [data-tl-variant="compact"] .tl-dot { display: none; }
[data-tl-variant="compact"] .tl-item { padding-bottom: var(--space-6); }
[data-tl-variant="compact"] .tl-item:last-child { padding-bottom: 0; }
[data-tl-variant="compact"] .tl-year { grid-column: 1 / span 2; position: static; text-align: left; padding-right: 0; }
[data-tl-variant="compact"] .tl-year .y { font-size: var(--text-display-4-size); letter-spacing: var(--text-display-4-ls); }
[data-tl-variant="compact"] .tl-body { grid-column: 3 / span 7; }
[data-tl-variant="compact"] .tl-body h3 { font-size: var(--text-display-5-size); }
[data-tl-variant="compact"] .tl-body > p:not(.tl-meta) { font-size: var(--text-body-size); max-width: 62ch; }

/* ---------- Timeline: pinned masked-slide (JS upgrade; approved 2026-08-17) ----
   timeline.js adds .tl-pin on desktop with motion allowed; without it the
   compact list above is what renders, so JS-off loses nothing. Inside the pin:
   sticky stage, rail at 60% with the --tl-p fill, the JOY motif right of the
   line (whole, one flat value, tone-on-tone; the page's single motif use), and
   the milestone stack in the left zone, right-aligned against the line. The big
   year slides vertically through its own overflow mask; every item's mask box
   coincides, so consecutive years read as one departure-board line. */
.tl-stage { display: contents; }
.tl-motif { display: none; }
.tl-stage-head { display: none; }

/* --tl-line is the single source of truth for the dividing line: the rail sits on
   it, and the text zone, stage heading and motif are all derived from it, so the
   whole stage moves together. 52% is one grid span left of the original 60%,
   giving the enlarged motif more room. */
.tl.tl-pin {
  --tl-line: 52%;
  /* The stage is 100vh minus the header, so HEIGHT is what runs out here — not width.
     The width-driven page scale cannot help: at 1440x900 nothing clips, but at 900x600
     every one of the nine milestones overflowed the stage's overflow:hidden, the worst
     by 40px. So the lockup's offset and the type inside it both answer to vh.
     --tl-lockup-top is solved to give 306px at a 900-tall window (its previous 34vh) and
     150px at 600, capped either side so it neither collapses nor drifts absurdly low. */
  --tl-lockup-top: clamp(120px, calc(52vh - 162px), 340px);
  --tl-year-size: min(var(--text-display-2-size), 9.5vh);
  --tl-body-size: max(15px, min(var(--text-body-lg-size), 2.6vh));
}
/* 62vh per milestone gave 466px of page scroll each — under 5 wheel notches. Paired
   with a 640ms year slide that meant a brisk scroll (~1500px/s) left each year only
   ~310ms on screen, so it began its slide and was replaced before arriving: the year
   read as skipped. 82vh gives 646px each, which at the same speed is ~430ms. */
.tl.tl-pin .tl-wrap { height: calc(var(--tl-n, 8) * 82vh); }
.tl.tl-pin .tl-stage { display: block; position: sticky; top: var(--header-h); height: calc(100vh - var(--header-h)); overflow: hidden; }
.tl.tl-pin .tl-list { position: static; }

.tl.tl-pin .tl-rail { display: block; left: var(--tl-line); right: auto; top: 8%; bottom: 8%;
  -webkit-mask-image: none; mask-image: none; }
.tl.tl-pin .tl-motif { display: block; position: absolute; left: calc(var(--tl-line) + var(--space-8)); top: 50%; transform: translateY(-50%);
  width: min(30vw, 560px); opacity: 0.06; pointer-events: none; }

/* In pin mode the section's h2 goes screen-reader-only (display:none would drop
   it from the a11y tree); the stage carries the visible heading instead. */
.tl.tl-pin .tl-head { position: absolute; width: 1px; height: 1px; margin: 0; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* The heading+year lockup: items stop centering and take a fixed 34vh top, so the
   year renders at a constant Y; the stage heading bottom-anchors 12px above that
   line, sharing the items' right edge — "Our journey" then the year, as one. */
.tl.tl-pin .tl-stage-head { display: block; position: absolute; top: calc(var(--tl-lockup-top) - 12px); transform: translateY(-100%);
  right: calc(100% - var(--tl-line) + var(--space-6)); margin: 0; text-align: right;
  font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--tl-year-size);
  line-height: 1.04; letter-spacing: var(--text-display-2-ls); color: var(--text-primary); }
.tl.tl-pin .tl-item { position: absolute; top: 0; bottom: 0; left: 0; right: calc(100% - var(--tl-line) + var(--space-6));
  display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-end;
  text-align: right; padding: var(--tl-lockup-top) 0 0; }
.tl.tl-pin .tl-dot { display: none; }
/* The mask: overflow hidden, height set by the year's own line box. */
/* align-self resets the base rule's `align-self: start` (meant for the old
   sticky grid column), which in this flex column would pin the year hard left. */
.tl.tl-pin .tl-year { position: static; grid-column: auto; align-self: flex-end; overflow: hidden; padding: 0; text-align: right; }
/* display-2 to match .tl-stage-head exactly: the two must read as one lockup, so
   any size difference between "Our journey" and the year reads as a mistake.
   Weight pinned to light for the same reason. */
.tl.tl-pin .tl-year .y { font-size: var(--tl-year-size); line-height: 1.04; letter-spacing: var(--text-display-2-ls);
  font-weight: var(--fw-display-light);
  color: var(--text-primary); transform: translateY(112%);
  /* 380ms, not 640: the slide has to finish inside the time one milestone is on
     screen, or intermediate years never actually arrive. */
  transition: transform 380ms var(--ease-in-out); }
.tl.tl-pin .tl-item.is-active .tl-year .y { transform: translateY(0); }
.tl.tl-pin .tl-item.is-past .tl-year .y { transform: translateY(-112%); }

.tl.tl-pin .tl-body { grid-column: auto; max-width: 46ch;
  opacity: 0; transform: translateY(18px);
  /* shortened with the year, and the delay with it, so the copy still lands just
     after its year rather than after the next one has started */
  transition: opacity 300ms var(--ease-standard), transform 300ms var(--ease-standard); }
.tl.tl-pin .tl-item.is-active .tl-body { opacity: 1; transform: none;
  transition-delay: 90ms, 90ms; }
/* 11px under an 80px year read as an error rather than a label; body-sm with eased
   tracking matches the quote attribution's treatment. */
.tl.tl-pin .tl-meta { margin-top: var(--space-3); font-size: var(--kicker-size); letter-spacing: var(--kicker-track); }
.tl.tl-pin .tl-body h3 { font-size: var(--text-display-4-size); line-height: var(--text-display-4-lh); letter-spacing: var(--text-display-4-ls); }
.tl.tl-pin .tl-body > p:not(.tl-meta) { max-width: 44ch; margin-left: auto; font-size: var(--tl-body-size); }

/* ---------- About: mission + close (the mist chapter) ----------
   Both share the mist ground with invisible seams, then a short white band keeps
   them off the ink footer. The licences/partners section that used to open this
   chapter was removed at the user's request (2026-08-17). */
/* --mission-plate-h is shared by the plate and the row's min-height, so the image
   can never grow taller than the row and spill out of the mist band: the section's
   own padding stays as its clearance. */
.mission { position: relative; background: var(--bg-alt); padding: var(--space-12) 0;
  --mission-plate-h: clamp(320px, 46vh, 560px); }
/* Direct child, NOT `.mission .row`: the closing buttons now sit inside
   .mission-col wrapped in their own .row, and a descendant selector hands them
   this grid's min-height and gap — which silently pushed them 280px down. */
.mission .shell > .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: center; min-height: var(--mission-plate-h); }
.mission-col { grid-column: 1 / span 5; }
/* Pairs the mission with a portrait plate so this row stops occupying only the
   left half. Uses the unused group architectural image at its native 2:3. */
/* Bleeds to the section's right edge. Positioned against .mission (a full-width
   section) rather than with a 100vw margin trick, which would overshoot by half
   the scrollbar and cause horizontal overflow. Smaller than before: landscape
   crop, capped height. */
.mission-plate { position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: min(52%, 880px); }
.mission-plate .frame { overflow: hidden; }
.mission-plate img { display: block; width: 100%; height: var(--mission-plate-h); object-fit: cover; }
.mission h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
/* :not(.page-kicker) — same specificity trap as .page-note and .tl-meta. */
.mission-col > p:not(.page-kicker) { margin: var(--space-4) 0 0; max-width: 52ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }

/* Closes the mist chapter and the page: two links, no form (a contact form is
   ruled out here; head-office details live on /contact/), and never ink against
   the footer. */
/* Inside .mission-col, so the gap above them is an exact token instead of whatever
   slack the row's min-height happens to leave. Two links, no form — a contact form
   is ruled out here and head-office details live on /contact/. */
.about-close { margin-top: var(--space-8); }
.about-close .row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

/* A short white band so the mist chapter never butts the ink footer. */
/* The white run that ends About — see --page-end in viewport.css.
   Deliberately BIGGER than --page-end. About is the one page that closes on a
   tint edge: the mist band stops hard, and a white stripe bounded above by that
   edge reads smaller than the same white does when it opens straight out of the
   content, which is why 1.5x --section-y measured fine here and still felt tight.
   2x, so the pause survives the edge. The mist cannot give the space up instead
   -- .mission is symmetric 96/96 and trimming only its bottom is the asymmetry
   bug already caught on the company pages -- so the white carries all of it. */
.mist-to-ink { height: calc(var(--section-y) * 2); background: var(--bg-page); }

/* ============================================================================
   COMPANY PAGES — the service chapters (built for /construction/, meant to carry
   to /management/ and /travel/ unchanged).
   Device vocabulary is About's, reused rather than reinvented — with the user's
   amendments (2026-08-17): NO numbered sections or cards, NO one-sided mist
   panel here (About keeps the site's single use), ONE mist chapter total and
   never two mist grounds adjacent, photography instead of the motif, and the
   contact close is nothing but a get-in-touch pill.
   Ground order on /construction/: white opening → white about → mist services →
   white + ink card (photo beside) → white delivery → white pill close → footer.
   ========================================================================== */

/* The white statement that follows the opening: light-on-light before any dark
   ground appears, the same way About runs into its quote. */

/* Section head shared by the chapters below: kicker + heading over 6 columns. */
.sec-head { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); }
.sec-head-inner { grid-column: 1 / span 7; }
.sec-head h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.sec-head-inner > p:not(.page-kicker) { margin: var(--space-4) 0 0; max-width: 56ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }

/* ---------- Image placeholders ----------
   Labelled mist frames standing in for photography that has not landed yet. No
   border (house rule) — the mist ground is what defines the slot. */
.ph { position: relative; background: rgba(199, 199, 197, 0.42); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.ph::after { content: attr(data-ph); font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--text-caption-size); letter-spacing: 0.12em; text-transform: uppercase; color: var(--graphite); text-align: center; padding: 0 var(--space-4); text-wrap: balance; }
.ph img, .ph video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Once a photograph lands in a frame, the slot label goes. */
.ph:has(img)::after, .ph:has(video)::after { content: none; }
/* One tint that reads on white AND on mist — a mist-filled frame disappears
   against the mist chapter and against the scaffolding panel. */

/* ---------- People: the mist chapter that opens the page ----------
   People before plant: who runs the work, then what they run it with. */
.people { background: var(--bg-alt); padding: var(--space-16) 0; }
/* NO top-padding override here, deliberately — .people stays symmetric on --space-16.
   Trimming only its TOP to buy back space was the mistake: it left 128px under the
   content and less above it, so the band's text and image sat high against their own
   tint and read as crowded onto the section before. A band has to be even; what comes
   BEFORE the band is the thing to tune, and that lives in the statement's bottom
   padding above. */
/* One composition, not a head above a row: the copy column runs kicker → heading
   → lead → steps, and the photograph stands beside ALL of it, starting at the
   top of the section. Previously the image began below the section head, which
   left the top-right corner empty and stranded it against the steps. */
.people .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: stretch; }
.people-copy { grid-column: 1 / span 6; }
.people-copy h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); }
.people-lead { margin: var(--space-4) 0 0; max-width: 48ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.people-img { grid-column: 7 / span 6; }

/* ---------- .gal — the two-up gallery (homepage "The Group" device) ----------
   One frame in full, the next cut by the right edge of the screen. Hovering the
   cut frame expands it and halves the first; leaving puts them back. Widths come
   from --h, so both frames are a true 3:4 and the ratio never drifts.
   Built generic on purpose: this is meant to carry to the homepage group
   section once it is approved here. */
.gal { --h: clamp(460px, 44vw, 660px); position: relative; display: flex; align-items: flex-start; gap: var(--space-3); height: var(--h); overflow: hidden;
  /* runs from the column's left edge to the right edge of the viewport */
  width: calc(100% + max(var(--gutter-lg), (100vw - var(--container)) / 2 + var(--gutter-lg))); }
.gal-item { position: relative; margin: 0; flex: 0 0 auto; overflow: hidden; height: var(--h); flex-basis: calc(var(--h) * 0.75);
  transition: flex-basis 900ms var(--ease-standard); }
.gal-item .ph, .gal-item img { position: absolute; inset: 0; width: 100%; height: 100%; }
.gal-item img { object-fit: cover; display: block; }
/* :has lets the hovered SECOND frame drive the FIRST one's width. Only the first
   moves: shrinking it uncovers the second, which is already a full 3:4 frame — so
   the second reaches its true size rather than being stretched past it. */
.gal:has(.gal-b:hover) .gal-a { flex-basis: calc(var(--h) * 0.335); }
.gal-dots { display: none; }

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

}
/* 48 above the list, 32 between items: the ratio still separates the list from the
   lead, but it tightens the four into one set AND lands the copy column flush with
   the gallery beside it, which at 64/40 overshot by 40px. */
.steps { list-style: none; margin: var(--space-6) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.steps h3 { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--text-body-lg-size); line-height: 1.3; color: var(--text-primary); }
.steps p { margin: 6px 0 0; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-size); line-height: var(--text-body-lh); color: var(--text-secondary); }

/* ---------- Machinery: one ink panel, margin to margin, holding four cards ----------
   The panel reaches both container margins but stays a CARD on a white ground —
   never a full-bleed ink band (ground rule). Each discipline is its own card with
   its own photograph, and all four sit on screen together. */
.mach { padding: var(--space-16) 0 var(--space-12); }
.mach-panel { background: var(--bg-inverse); color: var(--text-inverse); padding: var(--space-10) var(--space-8); }
.mach-head { max-width: 60ch; }
.mach-head .page-kicker { color: var(--white-a62); }
.mach-head h2 { margin: var(--space-3) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-4-lh); letter-spacing: var(--text-display-4-ls); color: var(--text-inverse); text-wrap: pretty; }
.mach-head > p:not(.page-kicker) { margin: var(--space-4) 0 0; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-inverse-secondary); text-wrap: pretty; }
/* Four across on desktop; a swipe track on phones (see the media block). */
.cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); margin-top: var(--space-10); }
/* ---------- .amen-list — the ink panel's list variant ----------
   .cards pairs each item with a photograph, which is right for four machinery
   disciplines and wrong for eight amenities we have no photography of: eight
   labelled grey frames would read as eight missing images. A plain list keeps the
   panel doing its job — one ink card on white — and says the eight things without
   pretending to illustrate them. No bullets and no rules: the columns are the
   structure, and the house rule is no hairlines unless they were asked for. */
.amen-list { list-style: none; margin: var(--space-8) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4) var(--space-6); }
.amen-list li { font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.5;
  color: var(--text-inverse); text-wrap: pretty; }
.card { display: flex; flex-direction: column; margin: 0; }
.card .ph { aspect-ratio: 4 / 5; }
/* Neue Haas, not Begum. These are functional labels — 'Security', 'Grounds &
   gardening', 'Planned upkeep' — not editorial headings, and the display serif
   reads wrong at that job. They keep the SIZE fix though: they were 11px uppercase
   labels, smaller than the 16px body underneath them, which was the actual defect.
   19px (--text-body-lg-size) sits above the body and matches .steps h3, so the two
   list-heading families finally agree. */
.card h3 { margin: var(--space-4) 0 0; font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: var(--text-body-lg-size); line-height: 1.3; letter-spacing: 0; color: var(--crisp-white); }
.card p { margin: 10px 0 0; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-sm-size); line-height: var(--text-body-sm-lh); color: var(--text-inverse-secondary); }
/* Placeholders sitting on ink must be lighter than it, not darker. */
.dc-inverse .ph { background: rgba(255, 255, 255, 0.10); }
.dc-inverse .ph::after { color: var(--white-a62); }

/* ---------- The full-bleed image between machinery and scaffolding ---------- */
.bleed-img { display: block; width: 100%; }
.bleed-img .ph { height: clamp(320px, 44vw, 620px); }

/* ---------- Scaffolding: the one-sided mist panel, mirrored right ----------
   About's panel runs from the left; this one runs from the right so the two
   pages never repeat the same composition. Used ONCE on this page. */
/* margin-top, not padding: the panel's background fills the section box, so only
   a margin can put white between the full-bleed image and where the mist begins. */
.scaff { position: relative; margin-top: var(--space-16); padding: var(--space-12) 0 var(--space-16); }
.scaff::before { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 58%; background: var(--bg-alt); }
.scaff .shell { position: relative; }
.scaff .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: center; }
.scaff-img { grid-column: 1 / span 6; }
.scaff-img .ph { aspect-ratio: 4 / 3; }
.scaff-copy { grid-column: 8 / span 5; }
.scaff-copy h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.scaff-copy > p:not(.page-kicker) { margin: var(--space-4) 0 0; max-width: 44ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }

/* ---------- .duo — a plain white two-column section (copy + image) ----------
   The neutral device for a section that needs no ground of its own: used for Real
   Estate on /management/, and available to any page that needs one more block
   without spending the ink card or the one-sided panel. */
.duo { padding: var(--space-16) 0; }
.duo .row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--grid-gap); align-items: center; }
.duo-copy { grid-column: 1 / span 5; }
.duo-copy h2 { margin: var(--space-2) 0 0; font-family: var(--font-display); font-weight: var(--fw-display-light); font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls); color: var(--text-primary); text-wrap: pretty; }
.duo-copy > p:not(.page-kicker) { margin: var(--space-4) 0 0; max-width: 44ch; font-family: var(--font-ui); font-weight: var(--fw-light); font-size: var(--text-body-lg-size); line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.duo-img { grid-column: 7 / span 6; }
.duo-img .ph { aspect-ratio: 4 / 3; }

/* ==========================================================================
   LEGAL PAGES — Terms of Use, Privacy Notice, Cookies Notice
   ==========================================================================
   One shell, three documents. No hero: a legal page is read, not entered, so it
   opens on its own title and gets straight to the text. The three link to each
   other through .legal-switch, because someone who lands on one of them from a
   footer usually wants a different one.
   The measure is the point. --measure is 62ch, which is the site's reading cap
   for a paragraph sitting in a column; a legal page is nothing BUT paragraphs
   stacked for several screens, and at that length the eye needs the shorter
   line. 58ch is about 76 real characters in Neue Haas (ch is the width of "0",
   11.1px against an 8.4px average lowercase glyph). */
[data-page="legal"] .legal { padding: var(--space-10) 0 var(--page-end); }
.legal-head { max-width: 58ch; }
.legal-head h1 { margin: var(--space-2) 0 0; font-family: var(--font-display);
  font-weight: var(--fw-display-light); font-size: var(--text-display-3-size);
  line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls);
  color: var(--text-primary); text-wrap: pretty; }
.legal-updated { margin: var(--space-4) 0 0; font-family: var(--font-ui);
  font-weight: var(--fw-medium); font-size: var(--kicker-size);
  letter-spacing: var(--kicker-track); text-transform: uppercase;
  color: var(--graphite); }
/* The lead sits above the switch and carries the one sentence that says what the
   document is for, so a reader knows within a line whether they are in the right
   one of the three. */
.legal-lead { margin: var(--space-5) 0 0; max-width: 58ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.75;
  color: var(--text-secondary); text-wrap: pretty; }
.legal-switch { display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin: var(--space-8) 0 0; }
/* The current document is a plain label, not a link to itself. */
.legal-switch .is-here { pointer-events: none; background: var(--ink-black);
  color: var(--crisp-white); border-color: var(--ink-black); }
.legal-body { margin: var(--space-10) 0 0; max-width: 58ch;
  counter-reset: legal-section; }
/* Numbered by CSS, not typed into the copy: sections get inserted and removed in
   documents like these, and hand-typed numerals go stale the first time. */
.legal-body h2 { counter-increment: legal-section;
  margin: var(--space-8) 0 var(--space-3);
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-5-size); line-height: var(--text-display-5-lh);
  letter-spacing: var(--text-display-5-ls); color: var(--text-primary); }
.legal-body h2::before { content: counter(legal-section) ". ";
  color: var(--graphite); }
.legal-body > h2:first-child { margin-top: 0; }
.legal-body p { margin: 0 0 var(--space-4); font-family: var(--font-ui);
  font-weight: var(--fw-light); font-size: var(--text-body-lg-size);
  line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.legal-body ul { margin: 0 0 var(--space-4); padding-left: 1.15em; }
.legal-body li { margin: 0 0 var(--space-2); font-family: var(--font-ui);
  font-weight: var(--fw-light); font-size: var(--text-body-lg-size);
  line-height: 1.75; color: var(--text-secondary); }
.legal-body strong { font-weight: var(--fw-medium); color: var(--text-primary); }
.legal-body a { color: var(--text-primary); }
@media (max-width: 900px) and (orientation: portrait) {
  .legal-head, .legal-lead, .legal-body { max-width: none; }
}
@media (max-width: 768px) {
  [data-page="legal"] .legal { padding: var(--space-6) 0 var(--page-end); }
  .legal-head h1 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .legal-body { margin-top: var(--space-8); }
  .legal-body p, .legal-body li { font-size: 17px; line-height: 1.62; }
  .legal-switch .joy-btn { flex: 1 1 auto; justify-content: center; min-height: 44px; }
}

/* ==========================================================================
   CONTACT
   ========================================================================== */
/* The opening follows CAREERS, not the company pages: a half hero with the next
   section starting straight after it. .page-opening — the wrapper that locks
   image + statement to exactly one viewport — was tried here and removed. It is
   the About/company rule and it does not belong on a page whose job is to get
   someone to the form: locked to a screen, the card sat 256px below the fold on
   a 900-tall window and further down on a taller one.
   The base .page-bleed height (clamp(320px, 46vh, 520px)) is already the half
   hero, so dropping the wrapper is the whole change. */

/* The kicker sits on its own grid row rather than inside .col-h. Inside the
   column it pushed the heading down and left the body starting level with the
   KICKER instead of the heading — which is what was wrong. Out here the h1 is
   :first-child, so the stylesheet's existing optical rule already lands its
   first line on the body's.
   row-gap, not the 40px --grid-gap the row carries for columns: the kicker to
   heading step is --space-2 everywhere else on the site. */
[data-page="contact"] .page-statement .row { row-gap: var(--space-2); }
[data-page="contact"] .page-statement .row > .page-kicker { grid-column: 1 / span 5; }

/* The run into the form. 96 + 160 was 256px of white between the statement and
   the card — the statement's own bottom padding plus .apply's careers-tuned
   top padding, neither of which was chosen for this page. Halved so the card
   edge is in view on a normal desktop window rather than a scroll away. */
[data-page="contact"] .page-statement { padding-bottom: var(--space-6); }
[data-page="contact"] .apply { padding-top: var(--space-8); }

/* ---- get in touch --------------------------------------------------------
   The form is the careers application's card and fields, reused by class rather
   than copied: .apply-card, .apply-form, .f-row, .f-field. Two forms on one site
   that look like two different products is the thing worth avoiding. */
/* No CV row here, so the send button is alone and would stretch. */
.f-actions--send { justify-content: flex-end; }

/* ---- our offices --------------------------------------------------------- */
.offices { padding: var(--space-16) 0 0; }
.offices-row { display: grid; grid-template-columns: 7fr 5fr;
  gap: var(--grid-gap); align-items: stretch; margin-top: var(--space-8); }
/* A RESERVED SLOT, not a labelled placeholder frame: the embed drops straight in
   and nothing around it moves. Mist rather than a border — the house rule is no
   hairlines unless they were asked for, and the tint is what defines the area.
   aspect-ratio, not a height: the map has to keep its shape as the column
   narrows, and a fixed height would letterbox it. */
.offices-map { background: rgba(199, 199, 197, 0.42); aspect-ratio: 4 / 3;
  min-height: 320px; }
/* The embed, once a slot is filled. The tint stays underneath as the loading state -
   a lazy iframe paints nothing until it is near the viewport, and an empty white
   rectangle mid-page reads as a fault. border: 0 because a frame arrives with one by
   default, and this system has no hairline it did not ask for. */
.offices-map iframe { display: block; width: 100%; height: 100%; border: 0; }
.offices-info { display: flex; flex-direction: column;
  justify-content: center; gap: var(--space-6); }
.office-label { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--kicker-size); letter-spacing: var(--kicker-track);
  text-transform: uppercase; color: var(--graphite); }
/* > p, class-scoped: a bare descendant rule here would also catch .page-kicker,
   the trap this stylesheet has fallen into before. */
.office-block > p.office-body { margin: var(--space-2) 0 0;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.6;
  color: var(--text-primary); }
.office-body a { color: inherit; text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: border-color var(--dur-base) var(--ease-standard); }
.office-body a:hover { border-bottom-color: var(--ink-black); }

/* ---- for agents: the broker portal --------------------------------------- */
/* Meraas runs three of these (residents, agents, investors). Only agents is kept
   — the other two describe services the group does not offer, and three blocks
   where one is true reads as padding. */
.brokers { padding: var(--space-16) 0 var(--page-end); }
.brokers-row { display: grid; grid-template-columns: repeat(12, 1fr);
  gap: var(--grid-gap); align-items: center; }
.brokers-copy { grid-column: 1 / span 5; }
.brokers-img { grid-column: 7 / span 6; }
.brokers-img img { display: block; width: 100%; height: 100%;
  aspect-ratio: 4 / 3; object-fit: cover; }
.brokers-copy h2 { margin: var(--space-2) 0 0; font-family: var(--font-display);
  font-weight: var(--fw-display-light); font-size: var(--text-display-3-size);
  line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls);
  color: var(--text-primary); text-wrap: pretty; }
.brokers-copy > p:not(.page-kicker) { margin: var(--space-4) 0 0; max-width: 52ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.75;
  color: var(--text-secondary); text-wrap: pretty; }

/* ---- contact: tablet portrait and phone ----------------------------------
   Both bands stack. The map keeps its aspect-ratio so it never letterboxes, and
   the broker image goes BELOW its copy — an image above the heading would push
   the section's own title off the first screen of the band. */
@media (max-width: 900px) and (orientation: portrait) {
  .offices-row, .brokers-row { grid-template-columns: 1fr; }
  .brokers-copy, .brokers-img { grid-column: auto; }
  .offices-info { justify-content: flex-start; }
}
@media (max-width: 768px) {
  .offices { padding: var(--space-8) 0 0; }
  .offices-row { grid-template-columns: 1fr; gap: var(--space-6);
    margin-top: var(--space-6); }
  .offices-map { min-height: 0; aspect-ratio: 3 / 2; }
  .offices-info { gap: var(--space-5); justify-content: flex-start; }
  .office-block > p.office-body { font-size: 17px; line-height: 1.55; }

  .brokers { padding: var(--space-8) 0 var(--page-end); }
  .brokers-row { grid-template-columns: 1fr; gap: var(--space-5); }
  .brokers-copy, .brokers-img { grid-column: auto; }
  .brokers-copy h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size));
    margin-top: 12px; }
  .brokers-copy > p:not(.page-kicker) { margin-top: var(--space-2);
    max-width: none; font-size: 17px; line-height: 1.62; }
  .brokers-img img { aspect-ratio: 3 / 2; }
  /* full-width send button, same as the careers form */
  .f-actions--send .joy-btn { display: flex; width: 100%;
    justify-content: center; min-height: 48px; }
}

/* ==========================================================================
   NEWS — one page: the article at the top, the index below it
   ========================================================================== */
/* No page heading and no intro: the article is the page, so its own headline is
   the h1 and the story starts immediately. There were two headings stacked here,
   the first only restating the nav. */

/* ---- the article --------------------------------------------------------- */
/* scroll-margin-top, not an offset in the script: the header is fixed, so
   without it scrollIntoView parks the first line underneath it. */
.reader { display: block; padding: var(--space-10) 0 var(--space-16);
  scroll-margin-top: calc(var(--header-h) + var(--space-4)); outline: none; }
.reader-date { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--kicker-size); letter-spacing: var(--kicker-track);
  text-transform: uppercase; color: var(--graphite); }
/* Shown only on the newest article (see news-page.js). It sits INLINE with the
   date rather than on a line of its own: a second small uppercase line above the
   first is two kickers stacked, which is the thing that was just removed from
   the top of this page.
   Ink against the date's graphite is what separates them — the flag is the claim
   and the date is the detail — and the middot is drawn rather than typed so it
   cannot end up read aloud as part of the label. */
.reader-flag { color: var(--text-primary); }
.reader-flag::after { content: "\00B7"; margin: 0 0.5em 0 0.55em;
  color: var(--soft-stone); }
/* display-3, down from display-2. At display-2 an Albanian headline ran three
   lines and filled the screen before a word of the article showed — too big for
   a short piece, and it made the page look like a poster rather than a story. */
.reader-title { margin: var(--space-2) 0 0; max-width: 26ch;
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh);
  letter-spacing: var(--text-display-3-ls); color: var(--text-primary);
  text-wrap: pretty; }

/* flow-root, not overflow:hidden — it contains the float without creating a
   scroll container that could clip a focus ring. */
/* FULL WIDTH, chosen after seeing both (2026-08-21). An 840px article column
   was built here — it matched the headline's own measure and held the lines
   under the picture to about 98 characters — and the wider version reads better
   on the page, so the cap came off.
   The known trade, so it does not get "fixed" later without a reason: once the
   text clears the floated picture it runs the full column, roughly 155
   characters a line at 1440. Beside the picture it is comfortable. If that ever
   becomes a problem the answer is the 840px cap, not a paragraph measure —
   capping the paragraphs stops the wrap entirely (see .reader-body p). */
.reader-main { display: flow-root; margin-top: var(--space-8); }
/* THE PRINT SET. The picture floats into the column so the text runs down its
   left and closes underneath it, the way a magazine sets a picture into a page.
   36% — 490px at 1440. Smaller than the half-and-half a float defaults you into,
   which is what makes it read as a picture SET INTO a column rather than two
   equal blocks side by side, and it leaves the text column wide enough to keep
   a proper measure beside it.
   A small top offset lines the image's top edge with the first line's cap
   rather than its line box. */
.reader-figure { float: right; width: 36%; margin: 6px 0 var(--space-6) var(--space-8); }
.reader-figure img { display: block; width: 100%; aspect-ratio: 4 / 3;
  object-fit: cover; }
/* max-width: none is REQUIRED, not tidying. The design system's base.css sets
   p { max-width: var(--measure) } — 62ch — on every paragraph on the site, so
   these were capped at 687px against a 1305px column. The text therefore never
   reached the floated picture and never closed underneath it: it just sat in a
   narrow column beside it, which is the one thing the print set exists to do.
   The float defines the column here; a measure cap on top of it is a second,
   invisible margin. */
.reader-body p { max-width: none;
  margin: 0 0 var(--space-4); font-family: var(--font-ui);
  font-weight: var(--fw-light); font-size: var(--text-body-lg-size);
  line-height: 1.75; color: var(--text-secondary); text-wrap: pretty; }
.reader-body p:last-child { margin-bottom: 0; }

/* ---- the index: a mist band of white cards ------------------------------- */
/* On white the list read as a continuation of the article — there was no line
   between where the story ended and the index began. The tint draws it without
   a rule.
   margin-bottom, not padding: the white run before the footer has to sit OUTSIDE
   the tint. A pause that ends a page comes from white, and mist meeting the ink
   footer would read as a hard edge rather than an ending. */
.news-band { background: var(--bg-alt); padding: var(--space-12) 0;
  margin-bottom: var(--page-end); }
.news-list-head { margin: 0 0 var(--space-6); font-family: var(--font-display);
  font-weight: var(--fw-display-light); font-size: var(--text-display-4-size);
  line-height: var(--text-display-4-lh); letter-spacing: var(--text-display-4-ls);
  color: var(--text-primary); }
.news-list { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(3, 1fr); gap: var(--grid-gap); }
/* A button, not a link: nothing navigates, and an href="#" that moves content
   lies to anything reading the page aloud. The white card is the button. */
.news-btn { display: block; width: 100%; height: 100%; text-align: left;
  padding: 0 0 var(--space-5); background: var(--crisp-white); border: 0;
  cursor: pointer; font: inherit; color: inherit;
  transition: transform var(--dur-base) var(--ease-standard); }
.news-thumb { display: block; overflow: hidden; }
.news-thumb img { display: block; width: 100%; aspect-ratio: 4 / 3;
  object-fit: cover; transition: transform var(--dur-image) var(--ease-standard); }
.news-btn:hover .news-thumb img { transform: scale(1.04); }
/* The card's own gutter, so the type is not flush to a white edge. */
.news-meta { display: block; padding: var(--space-5) var(--space-5) 0; }
.news-btn .news-date { display: block; font-family: var(--font-ui);
  font-weight: var(--fw-medium); font-size: var(--kicker-size);
  letter-spacing: var(--kicker-track); text-transform: uppercase;
  color: var(--graphite); }
.news-btn .news-title { display: block; margin-top: var(--space-2);
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-5-size); line-height: 1.24;
  color: var(--text-primary); text-wrap: pretty; }
.news-read { display: inline-block; margin-top: var(--space-3);
  font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: 12px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-primary);
  border-bottom: 1px solid var(--rule);
  transition: border-color var(--dur-base) var(--ease-standard); }
.news-btn:hover .news-read { border-bottom-color: var(--ink-black); }
/* The open item stays IN the list — pulling it out would make the grid jump
   every time someone reads something — and is marked instead. */
.news-btn.is-open .news-title { color: var(--graphite); }
.news-btn.is-open .news-read { border-bottom-color: transparent; }
.news-btn:focus-visible { outline: 2px solid var(--ink-black); outline-offset: 4px; }

.news-actions { display: flex; justify-content: center; gap: var(--space-4);
  margin-top: var(--space-8); }

@media (max-width: 900px) and (orientation: portrait) {
  .news-list { grid-template-columns: repeat(2, 1fr); }
  .reader-figure { width: 44%; }
}
@media (max-width: 768px) {
  .reader { padding: var(--space-6) 0 var(--space-8); }
  .reader-title { max-width: none;
    font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  /* The float comes off. At 375 a 42% picture leaves a 180px column beside it,
     which is four or five words a line — the print set needs a page, not a
     phone. Image above, text under it, full width. */
  .reader-main { margin-top: var(--space-5); }
  .reader-figure { float: none; width: 100%; margin: 0 0 var(--space-5); }
  .reader-figure img { aspect-ratio: 3 / 2; }
  .reader-body p { font-size: 17px; line-height: 1.62; }

  .news-band { padding: var(--space-8) 0; }
  .news-list { grid-template-columns: 1fr; gap: var(--space-5); }
  .news-btn .news-title { font-size: 22px; }
  .news-actions { flex-direction: column; gap: var(--space-3); }
  .news-actions .joy-btn { width: 100%; justify-content: center; min-height: 48px; }
}

/* ==========================================================================
   VALUES — six alternating one-sided panels
   ========================================================================== */
/* The opening reuses .page-bleed + .page-statement, with the kicker on its own
   grid row so the h1 is :first-child and the stylesheet's optical rule aligns it
   with the body column — same as contact and news. */
/* display-2, NOT the display-3 the statement carries by default. The page title
   and the six value words were both 56px, so the page had no top — six section
   headings and a page heading all tying at one size reads flat, and the reader
   cannot tell which is the page and which is an item on it. The values stay at
   56 (they should be big); the title moves up a step so there are two tiers.
   The companies/About tie at 56 is deliberate and is left alone — those pages
   have one statement above differently-shaped sections, not six headings of the
   same rank. */
[data-page="values"] .page-statement .row { row-gap: var(--space-2); }
[data-page="values"] .page-statement .row > .page-kicker { grid-column: 1 / span 5; }

/* THREE runs, not one, and the ground changes between them: two values on
   white, an ink claim, two on mist, a photograph, two on white. Six rows of the
   same shape on one ground is safe and reads safe — About works because the
   ground moves under the reader (white, ink, mist, image, mist, white) and this
   is that idea at the scale this page can carry.
   Six PANELS was the earlier mistake: each drew its own rectangle with an edge
   at an arbitrary 33%, tied to neither the image nor the grid. Grounds belong to
   the run, never to the row. */
.values-run { padding: var(--space-16) 0; }
/* The last run pays back exactly the field's overhang and nothing else.
   .page-close brings its own --space-16, and at first this was 0 so the close's
   padding was the whole gap — but that measured the gap from the PHOTOGRAPH,
   and the mist field carries --space-6 below it. The closing kicker therefore
   sat 128 from the picture and only 80 from the ground it should answer to.
   --space-6 here is the SAME token the field overhangs by, so the two stay
   locked: change one and the other follows, and the kicker keeps a full
   --space-16 from the bottom of the mist.
   An explicit class, NOT :last-of-type — that pseudo means "last SECTION among
   its siblings", and .page-close is a section too, so once the close existed no
   .values-run matched and the rule silently did nothing. */
.values-run.is-last { padding-bottom: var(--space-6); }
.values-run.is-mist { background: var(--bg-alt); }
/* .page-close carries the closing white now (it ships --page-end of its own), so
   the last run must NOT also pay it or the two stack into a hole. */

/* ---- the ink claim ------------------------------------------------------- */
/* A CARD, not a full-bleed slab: the ground rules prefer the card, and it is
   what About does with the founder's quote. Contained, with white either side,
   so the ink reads as a moment rather than a second page. */
.values-claim { padding: var(--space-6) 0 var(--space-12); }
.claim-card { background: var(--bg-inverse); color: var(--text-inverse);
  padding: var(--space-12) var(--space-10); }
/* display-3, down from display-2. At 80px this was the largest type on the page —
   larger than the page's own title — and a pull-quote outranking the heading it
   sits under is the wrong hierarchy. On ink it does not need the size: the ground
   is the emphasis, so it can tie with the value words and still land. */
.claim-line { margin: 0; max-width: 24ch; font-family: var(--font-display);
  font-weight: var(--fw-display-light); font-size: var(--text-display-3-size);
  line-height: var(--text-display-3-lh); letter-spacing: var(--text-display-3-ls);
  color: var(--crisp-white); text-wrap: pretty; }
.claim-card > p.claim-sub { margin: var(--space-5) 0 0; max-width: 56ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.75;
  color: var(--text-inverse-secondary); text-wrap: pretty; }

/* ---- the photographic breath --------------------------------------------- */
/* Taller than the base .page-bleed hero clamp: this is a pause in the middle of
   a page, and at hero height it would read as a second opening. */
/* margin-TOP is what keeps the mist band off this photograph. The band is the
   run's own background, so it fills the run's padding too — its bottom edge is
   the run's bottom edge, which without this sits flush on the picture. The
   claim card above needs no equivalent: .values-claim's own bottom padding
   already puts 96px of white between the card and where the tint starts. */
.values-breath { height: clamp(260px, 42vh, 460px);
  margin: var(--space-12) 0 var(--space-4); }

/* Top-aligned, not centred. Centring let every row settle at a different height
   depending on how long its paragraph ran, which is the other half of what read
   as messy — six rows, six different relationships between word and picture.
   Aligned to the top they share one line across the whole page. */
.value-row { display: grid; grid-template-columns: repeat(12, 1fr);
  gap: var(--grid-gap); align-items: start; }
.value-row + .value-row { margin-top: var(--space-16); }

/* grid-row: 1 on BOTH, and it is load-bearing. Grid's sparse auto-placement
   never goes backwards: on a flipped row the copy is first in the DOM at column
   8, so the image at column 1 could not be placed to its left and was pushed to
   a second row — measured 346px BELOW the copy instead of beside it. Every
   flipped value was broken this way, which is most of what read as messy.
   Pinning the row makes column order independent of DOM order. */
.value-copy { grid-column: 1 / span 5; grid-row: 1; }
.value-img  { grid-column: 7 / span 6; grid-row: 1; }
/* Flipped by explicit COLUMNS, not `direction: rtl` or `order` — those reverse
   the reading order for a screen reader too, and the value's name should still
   be announced before its picture. */
.value-row.is-flipped .value-copy { grid-column: 8 / span 5; grid-row: 1; }
.value-row.is-flipped .value-img  { grid-column: 1 / span 6; grid-row: 1; }

.value-img img { display: block; width: 100%; aspect-ratio: 4 / 3;
  object-fit: cover; }
/* calc(12px - 0.357em) is the stylesheet's own optical rule: Begum's cap sits
   0.357em below its line-box top, so this lands the word's first line level with
   the top edge of the photograph beside it rather than 20px under it. */
/* display-4, and the ladder is the reason — it was written into this page's own
   stub before it was built:
     heading on a hero image .. 80
     section headline ......... 56   <- what a page OPENING uses
     sub-heading .............. 40   <- what these are
     card headline ............ 28
   The page title and the six value words were both 56, which left the page with
   no top. The fix is NOT to raise the title: 56 is what every statement on the
   site uses (About, the company pages, contact, news) and moving it on one page
   breaks the system. The values are sub-headings inside the page, so they take
   the sub-heading rung and the hierarchy resolves itself.
   calc(12px - 0.357em) is the stylesheet's optical rule: Begum's cap sits
   0.357em below its line-box top, so this lands the word's first line level with
   the top edge of the photograph beside it. */
.value-copy h2 { margin: calc(12px - 0.357em) 0 0; font-family: var(--font-display);
  font-weight: var(--fw-display-light); font-size: var(--text-display-4-size);
  line-height: var(--text-display-4-lh); letter-spacing: var(--text-display-4-ls);
  color: var(--text-primary); text-wrap: pretty; }
/* The lead is the one-line definition the homepage carousel already shows; the
   body is what this page adds. Ink against graphite so the definition reads
   first and the paragraph supports it. */
.value-copy > p.value-lead { margin: var(--space-3) 0 0; max-width: 40ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.55;
  color: var(--text-primary); text-wrap: pretty; }
/* --text-body-lg-size, matching every other panel body on the site: .res-copy
   and .duo-copy on About and the company pages, the page statement, the claim
   card. This was --text-body-size (16 against their 19) — small enough to read
   as a caption rather than as the paragraph it is, and the only body on the site
   set that way.
   Lead and body now share a size, and that is fine: they are told apart by
   COLOUR (ink against graphite) and by leading (1.55 against 1.75), which is how
   the rest of the site separates a definition from the copy under it. Size is
   not the only axis available. */
.value-copy > p.value-body { margin: var(--space-4) 0 0; max-width: 44ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: 1.75;
  color: var(--text-secondary); text-wrap: pretty; }

/* ---- the mist field: one ground across two values -------------------------
   A field of mist down the LEFT of the page, opening at the top of the first
   value and running to the bottom of the second value's photograph. The first
   value's copy sits on it; the second value's photograph sits on it; the first
   value's photograph laps over its right edge.

   MIST, not ink. Ink was built here first and replaced — the page already has
   one ink moment in the claim card, and a second one this large made the card
   the smaller of two blacks rather than the page's one dark beat.

   THREE PAIRS, THREE TREATMENTS, deliberately — the page switches version
   rather than repeating one device:
     Integrity / Excellence      half-width MIST, left, photographs crossing it
     Customer Focus / Account.   the whole run in mist, no field at all
     Innovation / Sustainability half-width MIST, right, holding the copy
   The middle pair therefore keeps .values-run.is-mist doing the work, which is
   why nothing here takes that tint away any more.

   IT HANGS OFF .value-img OF THE GROUND ROW, and that containing block is the
   whole design. Anchored to the row or the run it cannot see the edge it must
   stop on: below roughly 1015px the copy outgrows the picture, the row's bottom
   leaves the photograph, and bottom:0 hangs a ledge of tint out in white. On
   the photograph's own box, bottom:0 IS the photograph's bottom edge, and stays
   so if a paragraph is ever rewritten longer. */
@media (min-width: 1200px) {

  /* The row above has to climb out over the field — BOTH its halves. The field
     lives inside the ground row's image, which is positioned; a positioned
     element paints above the non-positioned content of an earlier sibling, so
     left static the row above is simply covered. The photograph was already
     lifted; the COPY was not, and the first value's words disappeared under the
     tint the moment the field moved to this side. */
  .value-row.is-lifted .value-img,
  .value-row.is-lifted .value-copy { position: relative; z-index: 1; }


  /* z-index: 0 makes a stacking context that traps the ::before's -1 inside
     this photograph's box — behind the picture, still above the page. Left at
     auto the -1 escapes to the root and the run paints over it. */
  .value-row.is-ground .value-img { position: relative; z-index: 0; }
  /* The copy climbs out over the field. No colour change: mist is a LIGHT
     ground, so ink and graphite read on it exactly as they do on white — which
     is the whole reason mist is easier here than ink, and why the inverse
     colour, selection, focus and print rules the ink version needed are gone. */
  .value-row.is-ground .value-copy { position: relative; z-index: 1; }

  .value-row.is-ground .value-img::before {
    content: ""; position: absolute; z-index: -1;
    /* UP one full photograph, one row gap, and one step more — which opens the
       field ABOVE the row above rather than flush with it, in the white the run
       carries as padding. -100% is this photograph's own height and --space-16
       is the same token .value-row + .value-row uses for its margin, so both
       re-solve at every width instead of being maintained; --space-8 is the
       overshoot into the run's own --space-16 of padding, so the field can never
       climb out of the section. */
    top: calc(-100% - var(--space-16) - var(--space-8));
    /* 75%, NOT 0. The field stops a quarter of the way down this photograph so
       the picture BREAKS OUT of its bottom edge — the same move the photograph
       above makes through its right edge, turned ninety degrees. Ending on the
       photograph's own bottom made the field a rectangle the pictures merely sat
       inside; ending inside it makes them cross it.
       A percentage, so it is a quarter of the frame at every width with no
       number to re-solve, and the bottom edge still cannot drift into white:
       75% of a box is always inside that box however tall the copy beside it
       grows. */
    bottom: 75%;
    /* OUT to the viewport edge. The site's own bleed expression: the gutter,
       plus whatever margin the capped shell leaves once the page passes
       --container. No 100vw, so no scrollbar overshoot. */
    left: calc((max(0px, (100vw - var(--container)) / 2) + var(--gutter-lg)) * -1);
    /* One gutter and half a column past this box's right edge, which tucks the
       field under the left edge of the photograph above rather than stopping
       short of it. The box spans columns 1-6, so 8.3333% of it is half a
       column — derived from the grid, not chosen by eye. */
    right: calc((var(--grid-gap) + 8.3333%) * -1);
    background: var(--bg-alt);
  }

  /* THE SECOND PAIR TAKES A DIFFERENT FIELD, and that is the point — the two
     pairs are not meant to repeat one device.
     Integrity/Excellence keeps the half-width ground above: the photograph
     above laps its right edge, the one below breaks out of its bottom.
     Customer Focus/Accountability inverts it. The field runs the FULL width and
     covers the lower value whole — photograph and words together — rising a
     quarter into the photograph above so it touches that one too. One pair is a
     ground the pictures cross; the other is a ground that swallows a value and
     reaches up for the one before it. */
  .value-row.is-ground.is-full .value-img::before {
    /* a quarter into the row above's photograph, not all the way to its top */
    top: calc(-25% - var(--space-16));
    /* the lower value's own bottom. The photograph is the taller of the two
       cells, so its bottom IS the row's bottom and the words are covered with
       it — which is what "cover it completely" means here. */
    bottom: 0;
    /* BOTH edges to the viewport. The box spans columns 1-6 and the shell's
       content width is 2B + g, so -100% - gap lands exactly on the shell's right
       content edge; the bleed then carries it out to the screen. */
    left: calc((max(0px, (100vw - var(--container)) / 2) + var(--gutter-lg)) * -1);
    right: calc(-100% - var(--grid-gap)
      - (max(0px, (100vw - var(--container)) / 2) + var(--gutter-lg)));
  }

  /* ---- the third pair's field: MIST, mirrored ----------------------------
     The mirror of the first pair — same half-width ground, same mist — but on
     the RIGHT, and it holds the lower value's COPY rather than its photograph.
     Anchored to the same .value-img, so the bottom is still that photograph's
     own edge.
     MIST and not ink: the claim card is the page's one dark moment, and a
     second black this size would make the card the smaller of two. It also
     means the copy needs no inversion — ink and graphite read on mist exactly
     as they read on white, so no selection, focus or print fallbacks. */
  .value-row.is-ground.is-right .value-img::before {
    /* a third into the photograph above, as the first pair's field opens above
       its own row */
    top: calc(-33% - var(--space-16));
    /* PAST the photograph's bottom, not level with it. Level, the field and the
       picture ended on one line and the ground read as a box the picture was
       cut to fit; carried under it, the picture sits ON the ground and the
       ground is the larger thing. --space-6 is a step of the scale, not a
       nudge, and it is still measured from the photograph's own edge so it
       cannot drift if the copy grows. */
    bottom: calc(var(--space-6) * -1);
    /* THE GRID SETS THIS EDGE. The box spans columns 1-6, so backing off one
       column from its right lands on the start of column 6 — the leftmost line
       the field can take without running under this row's own photograph. */
    left: calc(83.3333% + var(--grid-gap) * 0.83333);
    /* ALL THE WAY OUT, mirroring the first pair exactly. -100% - gap lands on
       the shell's content edge (2B + g is the shell's content width, an
       identity); the bleed then carries it to the screen, the same expression
       the first pair uses to reach the left edge. Stopping at the content edge
       made this a panel where the other is a bleed — not a mirror. */
    right: calc(-100% - var(--grid-gap)
      - (max(0px, (100vw - var(--container)) / 2) + var(--gutter-lg)));
  }

}

/* Forced colours drop the field's background to Canvas — it would vanish while
   the composition around it did not. Removing it leaves the plain alternation
   rather than a phantom edge. No outline: that would need asking. */
/* No print or inverse-colour block is needed: every field is mist and every word
   on this page keeps its own ink and graphite. */
@media (forced-colors: active) {
  .value-row.is-ground .value-img::before { display: none; }
}

@media (max-width: 900px) and (orientation: portrait) {
  .value-row, .value-row.is-flipped { grid-template-columns: 1fr;
    gap: var(--space-5); }
  .value-copy, .value-img,
  .value-row.is-flipped .value-copy, .value-row.is-flipped .value-img {
    grid-column: auto; grid-row: auto; }
  .value-row + .value-row { margin-top: var(--space-10); }
  .value-img { order: -1; }
  .value-copy > p.value-lead, .value-copy > p.value-body { max-width: none; }
}
@media (max-width: 768px) {
  .values-run { padding: var(--space-8) 0; }
  .values-claim { padding: var(--space-5) 0 var(--space-8); }
  .claim-card { padding: var(--space-8) var(--space-5); }
  .claim-line { max-width: none;
    font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .claim-card > p.claim-sub { margin-top: var(--space-4); max-width: none;
    font-size: 17px; line-height: 1.62; }
  /* The full-bleed photograph is a DESKTOP device and comes off here. Stacked,
     every value already alternates photograph and copy down the whole page, so a
     further full-width picture is one more image in a column of images — it
     breaks nothing because there is no horizontal rhythm left to break. The
     phone gets its variety from the mist/white split alone, which is what it
     had and what worked. */
  .values-breath { display: none; }
  .value-row, .value-row.is-flipped { grid-template-columns: 1fr;
    gap: var(--space-4); }
  .value-copy, .value-img,
  .value-row.is-flipped .value-copy, .value-row.is-flipped .value-img {
    grid-column: auto; grid-row: auto; }
  .value-row + .value-row { margin-top: var(--space-8); }
  .value-copy h2 { margin-top: 12px; }
  /* Photograph first: it introduces the value the way the company pages do. */
  .value-img { order: -1; }
  .value-img img { aspect-ratio: 3 / 2; }
  .value-copy h2 { font-size: clamp(1.75rem, 7.2vw, var(--text-display-4-size)); }
  .value-copy > p.value-lead { margin-top: var(--space-2); max-width: none;
    font-size: 17px; line-height: 1.55; }
  /* 17px, the same step-down .res-copy and .duo-copy take here — not 16. */
  .value-copy > p.value-body { margin-top: var(--space-3); max-width: none;
    font-size: 17px; line-height: 1.62; }
}

/* ---------- Close: an invitation, then the pill ---------- */
.page-close { padding: var(--space-16) 0 var(--page-end); }
/* The pill follows the HEADING'S LAST WORD, with a gap — not the heading's box,
   and not the container edge.
   That distinction is the whole history of this block. Pinned to grid-column 8 it
   floated at a fixed 58% across, attached to nothing. As a flex sibling it sat
   beside the heading's BOX, so a wrapped heading with a short last line left a
   187px hole. Anchored flush right it was at least aligned to something, but on
   the short headings it still sat a long way from the words it belongs to.
   Inline flow is the only arrangement that tracks the text itself: the h2 goes
   inline and the pill becomes an inline-block after it, so it lands wherever the
   sentence happens to end and moves with the copy, the width and the language.
   If the last line is nearly full the pill simply wraps, which is the correct
   behaviour rather than a failure. */
/* Out of the text flow, 2026-08-21. The inline arrangement had a fault the
   static page never showed: :hover grows the pill by 19px (the arrow's 0.95em
   plus its 7px margin), and an inline-block sitting near the end of a full line
   wraps when it grows. The moment it wraps it is no longer under the pointer, so
   it un-hovers, shrinks, wraps back up, and re-hovers — a flicker loop, not a
   one-off jump. No width is safe from it; it just needs a last line with under
   19px of slack, which the fluid heading and the two languages guarantee at SOME
   width.
   A flex item cannot do that: the pill's growth is absorbed by the row's spare
   space instead of re-breaking a line. The headings pay for it by being broken
   deliberately (one sentence per .pc-l) rather than wherever they happen to run
   out of room, so the block is a composed two-line shape and not a rag.
   flex-start, NOT space-between: anchored to the container's right edge the pill
   was already tried and rejected for sitting a long way from the words it
   belongs to. Following the heading's box with a gap keeps that relationship —
   which is what the inline version was protecting — without the text flow. */
.page-close .row { display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-6); }
/* A kicker here takes the whole first line, so the heading and its pill keep
   the exact relationship they have without one — the pill still follows the
   heading, it has just gained a label above it. flex-basis 100% rather than a
   block, because the row is a flex container and a block child would simply
   become a flex item beside the heading.
   row-gap: 0 is load-bearing. The row's `gap` sets BOTH axes, so the 48px meant
   for the space between heading and pill was also being applied between the
   wrapped kicker line and the heading — 48 plus the kicker's own 16 gave 64px
   where every other kicker on the site sits 8 from its heading. The gap is
   split: 48 across, nothing down, and the kicker's margin is the whole
   vertical step. */
.page-close .row { column-gap: var(--space-6); row-gap: 0; }
/* max-width: none is load-bearing, not tidy-up. .page-kicker is a <p>, and the
   design system caps every paragraph at --measure (62ch = 588px). A max-width
   beats flex-basis, so the kicker took 588 of the 1520px row instead of the
   whole line and the heading came up BESIDE it — kicker and pill left, heading
   right. Releasing the cap is what puts the kicker back on its own line. */
.page-close .row > .page-kicker { flex: 0 0 100%; max-width: none;
  margin-bottom: calc(12px - 0.357em); }
/* 26ch is what guarantees the pill its room. Without a cap the heading takes its
   max-content width, and Management's is 1151px in English, 1412 in Albanian
   against a 1360px row — which left NINE pixels of slack after the pill in one
   language and pushed it onto its own line in the other. Nine is inside the 19px
   the pill grows on hover, so the flicker would simply have moved house. A
   capped heading wraps INSIDE its column and the pill never competes with it.
   26ch and not 24: at 24 (777px) Construction's Albanian first sentence, 834px,
   wraps and that block goes to three lines while the English stays at two. 26ch
   is 841px and holds both languages to the same shape. Begum's "0" is 32.4px at
   the 56px display size, so the cap scales with the type rather than the vw.

   The second term is what makes the fix hold at EVERY width rather than at the
   ones that got measured. 26ch alone still let the row get tight as the window
   narrowed — at 1024 the Albanian had 25px of slack and at 940 the pill had
   already wrapped to its own line, so somewhere between them sits a width with
   slack inside the 19px, and the flicker comes back there. Reserving 280px of
   the row (gap 48 + the widest pill, Albanian's 187 hovered, + margin) means the
   heading can never grow into the pill's space: slack stays about 70px however
   narrow it gets. Above ~1150 the 26ch term is the smaller one, so the desktop
   composition is untouched — this only bites where it has to. */
.page-close h2 { margin: 0; flex: 0 1 auto;
  max-width: min(26ch, calc(100% - 280px));
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh);
  letter-spacing: var(--text-display-3-ls); color: var(--text-primary); }
/* One sentence per line. Blocks rather than a <br> so the phone can put them
   back inline and let the heading wrap to its own width. */
.page-close h2 .pc-l { display: block; }
/* No vertical nudge any more. That -0.232em correction existed only because
   `vertical-align: middle` lands on the parent's x-height midpoint; flex
   `align-items: center` centres on the box, which is what it was imitating. */
.page-close .page-cta-row { flex: 0 0 auto; margin: 0; }

/* ---- The desktop drawer scales with the window, it does not step ----
   Driven by viewport HEIGHT: this is a full-height panel and the vertical fit is what
   constrains it — eight rows, an accordion that can add three, and a foot. The clamps'
   floors are the old 760px-tall step and their ceilings the original design sizes, so a
   1440x900 laptop and a 27" each get a menu in proportion to their own window rather than
   one fixed frame with two hard jumps. Replaces the max-height 840/760 blocks, which are
   gone. Gated to >=769px: unscoped, these selectors outrank the phone block's own. */
@media (min-width: 769px) {

}

/* ---------- Responsive (adapted from the homepage's media blocks) ---------- */
/* =========================================================================
   DESKTOP FLUID SCALE  (>=769px only — phones have their own treatment)
   The page scales DOWN with the window and never up: every clamp's ceiling is
   the token's existing value, reached at 1440px and held for anything wider,
   so nothing on a 1440 laptop or a larger monitor changes from before. Below
   that it falls linearly to 78% at 769px.

   Display type, spacing and layout only. Body and small UI text are
   deliberately absent: 16px body has a readability floor, and caption/label/
   nav at 11-13px would reach 9-10px at 78% and stop being legible.

   Each value is clamp(78%, calc(A + B*vw), 100%) where A and B are solved so
   the line passes through 100% at 1440 and 78% at 769. !important because the
   generated _ds token sheet also sets these on :root and is injected at
   runtime, so source order alone cannot be relied on.

   This also closes the 769-1024px gap, where the whole stylesheet previously
   had a single rule and a laptop got the full 1440 design unchanged. */
@media (min-width: 769px) {
  :root {
  /* display type */
  --text-display-1-size: clamp(87.36px, calc(59.12px + 3.6721vw), 112px) !important;
  --text-display-2-size: clamp(62.4px, calc(42.23px + 2.623vw), 80px) !important;
  --text-display-3-size: clamp(43.68px, calc(29.56px + 1.8361vw), 56px) !important;
  --text-display-4-size: clamp(31.2px, calc(21.11px + 1.3115vw), 40px) !important;
  --text-display-5-size: clamp(21.84px, calc(14.78px + 0.918vw), 28px) !important;

  /* The feature panels' headings, 1.3x the card tier - 36.4px at 1440, 29 at 820,
     because the multiplier rides the token's own clamp above.
     1.5x was tried first and came to 42, which is three quarters of the 56px section
     title above it; the two ranks stopped reading as different sizes. 1.3 is 65% of it
     and keeps the step.
     769 AND UP, and that bound matters: the clamp does not exist below 769, where the
     phone takes the design system's flat 28px. A multiplier written at base level
     therefore gave a PHONE 42px - the same as a 1440 desktop, in a 294px panel rather
     than a 552px one, and larger than the 33.5 a tablet got. The scale inverted.
     Phones keep 28, which is already under their own section title. */
  [data-page="residences"] .feat-copy h3 { font-size: calc(var(--text-display-5-size) * 1.3); }
  /* spacing */
  --space-2: clamp(12.48px, calc(8.45px + 0.5246vw), 16px) !important;
  --space-3: clamp(18.72px, calc(12.67px + 0.7869vw), 24px) !important;
  --space-4: clamp(24.96px, calc(16.89px + 1.0492vw), 32px) !important;
  --space-5: clamp(31.2px, calc(21.11px + 1.3115vw), 40px) !important;
  --space-6: clamp(37.44px, calc(25.34px + 1.5738vw), 48px) !important;
  --space-8: clamp(49.92px, calc(33.78px + 2.0984vw), 64px) !important;
  --space-10: clamp(62.4px, calc(42.23px + 2.623vw), 80px) !important;
  /* Height cap on the vertical rhythm. The desktop scale is width-only, so a
     1366x768 screen paid the same ~96px section bands a 2560x1440 does — 25%
     of a short viewport against 13% of a tall one. min() takes whichever is
     smaller, and each vh figure is solved to be INERT at 900 tall and above:
     11vh is 99px at 900 against a 96px token, 14.6vh is 131 against 128,
     18.3vh is 165 against 160. So 1440x900, 1920x1080 and 2560x1440 are
     untouched to the pixel and only genuinely short desktops compress. */
  --space-12: min(clamp(74.88px, calc(50.68px + 3.1475vw), 96px), 11vh) !important;
  --space-16: min(clamp(99.84px, calc(67.57px + 4.1967vw), 128px), 14.6vh) !important;
  --space-20: min(clamp(124.8px, calc(84.46px + 5.2459vw), 160px), 18.3vh) !important;
  --space-25: clamp(156.0px, calc(105.57px + 6.5574vw), 200px) !important;
  --section-y: min(clamp(74.88px, calc(50.68px + 3.1475vw), 96px), 11vh) !important;
  --section-y-loose: min(clamp(99.84px, calc(67.57px + 4.1967vw), 128px), 14.6vh) !important;
  /* layout */
  --gutter: clamp(24.96px, calc(16.89px + 1.0492vw), 32px) !important;
  --gutter-lg: clamp(31.2px, calc(21.11px + 1.3115vw), 40px) !important;
  --grid-gap: clamp(24.96px, calc(16.89px + 1.0492vw), 32px) !important;
  }

}

/* ---------- Tablet portrait (769-900, portrait only) ----------------------
   Measured, not guessed. The 12-column heading|copy split needs roughly 1000px
   to breathe: the copy column is 664px (79 characters) at 1440 and 464px (55)
   at 1024x768 landscape — both fine — but 365px, or 44 characters, at 820x1180.
   Choppy; the mission column is worse at 300px. The other side of the 768
   boundary has the opposite fault: the phone block's single column runs 614px
   at 768, 82 characters, too long to track.

   So this band stacks exactly what the phone block already stacks — same
   selectors, same structure, no new layout — while keeping the desktop's
   typography, gutters and header. Copy would then span the whole shell (91
   characters at 820), so it is capped in `ch` the way .one-group (62ch),
   .mission-col (52ch) and .people-lead (48ch) already are. 52ch, not 68: `ch`
   is the width of "0", 11.1px in Neue Haas against an 8.4px average lowercase
   glyph, so 52ch is ~69 real characters while 68ch would be 753px and cap
   nothing at all. The existing 62/52/48ch values are calibrated the same way.

   Portrait-only on purpose: a landscape PHONE is 844x390 or 932x430, wide
   enough to land in this range, and stacking there would make a 390px-tall
   viewport scroll further for nothing. `orientation: portrait` tells a tablet
   held upright from a phone turned sideways; width alone cannot. */
@media (min-width: 769px) and (max-width: 900px) and (orientation: portrait) {
  /* Two columns here as well: at 820 the four-across list runs to two words a
     column and reads as a table of fragments. */
  .amen-list { grid-template-columns: repeat(2, 1fr); }
  .page-statement .row { grid-template-columns: 1fr; gap: var(--space-5); }
  .page-statement .col-h, .page-statement .col-p { grid-column: auto; }
  .page-statement .col-p > p:not(.page-note) { max-width: 52ch; }
  /* The kicker's grid-column has to be RELEASED here. Contact and values put it
     on its own row with `grid-column: 1 / span 5` so the h1 becomes
     :first-child and the optical rule fires — but that rule sits outside any
     media query, and this band collapses the row to a single 1fr track. A
     5-column span against a 1-column template creates four IMPLICIT columns, so
     the statement rendered as `75px 207px 0 0 0` with the heading and the body
     side by side on a phone. */
  [data-page="contact"] .page-statement .row > .page-kicker,
  [data-page="values"]  .page-statement .row > .page-kicker { grid-column: auto; }

  [data-page="companies"] .page-statement .col-h,
  [data-page="companies"] .page-statement .col-p,
  [data-page="companies"] .page-statement .row.wide-copy .col-h,
  [data-page="companies"] .page-statement .row.wide-copy .col-p { grid-column: auto; }

  /* The mission plate is absolutely positioned against the row on desktop; once
     the row stacks it has to rejoin the flow or it sits on top of the copy. */
  .mission .shell > .row { grid-template-columns: 1fr; gap: var(--space-6);
    align-items: start; min-height: 0; }
  .mission-col { grid-column: auto; }
  .mission-plate { position: static; transform: none; width: auto; grid-column: auto; }
  .mission-plate img { height: auto; aspect-ratio: 4 / 3; }

  /* .quote-band is NOT stacked here: the card and the photograph stay side by side,
     as on desktop, so their shared edge reads the same way on a tablet. */
  .one-group .row, .res-feature .row { grid-template-columns: 1fr; }
  .one-group .col-h, .one-group .col-p,
  .res-copy, .res-img { grid-column: auto; }
  /* Once the row stacks, the copy spans the whole shell but the one-sided mist
     panel was still the 58% it takes beside a photograph — 476px of panel under
     788px of text at 820, so the copy ran off its own ground. At 100% the panel
     clears the text and carries the SAME margin on the right as it does on the
     left: the shell's gutter sits inside it at both ends. Matches the phone. */
  .res-feature::before { width: 100%; }

  .people .row, .mach .row, .scaff .row, .duo .row, .page-close .row {
    grid-template-columns: 1fr; }
  /* The close row went flex at base level (see the note there), which this band
     inherits — and at 820 a two-line display-3 heading leaves nowhere for the
     pill to sit beside it. Stacked, same as the phone. The sentences go back
     inline so the heading wraps to the width it actually has rather than
     holding a desktop break. */
  .page-close .row { display: block; }
  .page-close h2 .pc-l { display: inline; }
  .page-close .page-cta-row { margin-top: var(--space-5); }
  .people-copy, .people-img, .scaff-copy, .duo-copy { grid-column: auto; }
  /* The half-and-half rows stack here too. At 820 each half is 378px, and once the
     panel's 64px padding is off that leaves ~250px for a 40ch paragraph - a column
     too narrow to read. Same treatment as the phone: photograph first, cells
     released, panel padding eased. */
  .feat-row { grid-template-columns: 1fr; min-height: 0; }
  .feat-img, .feat-copy, .feat-row.is-flipped .feat-img, .feat-row.is-flipped .feat-copy {
    grid-column: auto; grid-row: auto; }
  .feat-img { order: -1; }
  .feat-img img, .feat-img .ph { position: static; height: auto; aspect-ratio: 3 / 2; }
  .feat-copy { padding: var(--space-6); }
  .feat-copy > p { max-width: none; }

  /* The plain reset above is (0,1,0); the residences rules are (0,2,0), so they
     survive this media query unless released BY NAME. That is the implicit-column
     trap the values and contact kickers were already caught by. */
  [data-page="residences"] .duo-copy,
  [data-page="residences"] .duo-img { grid-column: auto; grid-row: auto; }

  /* One full-width photograph instead of the two-up frame. Stacked, .people-img
     spans the whole shell, and two 3:4 frames sitting in it read as a pair of
     narrow slivers rather than the paired composition they are on desktop. The
     surviving frame keeps --h, so it crops to a wide band rather than towering
     to 1008px the way a full-width 3:4 would.
     This necessarily covers Construction as well as Management: both carry the
     identical .gal and both are data-page="companies", and the company pages are
     deliberately ONE design — there is no honest selector that separates them.
     .gal-dots are already display:none outside the phone block, so the swipe
     affordance does not appear here. */
  .gal-b { display: none; }
  .gal-item { flex: 1 1 auto; }

  .contact-grid { grid-template-columns: 1fr; gap: var(--space-8); }
}
@media (max-width: 768px) {
  /* The phone spacing scale lives in viewport.css — flat values here would
     switch the fluid scale off for whichever token they name. */
  #site-header { height: calc(var(--header-h-condensed) + var(--sa-t)); }
  /* The mark stays 168x30; the anchor grows to a 44px-tall hit box around it,
     centred, so nothing shifts. */
  #site-header .logo { width: 168px; height: 44px; display: flex; align-items: center; }
  #site-header .logo img { top: auto; transform: none; }
  #site-header .logo img { width: 168px; }
  main { padding-top: calc(var(--header-h-condensed) + var(--sa-t)); }
  /* "Stay informed" goes on phones; the pill and its subscribe button stay — the
     input's placeholder already says what the field is for. Mirrors the homepage. */
  /* The wordmark is CENTRED on phones, capping the footer above a full-width signup pill;
     the blocks below it keep the left margin. Mirrors the homepage. */
  /* The toggle stays absolute, bottom-right of the contact column — beside the last
     address line rather than stranded on its own row under it. */
  /* A section label and the list under it are ONE block: 32px was the value this footer
     spends BETWEEN blocks, so both labels floated free of their own lists. */
  .page-photo-band img { height: clamp(268.8px, calc(52.95vw + 4.709vh + 48.44px), 320px); }
  .page-hero h1 { font-size: clamp(2.25rem, 10vw, var(--text-display-2-size)); }
  .news-featured img, .article-figure img { height: clamp(268.8px, calc(52.95vw + 4.709vh + 48.44px), 320px); }
  .news-featured .below { flex-direction: column; align-items: flex-start; gap: var(--space-4); }
  .news-featured h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .news-card-x { flex-basis: calc((100% - var(--space-3)) / 1.2); }
  .article-hero h1 { font-size: clamp(2.25rem, 10vw, var(--text-display-2-size)); }
  .contact-grid { grid-template-columns: 1fr; gap: var(--space-8); }
  .contact-row2 { grid-template-columns: 1fr; }
  .page-bleed { height: clamp(240px, 38vh, 380px); }
  /* No one-viewport lock on phones: the statement alone can exceed the screen, and
     100vh is unreliable against mobile browser chrome. Specificity has to match the
     desktop rules to undo them. */
  .page-opening { display: block; min-height: 0; }
  .page-opening .page-bleed { position: relative; flex: none; height: clamp(240px, 38vh, 380px); min-height: 0; }
  .page-opening .page-bleed img, .page-opening .page-bleed video { position: static; }
  .page-statement .row { grid-template-columns: 1fr; gap: var(--space-5); }
  /* Only bites near the top of this block's range: at 768 the single column ran
     614px / 82 characters. At 375 the column is 335px, so the cap never applies.
     52ch, matching .mission-col: `ch` is the width of "0", and in Neue Haas that is
     11.1px against an average lowercase glyph of 8.4px — so 52ch lands at ~69 real
     characters. A literal 68ch would be 753px and cap nothing. */
  .page-statement .col-p > p:not(.page-note) { max-width: 52ch; }
  .page-statement .col-h, .page-statement .col-p { grid-column: auto; }
  .page-statement h1, .page-statement h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  /* The kicker's grid-column has to be RELEASED here. Contact and values put it
     on its own row with `grid-column: 1 / span 5` so the h1 becomes
     :first-child and the optical rule fires — but that rule sits outside any
     media query, and this band collapses the row to a single 1fr track. A
     5-column span against a 1-column template creates four IMPLICIT columns, so
     the statement rendered as `75px 207px 0 0 0` with the heading and the body
     side by side on a phone. */
  [data-page="contact"] .page-statement .row > .page-kicker,
  [data-page="values"]  .page-statement .row > .page-kicker { grid-column: auto; }
  .bleed-title { padding-bottom: var(--space-5); }
  .tl-head h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  /* One column: the year stacks above its milestone and stops sticking, so it
     never eats a third of a phone viewport. Both sit clear of the rail. */
  /* One column: the mirror is undone here, so the rail returns to the left edge
     and the year stacks above its milestone. */
  .tl-head { display: block; }
  .tl-head-inner { padding-left: 28px; }
  /* Rail back to the left edge, so the dot must flip sides with it. */
  .tl-rail { left: 0; right: auto; }
  .tl-dot { left: -4px; right: auto; }
  .mission .shell > .row { grid-template-columns: 1fr; gap: var(--space-6); }
  .mission-col { grid-column: auto; }
  /* Was missing: this h2 kept its full 56px on phones, 1.75x the page's own h1,
     and the Albanian ran to six lines. Matches its siblings above. */
  .mission h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .res-copy h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .one-group h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .mission .shell > .row { align-items: start; min-height: 0; }
  /* Back into flow on one column; the bleed is a desktop device. */
  .mission-plate { position: static; transform: none; width: auto; grid-column: auto; }
  .mission-plate img { height: auto; aspect-ratio: 4 / 3; }
  [data-tl-variant="compact"] .tl-year, [data-tl-variant="compact"] .tl-body { grid-column: auto; }
  .quote-band .row, .one-group .row, .res-feature .row { grid-template-columns: 1fr; }
  .q-card, .q-img, .one-group .col-h, .one-group .col-p, .res-copy, .res-img { grid-column: auto; }
  .q-card { min-height: 0; padding: var(--space-8) var(--space-4); }
  .q-img .frame { height: auto; }
  .q-img img { position: static; height: auto; aspect-ratio: 4 / 5; }
  .res-feature::before { width: 100%; }
  /* .joy-btn's shared padding yields 41px, under the 44px touch floor. Most pages set
     their own 48px on the CTA that matters; these are the ones that would otherwise be
     left at 41. The contact form's submit is the important one — a real button people
     have to hit, not a decorative pill inside a card link. */
  .about-close .joy-btn, .contact-form button[type="submit"] { min-height: 44px; }
  .q-card { --q-fs: clamp(1.5rem, 5.36vw, var(--text-display-4-size)); }
  .q-card .quote { max-width: none; }
  .tl-item { grid-template-columns: 1fr; gap: var(--space-3); padding-bottom: var(--space-12); }
  /* relative, NOT static: the dot is absolute, so static would hand it the
     .tl-wrap containing block and stack every dot at the top of the list. */
  .tl-year { grid-column: auto; grid-row: auto; position: relative; top: auto; text-align: left; padding-left: 28px; padding-right: 0; }
  .tl-year .y { font-size: 40px; }
  .tl-body { grid-column: auto; grid-row: auto; padding-left: 28px; }

  /* ---- PHONE PASS (company pages) ----
     Reading sizes, not desktop sizes: body drops from 19/33 to 17/28, headings
     step down, and each section gets an image crop chosen for a narrow screen
     instead of inheriting the desktop one. */
  .page-statement .col-p > p:not(.page-note),
  .sec-head-inner > p:not(.page-kicker),
  .scaff-copy > p:not(.page-kicker), .duo-copy > p:not(.page-kicker) { font-size: 17px; line-height: 1.62; }
  .mach-card > p:not(.page-kicker) { font-size: 17px; line-height: 1.6; }
  /* size + tracking now come from --kicker-size / --kicker-track (viewport.css) */
  /* The hero band sits closer to 16:9 on a phone — a wide desktop strip turns
     into a letterbox at 430px, and this source is portrait anyway. */
  .page-opening .page-bleed { height: clamp(260px, 34vh, 340px); }
  .bleed-title { padding-bottom: var(--space-4); }
  /* 80vh is a desktop figure — on a phone it would eat the screen before a word
     of copy showed. The band grows a little; the name still leads the headline. */
  [data-page="companies"] .page-opening,
  [data-page="about"] .page-opening { display: block; min-height: 0; }
  [data-page="companies"] .page-statement,
  [data-page="about"] .page-statement { display: block; }
  [data-page="companies"] .page-opening .page-bleed,
  [data-page="about"] .page-opening .page-bleed { height: clamp(300px, 42vh, 400px); min-height: 0; aspect-ratio: auto; max-height: none; }
  [data-page="companies"] .bleed-title .shell { display: block; }
  /* Fluid, not a flat 36: section headings on phones are clamp(2rem, 8.5vw, 56) and
     GROW with the screen, so a fixed size was overtaken by them — at 430 the section
     heading (36.6) had passed the name over the photograph (36) and the hierarchy ran
     backwards.
     10vw, not the 11.8 that would mirror desktop's 1.43 ratio. Ratio is the wrong
     thing to optimise here: a phone shell is narrow enough that matching desktop's
     PROPORTION made the name span 88% of the screen against 44% on desktop — twice as
     dominant, sitting across the whole photograph. 10vw lands it near 75% and keeps a
     1.17 step over the section heading, which is enough to rank them without the name
     swallowing the image. Desktop is untouched; this block is phones only. */
  [data-page="companies"] .bleed-title h1 { grid-column: auto;
    font-size: clamp(2.25rem, 10vw, var(--text-display-2-size)); }
  [data-page="companies"] .page-statement .col-h { grid-column: auto; }
  /* .wide-copy is a DESKTOP split. Its selector outweighs the one-column reset
     above, so it has to be undone explicitly or the phone renders a 5-column
     grid with the headline crushed into a sliver. */
  [data-page="companies"] .page-statement .row.wide-copy .col-h,
  [data-page="companies"] .page-statement .row.wide-copy .col-p { grid-column: auto; }
  [data-page="companies"] .page-statement { padding: var(--space-8) 0 var(--space-6); }
  /* Company-page chapters on phones: one column, and each section keeps its own
     crop rather than inheriting the desktop one. */
  .sec-head { display: block; }
  .sec-head-inner { grid-column: auto; }
  .sec-head h2, .people-copy h2, .scaff-copy h2, .duo-copy h2, .page-close h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  /* stacked on phones: side by side, the pill would be squeezed into a corner
     beside a heading that already needs the full width */
  .page-close .row { display: block; }
  .page-close h2 { max-width: none; }
  /* inline again: at 375 the desktop's one-sentence-per-line break would set
     two long lines that wrap anyway, so let the width decide */
  .page-close h2 .pc-l { display: inline; }
  .page-close .page-cta-row { margin: var(--space-5) 0 0; }
  .people { padding: var(--space-8) 0; }

  /* grid, not block. These rows already declared grid-template-columns AND a gap, but
     display:block discards both — so the stack rendered with the children touching and
     the scaffolding image sat flush against its own kicker. One column of grid stacks
     them exactly as block did, and the gap that was always intended finally applies. */
  .people .row, .mach .row, .scaff .row, .duo .row, .page-close .row { display: grid; grid-template-columns: 1fr; gap: var(--space-3); }
  /* the close heading and its pill were still sitting on the 12-column grid,
     which left the button 153px wide instead of thumb-width */
  .page-close h2, .page-close .page-cta-row { grid-column: auto; }
  /* two 4:5 frames side by side are ~177px each on a phone — unreadable. One up. */
  .people-copy, .people-img, .mach-card, .mach-side, .scaff-img, .scaff-copy, .duo-copy, .duo-img { grid-column: auto; }
  [data-page="residences"] .duo-copy,
  [data-page="residences"] .duo-img { grid-column: auto; grid-row: auto; }
  /* .duo .row becomes a flex COLUMN here but keeps align-items: center from the base
     rule, so an item with no intrinsic width shrinks to its content rather than
     filling the column. The copy only looks right because its paragraph fills the
     width; the media slot collapsed to 213 of 353. stretch is the fix. */
  [data-page="residences"] .duo .row { align-items: stretch; }
  /* One column for the details here - two of them in a 353px shell would be shreds -
     and the map keeps its wide band rather than reverting to contact's 3/2. */
  /* Stacked, photograph first - the same order every paired block on this site takes
     on a phone. The named grid cells have to be RELEASED, or a 2-column placement
     survives into a 1-column template and invents columns. */
  .feat-row { grid-template-columns: 1fr; min-height: 0; }
  .feat-img, .feat-copy, .feat-row.is-flipped .feat-img, .feat-row.is-flipped .feat-copy {
    grid-column: auto; grid-row: auto; }
  .feat-img { order: -1; position: relative; }
  .feat-img img, .feat-img .ph { position: static; height: auto; aspect-ratio: 3 / 2; }
  .feat-copy { padding: var(--space-5) var(--space-4) var(--space-6); }
  .feat-copy > p { max-width: none; font-size: 17px; line-height: 1.62; }
  .feat-rows { margin-top: var(--space-5); }
  [data-page="residences"] .office-block > p.office-body { max-width: none; }
  /* The phone header is the CONDENSED token, so the hero subtracts that instead —
     otherwise the film would end 20px past the fold on every phone. */
  [data-page="residences"] .page-bleed {
    height: calc(100svh - var(--header-h-condensed) - var(--sa-t)); }
  /* The same 1.5x, on the phone's own clamp rather than the desktop token - the
     phone name is deliberately a smaller share of its screen (see the note on the
     companies rule above). At 390 that is 58px, and "The Apartments" breaks to two
     lines, which is what the missing nowrap is for. */
  [data-page="residences"] .bleed-title h1 {
    font-size: calc(clamp(2.25rem, 10vw, var(--text-display-2-size)) * var(--hero-name-scale)); }
  /* The kicker keeps its step over the status here too, just smaller. */
  [data-page="residences"] .hero-kicker { font-size: 15px; }
  /* The status is the one line long enough to reach the floating contact button,
     which is fixed bottom-right and sits over everything. Measured at 390: the
     button occupies the last 64px of the viewport and the Albanian line ran 10px
     INTO it (English cleared by 3, which is touching). Reserving the button's
     footprint plus a gap makes the line wrap before it rather than under it - the
     name and kicker are short enough never to get there. */
  [data-page="residences"] .hero-status,
  [data-page="residences"] .bleed-title h1 { padding-right: 56px; }
  /* The name needs the same reservation, and only found out it did after the long
     name was scaled DOWN: at 47px "The Apartments" is 350px and fits a 353px column
     on one line, so instead of wrapping it ran 43px under the button. With the
     gutter reserved it breaks to two lines again, which is what it did at the larger
     size. "The Villas" is 267px and stays on one line either way. */
  /* Photo first, introducing the block it belongs to. */
  .people .row { display: flex; flex-direction: column; }
  .people-img { order: -1; }
  .steps { gap: var(--space-3); }
  /* The ink panel keeps its left margin and bleeds off the RIGHT only, so it reads as
     a card running off the edge of the page rather than a full-width band. Same
     one-sided device .gal and the news track already use, and the same reason the
     desktop .scaff panel covers 58% from one side.
     Only the right margin is pulled back, and it matches the shell's own expression —
     max(--gutter-lg, safe inset), not the token alone, or a notched phone in landscape
     would leave a sliver of white outside the bleed.
     The bottom padding on .mach is what separates the panel from the full-bleed
     photograph beneath it; at 0 the two blocks collided with no break. space-6 against
     the section's space-8 top mirrors desktop's 96-under-128. */
  .mach { padding: var(--space-8) 0 var(--space-6); }
  .mach-panel { padding: var(--space-6) var(--space-4);
    margin-right: calc(-1 * max(var(--gutter-lg), var(--sa-r))); }
  .mach-head h2 { font-size: clamp(2rem, 8.5vw, var(--text-display-3-size)); }
  .mach-head > p:not(.page-kicker) { margin-top: var(--space-2); }
  /* The four cards become a swipe track with dots, one card full and the next
     peeking — same behaviour as the people gallery. */
  .cards { display: flex; margin-top: var(--space-5); gap: var(--space-2);
    width: calc(100% + var(--space-4)); overflow-x: auto; scroll-snap-type: x mandatory;
    scrollbar-width: none; -ms-overflow-style: none; }
  .cards::-webkit-scrollbar { display: none; }
  /* The list does NOT become a track. .cards does because each card carries a
     photograph; these are one- and two-word labels, so a track would be four
     screens of almost nothing. Two columns fit at 375 and read as a block. */
  .amen-list { grid-template-columns: repeat(2, 1fr);
    margin-top: var(--space-5); gap: var(--space-3) var(--space-4); font-size: 17px; }
  .card { flex: 0 0 78%; scroll-snap-align: start; scroll-snap-stop: always; }
  .card h3 { margin-top: var(--space-3); }
  .bleed-img .ph { height: clamp(260px, 70vw, 420px); }

  .scaff { margin-top: clamp(47.04px, calc(9.265vw + 0.8242vh + 8.477px), 56px); padding: clamp(47.04px, calc(9.265vw + 0.8242vh + 8.477px), 56px) 0 var(--space-8); }
  .scaff::before { width: 100%; }
  .scaff-copy > p:not(.page-kicker), .duo-copy > p:not(.page-kicker) { margin-top: var(--space-2); }
  /* image first, as everywhere else on phones */
  .duo .row { display: flex; flex-direction: column; }
  .duo-img { order: -1; }
  .duo { padding: var(--space-8) 0; }
  .page-close { padding: var(--space-8) 0 var(--page-end); }
  .page-close .page-cta-row { margin-top: var(--space-5); }
  .page-close .joy-btn { display: flex; width: 100%; justify-content: center; min-height: 48px; }

  /* ---- PROXIMITY ON PHONES ----
     The parts of one section sit CLOSE so they read as a unit; the air is spent
     BETWEEN sections instead. Previously every gap was the same big number, so a
     heading, its body and its image looked unrelated. Nothing here tries to fit a
     section on one screen — on a phone the next section is always below the fold,
     and that is fine. */
  /* the opening: hero → headline → body is ONE unit */
  [data-page="companies"] .page-statement,
  [data-page="about"] .page-statement { padding: var(--space-4) 0 clamp(47.04px, calc(9.265vw + 0.8242vh + 8.477px), 56px); }
  .page-statement .row { gap: var(--space-2); }
  /* section heads bind to what follows them */

  .sec-head-inner > p:not(.page-kicker) { margin-top: var(--space-2); }
  .sec-head h2, .people-copy h2, .scaff-copy h2, .duo-copy h2 { margin-top: 12px; }
  /* cards: tighter shell, tighter inside */

  /* machinery: photo and card are one unit, and so are the fleet groups */
  .mach .row { gap: var(--space-3); }
  .mach-card > p:not(.page-kicker) { margin-top: var(--space-2); }
  .mach-list { margin-top: var(--space-4); gap: var(--space-3); }
  /* delivery: image, copy and steps are one unit */

  .steps { margin-top: var(--space-5); }
  .people-lead { margin-top: var(--space-2); font-size: 17px; line-height: 1.62; }
  /* The gallery becomes a swipe track: one frame full, the next peeking, dots
     under it. No hover on touch, so the expand interaction is dropped here. */
  .gal { --h: auto; height: auto; width: calc(100% + var(--gutter-lg)); gap: var(--space-2);
    overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; -ms-overflow-style: none; }
  .gal::-webkit-scrollbar { display: none; }
  /* scroll-snap-stop: a fast flick's momentum otherwise carries past the next snap
     point and lands on the one after, moving two cards at a time. */
  .gal-item { flex: 0 0 86%; scroll-snap-align: start; scroll-snap-stop: always; aspect-ratio: 3 / 4; }
  .gal:has(.gal-b:hover) .gal-a, .gal:has(.gal-b:hover) .gal-b { flex-basis: 86%; }
  .gal-item .ph { position: absolute; }
  .gal-dots { display: flex; justify-content: center; gap: 10px; margin-top: var(--space-3); }
  /* The dot still READS as 8px — the button is a 28px transparent box with the
     8px disc drawn as a centred pseudo-element. Same trick as the homepage's
     .joy-dot. Nothing moves: the row's 10px gap is offset by the negative
     margin, so the visual spacing is unchanged. Tapping an 8px target was the
     problem; it is a third of the 24px WCAG 2.5.8 minimum. */
  .gal-dot { appearance: none; border: none; padding: 0; background: none; cursor: pointer;
    width: 28px; height: 28px; margin: -10px; display: flex; align-items: center;
    justify-content: center; border-radius: 50%; }
  .gal-dot::before { content: ""; display: block; width: 8px; height: 8px; border-radius: 50%;
    background: var(--soft-stone); transition: background 260ms var(--ease-standard); }
  .gal-dot.is-on::before { background: var(--ink-black); }
  /* BETWEEN sections — this is where the air goes */

  /* the kicker is gone from the opening, so its heading needs no lead-in margin */
  [data-page="companies"] .page-statement h2 { margin-top: 0; }

  /* ================= THE SAME PHONE RULES, PROJECT-WIDE =================
     Reading sizes and proximity applied to every remaining page (About, News,
     Contact, Values, Careers, articles), so the whole site behaves the way the
     company pages do rather than only the one page we tuned. */

  /* 1 — reading size: desktop body-lg (19/33) is too large on a phone */
  .page-lead,
  .one-group .col-p > p,
  .mission-col > p:not(.page-kicker),
  .res-copy > p:not(.page-kicker),
  .tl-body > p,
  .article-body p,
  .contact-office p { font-size: 17px; line-height: 1.62; }

  /* 2 — proximity: a kicker, its heading and its body are ONE unit */
  .page-hero { padding: var(--space-6) 0 var(--space-4); }
  .page-hero h1 { margin-top: 12px; }
  .page-lead { margin-top: var(--space-2); }
  .page-note { margin-top: var(--space-3); }
  .one-group .row { gap: var(--space-2); }
  /* stays 0: stacked, the card and the image meet on a horizontal edge */
  .quote-band .row { gap: 0; }
  .mission .shell > .row { gap: var(--space-3); }
  .res-feature .row { gap: var(--space-3); }
  .res-copy h2, .mission h2 { margin-top: 12px; }
  .res-copy > p:not(.page-kicker), .mission-col > p:not(.page-kicker) { margin-top: var(--space-2); }
  .page-cta-row, .about-close { margin-top: var(--space-5); }
  .tl-body > h3 { margin-top: 8px; }
  .tl-body > p { margin-top: var(--space-2); }
  .news-featured .below { gap: var(--space-2); }
  .contact-form { gap: var(--space-3); }
  .contact-grid { gap: var(--space-6); }
  .contact-aside { gap: var(--space-5); }

  /* 3 — the air goes BETWEEN sections instead */
  .page-photo-band { padding: 0 0 64px; }
  .quote-band { padding-bottom: var(--space-8); }
  .one-group { padding-bottom: var(--space-8); }
  .res-feature { padding: var(--space-8) 0; }
  .mission { padding: var(--space-8) 0; }
  .news-track-section { padding: var(--space-8) 0; }
  .article-body { padding-bottom: var(--space-8); }

  /* 4 — full-width CTAs on phones, same as the company pages */
  .page-cta-row .joy-btn, .about-close .joy-btn, .article-back .joy-btn {
    display: flex; width: 100%; justify-content: center; min-height: 48px; }

  /* ================= THE PINNED TIMELINE, ON A PHONE =================
     The same stage and the same departure-board year as the desktop, minus the two
     things a 375px screen has no room for: the rail and the motif. Those need the
     52% split to exist, and without the split the year and copy take the full width.

     svh, not vh, throughout. A sticky stage measured in vh is exactly the case iOS
     breaks: the URL bar collapses, 100vh changes mid-scroll, and the pinned stage
     resizes under the reader. svh is the small viewport — the bar-visible height —
     so the stage is stable whether the bar is showing or not. Verified there is no
     clipping or transforming ancestor, which is the other way sticky dies silently.

     The lockup rule still holds: "Our journey" and the year are the same size, or the
     difference reads as a mistake. 48px is the ceiling because "Our journey" has to
     stay on one line — at 375px minus gutters that is about 315px of room, and Begum
     light runs out at roughly 57px. */
  .tl.tl-pin { --tl-line: 100%; --tl-year-size: min(48px, 6.4svh); }
  .tl.tl-pin .tl-wrap { height: calc(var(--tl-n, 8) * 78svh); }
  .tl.tl-pin .tl-stage { top: var(--header-h-condensed);
    height: calc(100svh - var(--header-h-condensed)); }
  /* The rail survives, turned on its side. Vertically it needed the 52% split; as a
     horizontal hairline under the lockup it does the same job — separating the year
     from its milestone — and still carries the progress fill, now on X. */
  .tl.tl-pin .tl-rail { display: block; left: 0; right: 0; width: auto;
    top: calc(var(--tl-lockup-top) + var(--tl-year-size) * 1.04 + 16px); bottom: auto;
    height: var(--hairline); background: var(--rule);
    -webkit-mask-image: none; mask-image: none; }
  /* Horizontal rail here, so the fill grows on Y instead of X — same 2px against a
     1px rule, just rotated with the rail. */
  .tl.tl-pin .tl-fill { inset: calc(-1 * var(--tl-fill-grow)) 0;
    transform: scaleX(var(--tl-p, 0)); transform-origin: left center; }
  /* A WATERMARK, as on desktop — not a small logo. Desktop gives the mark 28% of the
     stage height at 0.06; the phone was giving it 6% at 0.10, which is a different idea
     wearing the same asset. Here it occupies the band under the copy the way the desktop
     one occupies the right column.

     Sized from HEIGHT, not width, because the band it lives in is height-driven and it
     varies nearly fivefold: measured against the longest milestone (2002) the free band
     is 287px at 812, 158px at 667 and only 59px at 568. The flat 84px that was here fit
     the first two and OVERLAPPED the copy by 18px on the third.

     Solved through those measurements: 172px at 812, 100px at 667, 50px at 568 — always
     clearing the longest milestone rather than being hidden on small screens. The first
     curve tried left only 5px between the 2002 copy and the mark at 568, which read as a
     collision; this one leaves 13px. The bottom gap scales for the same reason. */
  .tl.tl-pin .tl-motif { display: block; left: 0; right: auto; top: auto;
    bottom: clamp(14px, 3.4svh, 32px);
    width: clamp(48px, calc(50svh - 234px), 190px);
    transform: none; opacity: 0.06; }
  /* clears the hairline: 16px above it from the year, 16px below it to the meta */
  .tl.tl-pin .tl-body { margin-top: var(--space-2); }
  /* left-aligned: the desktop's right alignment exists to sit against the rail, and
     with no rail there is nothing for it to align to */
  /* Solved against height, like the desktop lockup: 195px at an 812-tall phone and
     95px at 568. A flat 24svh held 136px there, which pushed the longest milestone
     past the stage's overflow:hidden and cut its last line off. */
  /* Raised, to give the frame something at both ends rather than a block of copy
     floating in white: the lockup sits high, the motif closes the bottom. */
  .tl.tl-pin { --tl-lockup-top: clamp(64px, calc(28svh - 100px), 150px); }
  .tl.tl-pin .tl-item { left: 0; right: 0; align-items: flex-start; text-align: left;
    padding: var(--tl-lockup-top) 0 0; }
  .tl.tl-pin .tl-year { align-self: flex-start; text-align: left; }
  .tl.tl-pin .tl-stage-head { left: 0; right: auto; text-align: left;
    top: calc(var(--tl-lockup-top) - 10px); }
  /* padding-left: 28px comes from the compact phone rules, where it clears the rail.
     Pin mode has no rail, and it was setting the copy 28px in from the year above it. */
  .tl.tl-pin .tl-body { max-width: none; margin-left: 0; padding-left: 0; text-align: left; }
  .tl.tl-pin .tl-body > p:not(.tl-meta) { max-width: none; margin-left: 0; font-size: 17px; }
  .tl.tl-pin .tl-body h3 { font-size: 25px; }
  .tl.tl-pin .tl-meta { margin-top: var(--space-2); }

}

/* ---------- Reduced motion (mirrors the homepage block) ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .joy-btn, .joy-btn__icon { transition-duration: 1ms; }
  .joy-btn__icon { transform: none; }
  #site-header { transition-duration: 1ms !important; transition-delay: 0ms !important; }
  /* timeline.js already short-circuits to the finished state; these guard the
     case where the preference changes after load. */
  .tl-ready .tl-body { opacity: 1 !important; transform: none !important; }
  .tl-fill { transform: scaleY(1) !important; }

}

/* ============================================================================
   CAREERS
   Sequence: half hero · opening + cards · positions · apply (ink card) · close.

   This page BREAKS the one-screen opening deliberately. About and the company
   pages lock image + statement to exactly one viewport so nothing below shows on
   load; careers must do the opposite — show it has something to offer before it
   asks for anything — so the hero is about half the fold and the cards start
   immediately under the heading. Reference: meraas.com/en/handover-financing,
   a 680px banner on a 900px viewport with its heading 30px below it.
   ========================================================================== */

/* ~half the fold, against the full-height opening the other pages use. The base
   .page-bleed clamp is already close; this pins the vh term to a true half and
   caps it so a tall monitor does not turn it back into a full-screen banner. */
[data-page="careers"] .careers-hero { height: min(clamp(280px, 50vh, 560px), 62svh); }

/* ---- opening: heading | body, then cards, in ONE section ----------------- */
/* Top padding is the whole point. As two sections this was 92 + 128 = 220px of
   air between the heading and the cards; the reference runs 30. --space-5 keeps
   the kicker clear of the photograph without opening a hole. */
.careers-open { padding: var(--space-5) 0 0; }
.careers-open .row { display: grid; grid-template-columns: repeat(12, 1fr);
  gap: var(--grid-gap); align-items: start; }
/* The SAME split the company pages use — col-h 1/span 6, col-p 7/span 6. I had
   pushed the paragraph out to 8/span 5, which is nothing the rest of the site
   does and read as a shoved-right column.
   The heading takes six because of the ALBANIAN, not the English: "Why Work With
   Us" sits at two lines either way, while "Bëhu pjesë e skuadrës" breaks to FOUR
   at span 5 (548px) and settles at two at span 6. The wider column is for the
   language that is not the fallback — easy to miss when checking only the
   English. Re-measure both if either is reworded. */
.careers-open .col-h { grid-column: 1 / span 6; }
.careers-open .col-p { grid-column: 7 / span 6; }
/* --space-2 is the kicker lead-in the homepage's section headers use, and what
   .page-hero h1 / .sec-head h2 already run. Careers was the one outlier at
   --space-3 — a leftover from before 3d98a1f pulled its kickers. */
.careers-open h1 { margin: var(--space-2) 0 0;
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh);
  letter-spacing: var(--text-display-3-ls); color: var(--text-primary); }
/* > p:not(...) — a bare descendant rule here would also capture .page-kicker,
   the trap this stylesheet has fallen into before. */
.careers-open .col-p > p { margin: 0; max-width: 52ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-lg-size); line-height: var(--text-body-lg-lh);
  color: var(--text-secondary); }

/* ONE ink card, four reasons, hairline-divided.
   Every boxed version of this failed the same way — mist cards on white, then a
   mist ground with white cards, then white cards carrying photographs — because
   each was one more variation of the card treatment the positions list already
   owns. Setting the type straight onto a single dark field stops the competition.

   CONTAINED, not full-bleed: the ground rules prefer an ink CARD to an ink slab,
   and the white to left and right is the same white that keeps this from touching
   the photographic band below. Vertical margins are asymmetric on purpose — more
   underneath, because two full-width grounds meeting with no air between them
   read as one confused block. */
.why { padding: 0; margin: var(--space-12) 0 var(--space-16); }
.why-grid { background: var(--bg-inverse); color: var(--text-inverse);
  padding: var(--space-10); display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-8); }

/* The hairline is the divider, and it is here because it was asked for — this
   site does not get rules by default. White at 0.16 rather than a solid tone: on
   ink a full-strength line reads as a border drawn around a box, which is the
   very thing this version exists to get away from. */
.why-item + .why-item { border-left: 1px solid rgba(255, 255, 255, 0.16);
  padding-left: var(--space-8); }
.why-item { display: flex; flex-direction: column; }
.why-item h3 { margin: 0;
  font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--text-display-5-size); line-height: 1.24;
  letter-spacing: 0; color: var(--crisp-white); }
.why-item > p.why-body { margin: var(--space-4) 0 0;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-size); line-height: var(--text-body-lh);
  color: var(--soft-stone, #C7C7C5); }

/* ---- the questions: the page's one composition ---------------------------- */
/* THE SPINE. Mist to the left holding every word, photograph to the right
   holding none. The questions are right-aligned so they all END on the same
   vertical, a hand's width clear of the picture's edge — so there is a hard
   spine down the middle of the section and a ragged left edge doing the
   composing, and the emphasised phrase always lands closest to the image
   without ever touching it.
   Type CROSSING onto the photograph was built here first and rejected
   (2026-08-21): reading a sentence that changes ground mid-line is work, and it
   made the join look like an accident rather than a decision. Do not re-derive
   it. Before that it was a single blurred photograph under a white wash, which
   at every veil value was either distracting or pointless and cropped to mush on
   a phone.

   The spine lands at 50% with no arithmetic: .shell is centred with symmetric
   gutters, so its CONTENT-box midpoint and the section's full-bleed midpoint are
   the same x. The text column's right edge inside and the photo's left:50%
   outside therefore agree at every width, in both languages, for free. */
/* min-height, because the content no longer sets a usable one. Four stacked
   questions plus a close made this band tall on their own; one passage is three
   lines, and at that height the photograph was squeezed into a letterbox strip
   and stopped being a photograph. svh not vh — this is a box that has to fit,
   and on iOS vh is the URL-bar-hidden viewport.
   The passage is centred in the band rather than sitting at the top of it, so
   the white above and below it is even and the picture reads at full height. */
.ask { position: relative; isolation: isolate; overflow: hidden;
  background: var(--bg-alt);
  min-height: 62svh; display: flex; align-items: center;
  padding: var(--space-16) 0; }
.ask > .shell { width: 100%; }

/* No scrim, no blur, FULL COLOUR. Nothing is set over this any more, so it has
   no legibility job to do and can simply be a photograph — which is the whole
   difference between this and the washed-out version it replaces. */
.ask-photo { position: absolute; top: 0; bottom: 0; left: 50%; right: 0;
  z-index: -1; overflow: hidden; }
.ask-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* gap:0 — the rhythm is set per row below, not by one flat number. A single
   48px gap under a 56px line is tight and under a 30px line is adrift; spacing
   that ignores the type it separates is what makes a varied ladder read as an
   accident. */
/* ONE passage in the mist column, right-aligned so it still ends on the spine.
   This replaced a stacked list of four questions plus a closing one — every line
   an interrogative ending in "?", which read as an interrogation. The qualities
   are unchanged; they are just written as continuous text now, with the load
   carried by the bold anchors rather than by repeating the question form.
   The size ladder went with it. It was solved and it worked, but it only means
   anything when there are separate lines to rank — one passage has one size.
   Recorded because a fair amount of measurement went into it: 56 / 44 / 32 / 30,
   ordered shortest-question-first, gaps at 0.8em of the row above. */
.ask-text { width: 50%; box-sizing: border-box; margin: 0;
  padding-right: var(--space-8); text-align: right;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: clamp(1.35rem, 2.55vw, 38px); line-height: 1.3;
  letter-spacing: -0.012em; word-spacing: 0.06em;
  color: var(--text-secondary); }
/* Graphite passage, ink anchors — so the bold words are what the eye picks up
   when it skims, which is the whole point of writing it this way. 500 is Neue
   Haas Medium, a real file; the family ships no 600. */
.ask-key { font-style: normal; font-weight: var(--fw-medium);
  color: var(--text-primary); }

/* ---- the reveal ----------------------------------------------------------
   Two stages. The passage arrives as one block, then the anchors LIGHT UP one at
   a time — so the sequence reads as the eye finding the words that matter,
   which is the same two-beat idea the questions had, moved onto the bold.
   opacity ONLY on the anchors: they are inline <em>, and transform does not
   apply to inline boxes. Making them inline-block to get transform back would
   stop "reliable and responsible" wrapping mid-phrase in a narrow column. */
.ask-text { opacity: 0; transform: translate3d(0, 18px, 0);
  transition: opacity 700ms var(--ease-standard), transform 700ms var(--ease-standard); }
.ask.is-in .ask-text { opacity: 1; transform: translate3d(0, 0, 0); }

.ask-key { opacity: 0.3;
  transition: opacity 520ms var(--ease-standard); transition-delay: var(--d, 0ms); }
.ask.is-in .ask-key { opacity: 1; }
/* 150ms apart, starting once the passage itself has settled. SIX anchors —
   "reliable" and "responsible" are separately bold, so a fifth step is not
   enough; the last one would inherit --d:0 and fire before all the others. */
.ask-text span > em:nth-of-type(1) { --d: 520ms; }
.ask-text span > em:nth-of-type(2) { --d: 670ms; }
.ask-text span > em:nth-of-type(3) { --d: 820ms; }
.ask-text span > em:nth-of-type(4) { --d: 970ms; }
.ask-text span > em:nth-of-type(5) { --d: 1120ms; }
.ask-text span > em:nth-of-type(6) { --d: 1270ms; }

@media (prefers-reduced-motion: reduce) {
  .ask-text, .ask-key { transition-duration: 1ms; transition-delay: 0ms;
    transform: none; }
}
}

/* ---- positions ----------------------------------------------------------- */
.jobs { padding: var(--space-16) 0 0; }
/* the mist band already puts an edge between them, so the full section gap on
   top of it would total 208px of separation for one transition */
.ask + .jobs { padding-top: var(--space-12); }
/* Section headline, same rung as the opening's h1 — these are the page's three
   real sections and they should carry equal weight. */
.jobs h2 { margin: var(--space-2) 0 0;
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh);
  letter-spacing: var(--text-display-3-ls); color: var(--text-primary); }

/* Three across, and EVERY role shown — no track, no swipe, however many there
   are. A carousel hides the count and makes the reader work a control to find out
   what else exists; someone job-hunting has to be able to scan the whole list at
   once and compare. Four roles land 3 + 1, which is the natural consequence of a
   three-column grid rather than a rule to maintain.
   auto-fit with a floor also stops the last row stretching a lone card across the
   full shell when the count is not a multiple of three. */
.job-list { list-style: none; margin: var(--space-8) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); }
/* Squarer: at two across these were ~656px wide against short copy and read as
   flat bands. Three across is ~430, and the min-height keeps a card with one line
   of description from collapsing out of proportion with its neighbours. */
.job { background: var(--bg-alt); padding: var(--space-6);
  display: flex; flex-direction: column; align-items: flex-start;
  min-height: 340px; }
/* company + sector first: which of the four employers, and which department */
.job > p.job-co { margin: 0; font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--kicker-size); letter-spacing: var(--kicker-track);
  text-transform: uppercase; color: var(--text-primary); }
.job h3 { margin: var(--space-3) 0 0; font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--text-display-5-size); line-height: 1.24; color: var(--text-primary); }
.job > p.job-meta { margin: var(--space-2) 0 0; font-family: var(--font-ui);
  font-weight: var(--fw-light); font-size: var(--text-body-size); color: var(--text-secondary); }
.job > p.job-body { margin: var(--space-3) 0 var(--space-5); max-width: 46ch;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-size); line-height: var(--text-body-lh); color: var(--text-secondary); }
/* margin-top:auto so the button sits on the card's floor whatever the copy length */
.job-apply { margin-top: auto; cursor: pointer; border: 0; background: none; padding: 0;
  font-family: var(--font-ui); font-weight: var(--fw-medium);
  font-size: var(--kicker-size); letter-spacing: var(--kicker-track);
  text-transform: uppercase; color: var(--text-primary);
  transition: opacity var(--dur-fast) var(--ease-standard); }
.job-apply::after { content: " \2192"; }
.job-apply:hover { opacity: 0.6; }
.job-apply:focus-visible { outline: 2px solid var(--ink-black); outline-offset: 4px; }

/* ---- apply: one ink card ------------------------------------------------- */
/* More air above than the other sections take. With the heading INSIDE the card,
   the ink edge is what now sits under the job list — where before there were 128px
   of white, then a 56px heading, then another 64 before the box began, so the dark
   block started about 248px clear of the cards. At 128 it would butt almost
   straight onto them. 160 keeps the two blocks apart without opening a hole. */
.apply { padding: var(--space-20) 0 0; }
.apply-card h2 { margin: var(--space-2) 0 var(--space-8);
  font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-3-size); line-height: var(--text-display-3-lh);
  letter-spacing: var(--text-display-3-ls); color: var(--crisp-white); }
/* This kicker is the only one on the page standing on ink — graphite disappears
   against it, so it takes the same inverse tone .mach-head uses. */
.apply-card .page-kicker { color: var(--white-a62); }
.apply-card { background: var(--bg-inverse); color: var(--text-inverse);
  padding: var(--space-10) var(--space-10) var(--space-8); }

.apply-form { display: flex; flex-direction: column; gap: var(--space-4); }
.f-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.f-field { display: block; }

/* The label is kept for assistive tech and hidden from view — the placeholder is
   the visible label, the way Meraas does it, but a placeholder alone is not a
   label: it disappears the moment someone types. */
.f-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* Measured off meraas.com/en/contact-us: 64px tall, radius 40 (past half the
   height, so a true pill), solid white, no border, 20px padding, 16px type. */
.apply-form input, .apply-form select, .apply-form textarea {
  width: 100%; height: 64px; border: 0; border-radius: 40px;
  background: var(--crisp-white); padding: 0 24px;
  font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-size); line-height: 1.4; color: var(--ink-black);
  transition: box-shadow var(--dur-fast) var(--ease-standard); }
/* the one field that cannot be a pill — a multi-line box with a 40px radius
   scoops its own first and last lines */
.apply-form textarea { height: auto; min-height: 132px; border-radius: 28px;
  padding: 20px 24px; resize: vertical; }
.apply-form select { -webkit-appearance: none; appearance: none;
  padding-right: 52px;
  /* the native arrow goes with the native chrome, so the chevron is drawn here;
     inline SVG, since the CSP on this site allows no external asset */
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%231E1E1E' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 24px center; }
.apply-form ::placeholder { color: #6B6B6B; opacity: 1; }
.apply-form input:focus-visible, .apply-form select:focus-visible, .apply-form textarea:focus-visible {
  outline: 2px solid var(--crisp-white); outline-offset: 3px; }
/* Missing-field marker. The palette has no red and is not getting one, so the
   ring is white — on an ink card it is the loudest thing available, and it says
   "this one" without inventing a status colour the design system does not have.
   Weight and wording carry the meaning here, exactly as elsewhere on the site. */
.apply-form .is-missing { box-shadow: 0 0 0 2px var(--crisp-white); }
.f-file-btn.is-missing { box-shadow: inset 0 0 0 2px var(--crisp-white); }
/* white on white needs a lift on hover, not a colour change */
.apply-form input:hover, .apply-form select:hover, .apply-form textarea:hover {
  box-shadow: 0 0 0 3px rgba(255,255,255,0.18); }

/* Upload and send SHARE a row. Stacked, two full-width controls one under the
   other read as the same button twice — which is what they looked like. */
.f-actions { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-top: var(--space-2); }
.f-file { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.f-file input[type="file"] { position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* a pill too, so it belongs to the same family as the fields it sits under */
.f-file-btn { cursor: pointer; background: transparent; height: 48px;
  display: inline-flex; align-items: center; padding: 0 24px; border-radius: 40px;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.28);
  font-family: var(--font-ui); font-size: var(--kicker-size);
  letter-spacing: var(--kicker-track); text-transform: uppercase; color: var(--crisp-white);
  transition: background var(--dur-fast) var(--ease-standard); }
/* Fills white on hover, with the label flipping to ink — the same inversion the
   joy-btn pills use, so the two controls in this row behave alike even though
   they are still different sizes.
   NOTE, and worth raising again before this page is signed off: this control is
   48px while every field above it is 64. That was deliberate, to keep an upload
   from reading as another text input, and the user has accepted it FOR NOW as a
   known inconsistency rather than as the settled answer. */
.f-file-btn:hover { background: var(--crisp-white); color: var(--ink-black);
  box-shadow: inset 0 0 0 1px var(--crisp-white); }
.f-file input[type="file"]:focus-visible + .f-file-btn { outline: 2px solid var(--crisp-white); outline-offset: 3px; }
.f-file-name { font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: var(--text-body-size); color: var(--soft-stone, #C7C7C5); }

.apply-form > p.f-status { margin: var(--space-2) 0 0; font-family: var(--font-ui);
  font-weight: var(--fw-light); font-size: var(--text-body-size);
  color: var(--soft-stone, #C7C7C5); }
.apply-form > p.f-status.is-err, .apply-form > p.f-status.is-ok { color: var(--crisp-white); }

/* ---- tablet portrait ----------------------------------------------------- */
@media (min-width: 769px) and (max-width: 900px) and (orientation: portrait) {
  .careers-open .row { grid-template-columns: 1fr; gap: var(--space-5); }
  .careers-open .col-h, .careers-open .col-p { grid-column: auto; }
  .careers-open .col-p > p { max-width: 52ch; }
  .why { margin: var(--space-8) 0 var(--space-10); }
  .why-grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-6);
    padding: var(--space-8); }
  /* 2x2: only the right-hand column carries a vertical rule, and the lower row
     needs a horizontal one instead — a left border on item 3 would draw a line
     against nothing. */
  .why-item + .why-item { border-left: 0; padding-left: 0; }
  .why-item:nth-child(even) { border-left: 1px solid rgba(255,255,255,0.16);
    padding-left: var(--space-6); }
  .why-item:nth-child(n+3) { border-top: 1px solid rgba(255,255,255,0.16);
    padding-top: var(--space-6); }
  /* two across on tablet portrait: three at ~340px each turns every job title
     into a two-line wrap */
  .job-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .apply-card { padding: var(--space-8) var(--space-6); }
}

/* ---- phone ---------------------------------------------------------------- */
@media (max-width: 768px) {
  [data-page="careers"] .careers-hero { height: min(clamp(240px, 46svh, 420px), 46svh); }

  .careers-open { padding: var(--space-5) 0 0; }
  .careers-open .row { grid-template-columns: 1fr; gap: var(--space-4); }
  .careers-open .col-h, .careers-open .col-p { grid-column: auto; }
  .careers-open .col-p > p { max-width: none; font-size: var(--text-body-size); }
  /* the same step-down the other pages' section headings get here — 56px is a
     desktop size and simply does not belong on a 375 screen */
  .careers-open h1, .jobs h2, .apply-card h2 {
    font-size: clamp(2rem, 8.5vw, var(--text-display-3-size));
    /* proximity, same 12px the other pages' kicker → heading pairs take here */
    margin-top: 12px; }

  /* CONSIDERED AND DECLINED 2026-08-21: turning these four into a swipe track
     like the homepage's values-track. Stacked they run 1.2 screens at 375, so a
     track would shorten the page — but these four are the page's pitch, and a
     track means most visitors read one of them instead of four. Persuasion wants
     to be scrolled past, not paged through. Same answer for the positions below,
     for a different reason (see .jobs). */
  .why { margin: var(--space-6) 0 var(--space-8); }
  .why-grid { grid-template-columns: 1fr; gap: var(--space-6);
    padding: var(--space-6) var(--space-5); }
  /* stacked, the divider has to turn: a vertical rule between rows separates
     nothing, so the four are cut horizontally instead */
  .why-item + .why-item { border-left: 0; padding-left: 0;
    border-top: 1px solid rgba(255,255,255,0.16); padding-top: var(--space-6); }

  /* the converging offsets cannot survive here: at 375 a 6vw travel is 22px and an
     off-centre rest position just reads as broken indentation. Flattened to a
     vertical stagger, which is the same idea at this width. */
  /* THE SPINE TURNS. Half a phone is 167px and holds no line worth reading, so
     the vertical split cannot survive — but the idea can. The photograph stops
     being a right-hand panel and becomes a full-width band at the TOP of the
     section, with the passage under it. Still sharp, still full colour, still
     with no type over it. A backdrop is what read as cheap here, and for a
     specific reason: cover-cropping a wide room into a tall box shows a
     meaningless fragment, and the blur turned that fragment to mush.

     ZERO top padding, and that is the point. The section's ground is mist, so
     ANY padding above the photograph paints a cream stripe across the top of it
     — the picture has to start flush against the section's own edge. The whole
     vertical gap is spent below instead.
     align-items back to stretch: the base centres for the desktop row, and left
     alone in a column that shrinks both the photo and the passage to their
     content width instead of the full screen.
     min-height off: 62svh is there to stop the desktop band collapsing around a
     three-line passage; stacked, the photo already gives the section its height
     and the floor would only add dead mist under the text. */
  /* Same tokens as every other section on this page, not bespoke values, and
     the paragraph now sits in EQUAL air: --space-6 above it (the photo's
     margin) and --space-6 below it (this padding). It was --space-10 below,
     which resolves to 21px against the photo gap's 43 — the mist stopped short
     under the last line while the picture had twice the room above it.
     --space-6 is the same internal step .why uses, so this stays on the page's
     scale rather than inventing a value.
     Top padding stays 0: the ground is mist, so any padding above the
     photograph paints a cream stripe across the top of it. */
  .ask { display: flex; flex-direction: column; align-items: stretch;
    min-height: 0; padding: 0 0 var(--space-6); }
  .ask-photo { order: 1; position: static; left: auto; right: auto;
    height: 42svh; margin: 0 0 var(--space-6); }
  .ask .shell { order: 2; }

  /* Full width and LEFT-aligned: with no photograph beside it there is no spine
     to end on, and right-aligned copy with nothing to its right is just copy
     pushed away from the reading edge. */
  .ask-text { width: 100%; text-align: left; padding-right: 0;
    font-size: clamp(1.25rem, 5.6vw, 1.75rem); line-height: 1.34; }

  .jobs { padding: var(--space-8) 0 0; }
  /* stacked, all of them, still no track — the whole point is that the list can
     be scanned rather than paged through */
  .job-list { grid-template-columns: 1fr; gap: var(--space-3); margin-top: var(--space-6); }
  .job { padding: var(--space-5) var(--space-4); min-height: 0; }

  .apply { padding: var(--space-10) 0 0; }
  .apply-card { padding: var(--space-6) var(--space-4); }
  .apply-card h2 { margin-bottom: var(--space-6); }
  .f-row { grid-template-columns: 1fr; gap: var(--space-3); }
  .apply-form { gap: var(--space-3); }
  /* 16px keeps iOS from zooming the page when a field takes focus */
  .apply-form input, .apply-form select, .apply-form textarea {
    font-size: 16px; height: 56px; padding: 0 20px; }
  .apply-form textarea { height: auto; padding: 16px 20px; }
  .apply-form select { padding-right: 44px; background-position: right 20px center; }
  /* stacked here, so the send button takes the full width rather than sitting in
     a lonely corner beside a wrapped upload control */
  .f-actions { flex-direction: column; align-items: stretch; gap: var(--space-4); }
  .f-file-btn { justify-content: center; }
}
