/* ==========================================================================
   main.css — layout and components. Colours, fonts and rhythm come from
   tokens.css only. Design v3 (DECISIONS 79-80): warm white ground, charcoal type,
   full-bleed photography whitened on the text side, light serif display type, champagne gold hairlines.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; background: var(--bg); }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 300 var(--fs-body)/1.75 var(--font-sans);
  -webkit-font-smoothing: antialiased; overflow-x: clip;
}
img, picture, svg { display: block; max-width: 100%; }
picture img { width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
::selection { background: var(--gold-text); color: var(--bg); }
:focus-visible { outline: 1px solid var(--gold-text); outline-offset: 4px; }
.visually-hidden { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip-link { position: absolute; left: var(--gutter); top: -4rem; z-index: 100; background: var(--ink); color: var(--bg); padding: .6rem 1rem; font: 500 var(--fs-label)/1 var(--font-sans); letter-spacing: .16em; text-transform: uppercase; }
.skip-link:focus { top: 1rem; }
.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter); }

/* ---------- type ---------- */
h1, h2, h3, .statement { font-family: var(--font-display); font-weight: 300; letter-spacing: -.01em; line-height: 1.02; margin: 0; color: var(--ink); text-wrap: balance; }
h1 em, h2 em, h3 em, .statement em { font-style: italic; font-weight: 300; color: var(--gold-em); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); line-height: 1.12; }
p { margin: 0; color: var(--ink-2); max-width: 60ch; }
p + p { margin-top: 1.1em; }
.label, .eyebrow { font: 500 var(--fs-label)/1.4 var(--font-sans); letter-spacing: .22em; text-transform: uppercase; color: var(--gold-text); }
/* rule width and gap are capped by vw so 200% text keeps the label on screen (same as before at 100%); anywhere is the last resort */
.eyebrow { display: flex; align-items: center; gap: min(1rem, 5vw); margin: 0 0 1.5rem; overflow-wrap: anywhere; }
.eyebrow::before { content: ""; flex: none; width: min(2.5rem, 12.5vw); height: 1px; background: var(--gold); }
.eyebrow:empty { display: none; }
.num { font: 400 1.05rem/1 var(--font-display); color: var(--gold-text); letter-spacing: .1em; }
.statement { font-size: var(--fs-statement); line-height: 1.22; max-width: 24ch; }
.statement + p { margin-top: 2rem; }
.text a, .prose a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--gold); text-decoration-thickness: 1px; text-underline-offset: .25em; transition: color .3s; }
.text a:hover, .prose a:hover { color: var(--gold-text); }
/* Reflow at 200% text (WCAG 1.4.10): long unbroken strings such as the email address wrap instead of widening the page.
   Body and contact text may break anywhere; headings only break a word that would otherwise overflow (no change at 100%). */
.email, .text a, .prose a, .layout-prose p, .layout-prose li, .contact dd, .footer-contact li { overflow-wrap: anywhere; }
h1, h2, h3, .statement { overflow-wrap: break-word; }
.hero h1 .w { max-width: 100%; } /* motion splits hero titles into inline-block words; let an over-wide word break instead of being clipped by .hero */

/* ---------- controls ---------- */
.button {
  display: inline-flex; align-items: center; gap: 1rem; padding: 1.05rem 2rem; margin-top: 2.5rem;
  border: 1px solid var(--line-strong); color: var(--ink);
  font: 500 var(--fs-label)/1 var(--font-sans); letter-spacing: .2em; text-transform: uppercase;
  transition: background .5s var(--ease), color .5s var(--ease), border-color .5s var(--ease);
}
.button::after { content: "\2192"; font-size: 1.1em; line-height: 0; transition: transform .5s var(--ease); }
.button:hover { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.button:hover::after { transform: translateX(.35em); }
.text a.button:hover, .prose a.button:hover { color: var(--bg); } /* button hover beats .text a:hover (was gold on dark, 2.82:1) */
.link { display: inline-block; padding-top: 1.6rem; font: 500 var(--fs-label)/1 var(--font-sans); letter-spacing: .2em; text-transform: uppercase; color: var(--ink-2); border-bottom: 1px solid var(--gold); padding-bottom: .45rem; transition: color .3s; } /* inline-block + padding-top: 44px tap target, underline position unchanged (visual opt E2) */
.link:hover { color: var(--ink); }
.email { display: inline-block; margin-top: 2.5rem; padding-top: .75rem; padding-bottom: .3rem; border-bottom: 1px solid var(--gold); font: 300 clamp(1.6rem, 1.2rem + 1.6vw, 2.75rem)/1.1 var(--font-display); color: var(--ink); transition: color .3s; }
.email:hover { color: var(--gold-text); }

/* ---------- header ---------- */
/* The header itself must never carry backdrop-filter, filter, transform, perspective, contain or will-change of those:
   each makes it the containing block of position:fixed descendants, and the phone menu (#site-menu, fixed, inset 0)
   would then shrink to the header box (160px tall, cut off) instead of covering the viewport. The glass sits on ::after. */
.site-header { position: fixed; inset: 0 0 auto 0; z-index: 50; padding-block: 1.5rem; transition: padding .4s var(--ease); }
.site-header::before { content: ""; position: absolute; inset: 0 0 -3rem 0; background: var(--header-fade); pointer-events: none; transition: opacity .4s; }
.site-header::after { content: ""; position: absolute; inset: 0 0 -1px 0; z-index: -1; background: var(--bg-glass); border-bottom: 1px solid var(--line); opacity: 0; pointer-events: none; transition: opacity .4s var(--ease); }
.site-header.is-scrolled { border-bottom: 1px solid transparent; padding-block: .9rem; }
.site-header.is-scrolled::before { opacity: 0; }
.site-header.is-scrolled::after { opacity: 1; -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
.header-inner { position: relative; display: flex; align-items: center; justify-content: space-between; gap: min(2rem, 5vw); }
.brand { min-width: 0; }
/* Reflow at 200% text (WCAG 1.4.4/1.4.10): the logo may shrink inside its box without distorting; capped so it never crowds the menu button. Unchanged at 100%. */
.brand img { height: min(2.25rem, 56px); width: auto; max-width: 100%; object-fit: contain; object-position: left center; }
.site-nav ul { list-style: none; margin: 0; padding: 0; display: flex; gap: 2.75rem; }
.site-nav a { position: relative; display: inline-block; padding: .5rem 0; font: 500 var(--fs-label)/1 var(--font-sans); letter-spacing: .2em; text-transform: uppercase; color: var(--ink-2); transition: color .3s; }
.site-nav a:hover, .site-nav a[aria-current="page"] { color: var(--ink); }
.site-nav a::after { content: ""; position: absolute; left: 0; right: 100%; bottom: 0; height: 1px; background: var(--gold); transition: right .5s var(--ease); }
.site-nav a:hover::after, .site-nav a[aria-current="page"]::after { right: 0; }
.nav-toggle-label { display: none; }
.nav-email { display: none; }
/* em, not px: an em media query follows the browser's text-size setting, so at 200% text the menu collapses
   to the button up to 1720px wide instead of running the links off screen. At 100% this is exactly 860px. */
@media (max-width: 53.75em) {
  /* safe center + overflow-y: at 200% text on a short screen the open menu scrolls instead of cutting off its first and last links */
  .site-nav ul { position: fixed; inset: 0; z-index: 60; display: flex; flex-direction: column; justify-content: center; justify-content: safe center; overflow-y: auto; overscroll-behavior: contain; gap: 0; padding: 6rem var(--gutter) 4rem; background: var(--bg-overlay); opacity: 0; visibility: hidden; transition: opacity .4s var(--ease), visibility 0s .4s; }
  .nav-toggle:checked ~ ul { opacity: 1; visibility: visible; transition-delay: 0s; }
  .site-nav li { border-top: 1px solid var(--line); }
  .site-nav li:last-child { border-bottom: 1px solid var(--line); }
  .site-nav a { display: block; padding: 1.2rem 0; font: 300 2.25rem/1.1 var(--font-display); letter-spacing: 0; text-transform: none; color: var(--ink); }
  .site-nav a[aria-current="page"] { color: var(--gold-text); }
  .site-nav a::after { display: none; }
  /* last line of the phone menu: the email address (DECISIONS 73 ②; visual opt E6). Hidden on desktop and in the footer nav. */
  .site-nav .nav-email { display: block; }
  .site-nav .nav-email a { font-size: 1.25rem; color: var(--gold-text); overflow-wrap: anywhere; }
  .nav-toggle-label { display: block; flex: none; position: relative; z-index: 70; width: min(2.75rem, 56px); height: min(2.75rem, 56px); margin-right: max(-.6rem, -12px); cursor: pointer; }
  .nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after { position: absolute; left: .55rem; right: .55rem; height: 1px; background: var(--ink); transition: transform .4s var(--ease), top .4s var(--ease), background .3s; }
  .nav-toggle-bars { top: 50%; }
  .nav-toggle-bars::before { content: ""; left: 0; right: 0; top: -.4rem; }
  .nav-toggle-bars::after { content: ""; left: 0; right: 0; top: .4rem; }
  .nav-toggle:checked ~ .nav-toggle-label .nav-toggle-bars { background: transparent; }
  .nav-toggle:checked ~ .nav-toggle-label .nav-toggle-bars::before { top: 0; transform: rotate(45deg); }
  .nav-toggle:checked ~ .nav-toggle-label .nav-toggle-bars::after { top: 0; transform: rotate(-45deg); }
  /* Scroll lock while the menu is open. :has() works without JS (Safari 15.4+, Chrome 105+); nav.js adds .menu-open as the
     fallback. Two separate rules on purpose: a browser that cannot parse :has() drops the whole rule it sits in. */
  .menu-open { overflow: hidden; }
  html:has(#nav-toggle:checked) { overflow: hidden; }
}

/* ---------- hero ---------- */
.hero { position: relative; display: flex; align-items: flex-end; overflow: hidden; isolation: isolate; }
/* min-height (not height) so a long title grows the hero instead of running under the fixed header (audit M3) */
/* Heights use svh (the small viewport: browser bars shown), so the budget is the area actually visible on first load;
   each line is preceded by a vh fallback for browsers without svh (Codex review fix 3). Type never uses vh/svh/dvh. */
.hero--home { min-height: max(100vh, 40rem); min-height: max(100svh, 40rem); }
.hero--page { min-height: max(min(72vh, 46rem), 28rem); min-height: max(min(72svh, 46rem), 28rem); }
.hero-media, .hero-media picture, .hero-media img { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero-media img { object-fit: cover; object-position: center 40%; }
.hero-media::after { content: ""; position: absolute; inset: 0; background: var(--hero-fade); }
.hero-text { position: relative; z-index: 1; width: 100%; padding-top: 8rem; padding-bottom: clamp(3rem, 7vw, 5.5rem); }
.hero h1 { font-size: var(--fs-h1-page); max-width: 14ch; }
/* Home title (Codex review fix 2): rem-led sizes, so a larger browser text size enlarges the title (x1.6 or more at a
   32px default) instead of being capped by the viewport. The viewport only picks the tier (px queries, so the tier
   does not move with text size); short screens are handled by padding below and by letting the hero grow.
   Default text size: 390 = 55.6px, 768 = 79px, 1280x720 = 94.7px, 1366x768 = 96.8px, 1440x900 = 115.2px, >=1734 = 124px. */
.hero--home h1 { font-size: calc(2.5rem + 4vw); max-width: 12ch; }
@media (min-width: 640px) { .hero--home h1 { font-size: calc(3.5rem + 3vw); } }
@media (min-width: 1024px) { .hero--home h1 { font-size: min(4.5rem + 3vw, 7.75rem); } }
@media (min-width: 1024px) and (max-height: 820px) { .hero--home h1 { font-size: min(4rem + 2.4vw, 7.75rem); } }
.hero-lede { margin-top: 1.75rem; max-width: 46ch; font-size: 1.1rem; color: var(--ink-2); }
.hero-lede:empty { display: none; }
.hero-cue { position: absolute; right: var(--gutter); bottom: clamp(3rem, 7vw, 5.5rem); z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 1rem; color: var(--ink-3); font: 500 .62rem/1 var(--font-sans); letter-spacing: .3em; text-transform: uppercase; writing-mode: vertical-rl; }
.hero-cue::after { content: ""; width: 1px; height: 4.5rem; background: var(--cue-line); }
.hero--page .hero-cue { display: none; }
@media (max-width: 53.75em) { .hero-cue { display: none; } } /* em like the nav breakpoint: 860px at 100% text; at 200% text the SCROLL cue would sit on the lede (Codex review of 55de967) */
/* Legibility of text over the photo (visual opt V01, V03). The home overlay is .hero-shade (motion.css), page heroes use
   .hero-media::after; the selectors below out-rank both. The photo position and line-art stage are not touched (DECISIONS 39). */
.hero { --gold-text: var(--hero-gold-text); --gold-em: var(--hero-gold-em); } /* eyebrow and italic word sit on a whitened photo (tokens.css) */
.hero h1 { text-shadow: var(--hero-title-glow); }
.hero .eyebrow { text-shadow: var(--hero-eyebrow-glow); } /* the halo lifts the 2-8px round each letter to near --bg: the eyebrow's 4.5:1 floor */
.hero-lede { text-wrap: pretty; }
/* Right edge of the hero text block for --hero-fade-side (tokens.css), >=1024px only. Width of the widest title line =
   k x the title font size (measured, the lines do not re-wrap with text size: home 4.73, services 5.76, about 5.09,
   projects 4.34; rounded up to 4.8 / 5.8); the lede is 46ch of 1.1rem Jost = 29rem. If a hero title is rewritten, re-measure k. */
.hero { --hero-text-w: calc(var(--gutter) + max(var(--hero-title-w, 0px), 29.5rem)); --hero-text-edge: calc(var(--hero-text-w) + max(0px, (100vw - var(--max)) / 2)); } /* edge measured from the hero's left; the text sits in the centred 1440px .wrap */
.hero--home { --hero-title-w: calc(4.8 * min(4.5rem + 3vw, 7.75rem)); }
.hero--page { --hero-title-w: calc(5.8 * var(--fs-h1-page)); }
@media (min-width: 1024px) and (max-height: 820px) { .hero--home { --hero-title-w: calc(4.8 * min(4rem + 2.4vw, 7.75rem)); } }
@media (min-width: 1024px) {
  .hero--home .hero-shade, .hero--page .hero-media::after { background: var(--hero-fade-side), var(--hero-fade); }
  /* Behind the text block only, above the line art: the gold strokes (and the darkest photo pixels) must not sit under
     the letters at full strength. Width follows the text (grows with browser text size), feathered 4rem to the right and
     5rem at the top; the eyebrow starts 7-8rem down, so the type always sits on the full veil. */
  .hero-text::before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; z-index: -1; width: min(100%, calc(var(--hero-text-w) + 4rem)); background: linear-gradient(90deg, var(--hero-text-veil) calc(100% - 4rem), transparent); -webkit-mask-image: linear-gradient(to bottom, transparent, #000 5rem); mask-image: linear-gradient(to bottom, transparent, #000 5rem); pointer-events: none; }
}
@media (max-width: 1023px) {
  .hero--home .hero-shade, .hero--page .hero-media::after { background: var(--hero-fade-narrow); }
  .hero-text::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--hero-fade-text); pointer-events: none; } /* .hero-text is its own stacking context (z-index 1 / 3), so this sits above photo and line art, below the type */
}
@media (max-height: 820px) {
  .hero--home .hero-text { padding-top: 7rem; padding-bottom: 2.75rem; } /* 7rem still clears the 84px header */
  .hero--home .hero-cue { bottom: 2.75rem; }
}
@media (max-width: 639px) {
  .hero-lede { font-size: 1rem; line-height: 1.65; margin-top: 1.4rem; }
  .hero--page .hero-media img { object-position: center 35%; } /* V21 / V03 fallback: lift bright windows out of the text area */
}
/* short landscape phones (844x390, 932x430): bring the home hero close to one screen (visual opt E8) */
@media (max-height: 500px) and (orientation: landscape) {
  .hero--home, .hero--page { min-height: 100vh; min-height: 100svh; } /* one height basis (svh, vh fallback); the hero may grow past it */
  .hero--home .hero-text { padding-top: 5rem; padding-bottom: 2rem; }
  .hero--home h1 { font-size: calc(2.25rem + 1.2vw); } /* 844 = 46.1px, 932 = 47.2px; x1.7 at a 32px default */
  .hero-lede { font-size: 1rem; margin-top: 1rem; }
}

/* ---------- sections ---------- */
.block { margin: 0; }
.sec { padding-block: var(--section); }
.sec--tight { padding-block: calc(var(--section) * .6); }
.sec--flush { padding-block: 0; }
.sec + .sec, .block + .block > .sec:first-child { border-top: 1px solid var(--line); }
.block + .block > .band:first-child, .block + .block > .cta:first-child { border-top: 0; }
.no-hero .page > .block:first-child > .sec:first-child { padding-top: calc(var(--section) * .8 + 5.5rem); }
.sec-head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 1.5rem 3rem; margin-bottom: clamp(2.5rem, 5vw, 4.5rem); padding-top: var(--section); }
.sec-head .num { display: block; margin-bottom: 1.1rem; }
.sec-head h2 { max-width: 18ch; }
.sec-head .link { margin-bottom: .6rem; }
.sec-head--flush { padding-top: 0; }
.eyebrow--center { justify-content: center; }

/* asymmetric statement */
.split { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2.5rem 4rem; align-items: start; }
@media (min-width: 900px) { .split { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); } }
.split-aside { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; } /* wraps only when the label no longer fits (200% text) */
.split-aside .num { padding-top: .2rem; }

/* editorial feature rows */
.features { display: grid; }
.feature { display: grid; gap: 1.75rem; align-items: end; padding-block: clamp(2rem, 4vw, 3.5rem); }
.feature + .feature { border-top: 1px solid var(--line); }
.features .feature:last-child { padding-bottom: var(--section); }
@media (min-width: 900px) {
  .feature { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 2rem min(3rem, 48px); } /* 11 column gaps: capped so 200% text cannot push the row past the viewport */
  .feature-media { grid-column: 1 / 9; }
  .feature-text { grid-column: 9 / 13; padding-bottom: .75rem; }
  .feature:nth-child(even) .feature-media { grid-column: 5 / 13; grid-row: 1; }
  .feature:nth-child(even) .feature-text { grid-column: 1 / 5; grid-row: 1; }
}
.feature-media { display: block; overflow: hidden; aspect-ratio: 3 / 2; background: var(--bg-2); }
.feature-text h3 a { transition: color .3s; }
.feature-text h3 a:hover { color: var(--gold-text); }
.feature-media picture, .feature-media img { width: 100%; height: 100%; }
.feature-media img { object-fit: cover; transition: transform 1.4s var(--ease); }
.feature:hover .feature-media img { transform: scale(1.03); }
.feature-text h3 { margin-top: .9rem; }
.feature-text p { margin-top: 1.1rem; font-size: 1rem; }
.feature-text .meta { color: var(--ink-3); font-size: .9rem; }

/* full-bleed statement band */
.band { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); place-items: center; min-height: min(78vh, 44rem); overflow: hidden; isolation: isolate; text-align: center; padding: var(--section) var(--gutter); }
.band > :not(.band-media) { max-width: 100%; } /* 200% text: cap the centred column so a long heading word breaks instead of being clipped */
.band-media { position: absolute; inset: 0; z-index: -1; }
.band-media picture, .band-media img { width: 100%; height: 100%; }
.band-media img { object-fit: cover; }
.band-media::after { content: ""; position: absolute; inset: 0; background: var(--band-fade); }
.band { --gold-text: var(--band-gold-text); --gold-em: var(--band-gold-em); } /* type sits on a whitened photo, one gold step deeper (tokens.css) */
.band h2 { max-width: 18ch; margin-inline: auto; }
.band .eyebrow { justify-content: center; }
.band p { margin: 1.75rem auto 0; max-width: 46ch; text-wrap: pretty; }

/* numbered columns */
.cols { display: grid; gap: 2.5rem; }
@media (min-width: 760px) { .cols { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 3rem; } }
.col { padding-top: 1.5rem; border-top: 1px solid var(--line); }
.col .num { display: block; margin-bottom: 1.4rem; }
.col h3 { font-size: clamp(1.5rem, 1.2rem + .8vw, 2rem); }
.col p { margin-top: 1rem; font-size: 1rem; }

/* closing call */
.cta { text-align: center; padding-block: calc(var(--section) * .25) var(--section); } /* the block before already ends with --section (visual opt V05) */
.cta h2 { max-width: 14ch; margin-inline: auto; }
.cta p { margin: 1.5rem auto 0; max-width: 48ch; text-wrap: pretty; }
.cta .eyebrow { margin-top: 0; } /* .cta p above also matched the eyebrow and added 1.5rem over the CTA (visual opt R5) */
.cta .post { margin-top: 1.25rem; color: var(--ink-3); font-size: .95rem; }

/* contact split */
.contact { display: grid; grid-template-columns: minmax(0, 1fr); gap: 3rem; align-items: center; }
@media (min-width: 900px) { .contact { grid-template-columns: minmax(0, 6fr) minmax(0, 6fr); gap: 5rem; } }
.contact h1 { font-size: var(--fs-h1-page); max-width: 12ch; }
.contact-text p { margin-top: 1.75rem; }
.contact-media { aspect-ratio: 4 / 5; overflow: hidden; }
.contact-media picture, .contact-media img { width: 100%; height: 100%; }
.contact-media img { object-fit: cover; }
.contact dl { margin: 2.5rem 0 0; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .6rem 1.5rem; }
.contact dt { font: 500 var(--fs-label)/1.7 var(--font-sans); letter-spacing: .2em; text-transform: uppercase; color: var(--ink-3); }
.contact dd { margin: 0; color: var(--ink-2); }

/* projects gallery: editorial 12-column rhythm */
.gallery { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; }
@media (min-width: 640px) { .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; } }
@media (min-width: 1100px) {
  .gallery { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1.5rem; }
  .gallery li { grid-column: span 4; }
  .gallery li:nth-child(6n + 1) { grid-column: span 8; grid-row: span 2; aspect-ratio: auto; }
}
.gallery li { position: relative; overflow: hidden; aspect-ratio: 3 / 2; background: var(--bg-2); }
.gallery-link, .gallery picture, .gallery img { display: block; width: 100%; height: 100%; }
.gallery img { object-fit: cover; transition: transform 1.4s var(--ease); }
.gallery li:hover img { transform: scale(1.04); }

/* projects page: one section per project — heading row, one lead image, then a grid */
.project + .project { margin-top: clamp(4rem, 9vw, 8rem); padding-top: clamp(3rem, 6vw, 5rem); border-top: 1px solid var(--line); }
.project-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .75rem 1.5rem; margin-bottom: clamp(1.75rem, 3.5vw, 3rem); }
.project-head .label, .project-head .project-note { flex-basis: 100%; max-width: none; }
.project-head h2 { font-size: var(--fs-h3); line-height: 1.12; }
.project-tag { padding: .45rem .6rem; border: 1px solid var(--line-strong); color: var(--gold-text); font: 500 .7rem/1 var(--font-sans); letter-spacing: .22em; text-transform: uppercase; }
.project-note { flex-basis: 100%; color: var(--ink-3); font-size: .95rem; }
.project-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; }
/* below 1100px: two columns, phones included (visual opt D / V15). Lead and an odd last image run full width at 16:9.
   DOM order (lightbox order) is unchanged. */
@media (max-width: 1099px) {
  .project-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; }
  .project-grid .project-lead, .project-grid--odd li:last-child { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
}
/* phones: captioned renders run full width so the "Architectural visualisation" label fits (from 640px it fits a half cell) */
@media (max-width: 639px) { .project-grid { gap: .5rem; } .project-grid .has-cap { grid-column: 1 / -1; } }
@media (min-width: 1100px) {
  /* six tracks: three images per row (span 2); a short last row uses span 3 so no image stands alone */
  .project-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 1.5rem; }
  .project-grid li { grid-column: span 2; }
  .project-grid--r2 li:nth-last-child(-n+2):not(.project-lead), .project-grid--r1 li:nth-last-child(-n+4):not(.project-lead) { grid-column: span 3; }
  .project-grid .project-lead { grid-column: 1 / -1; }
}
.project-grid li { position: relative; overflow: hidden; aspect-ratio: 3 / 2; background: var(--bg-2); }
.project-grid .project-lead { aspect-ratio: 16 / 9; }
.project-grid picture, .project-grid img { display: block; width: 100%; height: 100%; }
.project-grid img { object-fit: cover; transition: transform 1.4s var(--ease); }
.project-grid li:hover img { transform: scale(1.04); }

/* lightbox: stays dark (DECISIONS 80); the dialog re-points the type tokens to their on-dark values */
.lightbox { --ink: var(--ink-on-dark); --line-strong: var(--line-on-dark); --gold-text: var(--tag-ink); border: 0; padding: 0; margin: 0; inset: 0; width: 100vw; height: 100vh; max-width: none; max-height: none; background: transparent; }
.lightbox::backdrop { background: var(--lb-backdrop); }
.lightbox img { position: absolute; inset: 0; margin: auto; max-width: 92vw; max-height: 88vh; width: auto; height: auto; object-fit: contain; }
.lightbox button { position: absolute; top: 50%; transform: translateY(-50%); width: 3rem; height: 3rem; background: transparent; border: 1px solid var(--line-strong); color: var(--ink); font: 300 1.6rem/1 var(--font-display); cursor: pointer; }
.lightbox button:hover { border-color: var(--gold); color: var(--gold-text); }
.lightbox .lb-prev { left: 1rem; } .lightbox .lb-next { right: 1rem; } .lightbox .lb-close { top: 1.25rem; right: 1.25rem; transform: none; }
.lb-cap { position: absolute; left: 50%; bottom: 1.25rem; transform: translateX(-50%); margin: 0; padding: .45rem .6rem; background: var(--bg-tag); color: var(--tag-ink); font: 500 .7rem/1 var(--font-sans); letter-spacing: .22em; text-transform: uppercase; white-space: nowrap; pointer-events: none; }

/* prose pages (privacy) */
.layout-prose .page > .block { max-width: 52rem; margin-inline: auto; padding: var(--section) var(--gutter); }
.layout-prose.no-hero .page > .block:first-child { padding-top: calc(var(--section) * .8 + 5.5rem); }
.layout-prose h1 { font-size: var(--fs-h1-page); margin-bottom: 2.5rem; }
.layout-prose h2 { font-size: var(--fs-h3); margin: 3rem 0 1rem; }
.layout-prose p { max-width: 64ch; }
.layout-prose .page a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--gold); text-underline-offset: .25em; }
.figure { margin: 0; }

/* ---------- footer ---------- */
.site-footer { background: var(--bg-2); border-top: 1px solid var(--line); padding-block: clamp(3.5rem, 7vw, 6rem) 2rem; }
.footer-inner { display: grid; gap: 3rem; }
@media (min-width: 760px) { .footer-inner { grid-template-columns: 2fr 1fr 1fr; gap: 3rem 4rem; } }
.footer-brand img { height: 5.5rem; width: auto; }
.footer-note { margin-top: 1.5rem; max-width: 34ch; font-size: .95rem; color: var(--ink-3); }
.footer-head { display: block; margin-bottom: .75rem; color: var(--ink-3); } /* + the ~8px inside each 44px link box = the former 1.25rem to the first link */
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; } /* rows are spaced by the 44px link boxes below */
.site-footer li, .site-footer li a { color: var(--ink-2); font-size: .95rem; transition: color .3s; }
.site-footer li a:hover { color: var(--ink); }
.footer-bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem; margin-top: clamp(3rem, 6vw, 5rem); padding-top: 1.5rem; border-top: 1px solid var(--line); color: var(--ink-3); font-size: .85rem; letter-spacing: .06em; }
.footer-bottom a:hover { color: var(--ink); }
/* 44px tap targets that take part in layout (Codex review fix 1): the link boxes themselves set the row pitch, so
   neighbouring targets never overlap. vertical-align: top keeps the line box exactly as tall as the link box. */
.site-footer li a { display: inline-flex; align-items: center; min-height: 44px; vertical-align: top; }
.footer-bottom a { display: inline-flex; align-items: center; min-height: 44px; } /* baseline-aligned: "Website by" and "VJO Studio" share one baseline */
/* Footer credit "Website by VJO Studio" (DECISIONS 50, spec: Websites PM plan/页脚署名-规划-fable.md 1.5). Same size and colour as the copyright line. */
.footer-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0 1.5rem; }
.footer-credit a { color: inherit; text-decoration: underline; text-decoration-color: currentColor; text-underline-offset: .2em; transition: color .3s; }
.footer-credit a:hover, .footer-credit a:focus-visible { color: var(--ink); }
@media (max-width: 759px) {
  .footer-bottom > span:first-child, .footer-meta { flex-basis: 100%; }
  .footer-credit { flex-basis: 100%; }
}
/* phone layout (visual opt E3, E4, E7) */
@media (min-width: 480px) and (max-width: 759px) {
  .footer-inner { grid-template-columns: 1fr 1fr; gap: 2.5rem 2rem; }
  .footer-brand { grid-column: 1 / -1; }
}
@media (max-width: 759px) {
  .footer-inner { gap: 2.25rem; }
  .footer-meta { flex-basis: auto; }
}
@media (max-width: 639px) {
  .feature-text .meta { font-size: .95rem; }
  .project-note, .footer-note, .site-footer li, .site-footer li a, .cta .post { font-size: 1rem; }
  .band { min-height: min(64vh, 36rem); }
  .project + .project { margin-top: 3rem; padding-top: 2.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
.gallery-cap { position: absolute; right: 1rem; bottom: .9rem; padding: .45rem .6rem; background: var(--bg-tag); color: var(--tag-ink); font: 500 .7rem/1 var(--font-sans); letter-spacing: .22em; text-transform: uppercase; pointer-events: none; }
