/* ==========================================================================
   MINA SURFACE — Core stylesheet
   Hand-authored. No framework, no build step.
   Order: tokens → reset → typography → layout → motion → components → pages
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* Surface palette — named after the material, not the hex */
  --ink:          #14110F;   /* honed basalt   */
  --ink-2:        #1E1A17;
  --ink-soft:     #3A3430;
  --stone-50:     #F8F6F3;   /* limestone      */
  --stone-100:    #EFEBE5;
  --stone-200:    #DFD9D0;
  --stone-300:    #C8C0B4;
  --stone-400:    #A69C90;
  --stone-500:    #7C7368;

  --brass:        #B08D57;   /* decorative / on dark only — 2.9:1 on light */
  --brass-deep:   #7A5C2E;   /* text-safe on light — 5.8:1 on --stone-50   */
  --brass-glow:   rgba(176, 141, 87, 0.22);

  /* Semantic */
  --bg:           var(--stone-50);
  --fg:           var(--ink);
  --fg-muted:     var(--stone-500);
  --rule:         var(--stone-200);
  --accent:       var(--brass-deep);

  /* Type */
  --font-display: "Cormorant Garamond", "Iowan Old Style", Georgia, serif;
  --font-sans:    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  --t-display-xl: clamp(2.75rem, 1.4rem + 6.4vw, 7rem);
  --t-display-l:  clamp(2.25rem, 1.4rem + 4vw, 4.75rem);
  --t-display-m:  clamp(1.75rem, 1.25rem + 2.4vw, 3.25rem);
  --t-display-s:  clamp(1.4rem, 1.15rem + 1.2vw, 2.125rem);
  --t-lead:       clamp(1.0625rem, 1rem + 0.35vw, 1.3125rem);
  --t-body:       clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  --t-small:      0.9375rem;
  --t-label:      0.75rem;

  /* Spacing — spacious scale */
  --s-1:  0.5rem;
  --s-2:  1rem;
  --s-3:  1.5rem;
  --s-4:  2rem;
  --s-5:  3rem;
  --s-6:  4rem;
  --s-7:  6rem;
  --s-8:  8rem;
  --s-9:  10rem;
  --section: clamp(4.5rem, 3rem + 7vw, 10rem);

  /* Layout */
  --gutter: clamp(1.25rem, 0.6rem + 3vw, 4rem);
  --measure: 62ch;
  --max: 1440px;

  /* Motion */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-soft: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur-fast: 180ms;
  --dur-mid:  320ms;
  --dur-slow: 1000ms;

  --header-h: 76px;
}

@media (max-width: 640px) {
  :root { --header-h: 64px; }
}

/* --------------------------------------------------------------------------
   2. RESET
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

body.nav-open { overflow: hidden; }

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; cursor: pointer; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--brass-deep);
  outline-offset: 3px;
  border-radius: 2px;
}
.on-dark :focus-visible,
.section--ink :focus-visible { outline-color: var(--brass); }

::selection { background: var(--ink); color: var(--stone-50); }

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: 0;
  transform: translateY(-140%);
  z-index: 200;
  background: var(--ink);
  color: var(--stone-50);
  padding: 0.75rem 1.25rem;
  font-size: var(--t-small);
  transition: transform var(--dur-mid) var(--ease-out-soft);
}
.skip-link:focus-visible { transform: translateY(0.5rem); }

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

/* --------------------------------------------------------------------------
   3. TYPOGRAPHY
   -------------------------------------------------------------------------- */
.display, h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 300;
  line-height: 1.02;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.d-xl { font-size: var(--t-display-xl); }
.d-l  { font-size: var(--t-display-l); }
.d-m  { font-size: var(--t-display-m); line-height: 1.08; }
.d-s  { font-size: var(--t-display-s); line-height: 1.15; }

.display em,
.d-xl em, .d-l em, .d-m em {
  font-style: italic;
  font-weight: 400;
}

.label {
  font-family: var(--font-sans);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-muted);
  line-height: 1.4;
}
.label--accent { color: var(--brass-deep); }
.on-dark .label { color: var(--stone-400); }
.on-dark .label--accent { color: var(--brass); }

/* flex-start + a nudge keeps the hairline optically on the FIRST line when
   the label wraps on narrow screens, rather than floating between two lines. */
.label-rule {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
}
.label-rule::before {
  content: "";
  width: 2.5rem;
  height: 1px;
  margin-top: 0.62em;
  background: var(--brass-deep);
  flex: none;
}
.on-dark .label-rule::before { background: var(--brass); }

.lead {
  font-size: var(--t-lead);
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 46ch;
  font-weight: 300;
}
.on-dark .lead { color: var(--stone-300); }

.prose p { max-width: var(--measure); }
.prose p + p { margin-top: 1.15em; }
.muted { color: var(--fg-muted); }
.on-dark .muted { color: var(--stone-400); }

/* --------------------------------------------------------------------------
   4. LAYOUT
   -------------------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: 980px; }

.section { padding-block: var(--section); }
.section--tight { padding-block: clamp(3rem, 2rem + 4vw, 6rem); }

.section--ink {
  /* Honed-basalt surface. Extends the existing .tex-ink stone language
     (load-bearing, not decoration) from the media boxes to the section fill
     itself, at felt-not-seen contrast (~5% peak) so #14110F reads as a lit
     stone block rather than a flat web colour. No visible pattern.
     Blooms are centred just OFF-canvas so neither edge carries a hotspot —
     that keeps the seam between two adjacent ink sections clean. */
  background-color: var(--ink);
  background-image:
    /* quarry light — a warm bloom off the upper-left gives the block a direction */
    radial-gradient(130% 90% at 5% -10%, rgba(176, 141, 87, 0.055) 0%, transparent 60%),
    /* opposing shadow — the lower-right falls off, so the surface reads as tonal, not flat */
    radial-gradient(130% 100% at 105% 110%, rgba(6, 5, 4, 0.42) 0%, transparent 60%),
    /* one whisper of vein, kept well under the read threshold */
    linear-gradient(122deg, transparent 46%, rgba(176, 141, 87, 0.04) 47.6%, transparent 49%);
  color: var(--stone-100);
}
.section--stone {
  /* Matte-limestone surface — the light-section counterpart to .section--ink.
     Same light direction (warm bloom upper-left, shadow lower-right) so the
     whole page reads as lit from one source. The .tex-marble vein language,
     one surface wider, kept felt-not-seen.
     Contrast guard: these sections carry muted body (--stone-500, already
     ~3.9:1 on --stone-100). The light bloom only ever RAISES contrast; the
     shadow is held to ~5% and centred off-canvas so muted text — which sits
     lower-right in the capability grid — never drops meaningfully. */
  background-color: var(--stone-100);
  background-image:
    /* light catching the slab — warm, near luminance-neutral, upper-left */
    radial-gradient(130% 90% at 8% -10%, rgba(255, 252, 246, 0.6) 0%, transparent 55%),
    /* the shadow side — deliberately faint, so it reads as depth, not a vignette */
    radial-gradient(130% 100% at 108% 114%, rgba(124, 115, 104, 0.05) 0%, transparent 55%),
    /* one whisper of directional vein, from the marble texture language */
    linear-gradient(118deg, transparent 42%, rgba(122, 92, 46, 0.035) 44%, transparent 46%);
}

.rule {
  height: 1px;
  background: var(--rule);
  border: 0;
  margin: 0;
}
.on-dark .rule { background: rgba(248, 246, 243, 0.14); }

.grid { display: grid; gap: var(--s-4); }
.cols-2 { grid-template-columns: repeat(2, 1fr); }
.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cols-4 { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 900px) {
  .cols-3, .cols-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .cols-2, .cols-3, .cols-4 { grid-template-columns: 1fr; }
}

/* Editorial split: sticky heading + flowing content */
.split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2rem, 1rem + 5vw, 6rem);
  align-items: start;
}
.split__aside { position: sticky; top: calc(var(--header-h) + 3rem); }
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .split__aside { position: static; }
}

/* --------------------------------------------------------------------------
   5. MEDIA + STONE PLACEHOLDERS
   The .media box carries a CSS-generated stone texture. A real photograph
   dropped into assets/img/ covers it. If the file is missing, the texture
   remains — so the page never shows a broken-image icon.
   -------------------------------------------------------------------------- */
.media {
  position: relative;
  overflow: hidden;
  background-color: var(--stone-200);
  isolation: isolate;
}
.media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: relative;
  z-index: 1;
}
/* The stone-texture placeholder layer. Sits under the photograph, so it only
   shows through while assets/img/ is still empty. */
.media > .media__inner {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.media--4x5  { aspect-ratio: 4 / 5; }
.media--3x4  { aspect-ratio: 3 / 4; }
.media--1x1  { aspect-ratio: 1 / 1; }
.media--3x2  { aspect-ratio: 3 / 2; }
.media--16x9 { aspect-ratio: 16 / 9; }

/* Quartz — cool, dense, fine crystalline scatter.
   Layer sizes are mutually coprime and each carries its own offset, so the
   repeats never line up into a visible grid. */
.tex-quartz {
  background-color: #E9E7E3;
  background-image:
    radial-gradient(circle at 18% 24%, rgba(255,255,255,.9) 0 1.5px, transparent 2px),
    radial-gradient(circle at 63% 12%, rgba(160,158,152,.5) 0 2px, transparent 2.6px),
    radial-gradient(circle at 81% 68%, rgba(255,255,255,.85) 0 1.8px, transparent 2.4px),
    radial-gradient(circle at 34% 77%, rgba(150,148,142,.45) 0 1.6px, transparent 2.2px),
    linear-gradient(142deg, #F2F0EC 0%, #E2DFDA 55%, #EDEAE5 100%);
  background-size: 47px 61px, 79px 53px, 67px 89px, 59px 43px, 100% 100%;
  background-position: 0 0, 17px 29px, 41px 11px, 23px 47px, 0 0;
}

/* Engineered marble — soft directional veining */
.tex-marble {
  background-color: #EDE9E3;
  background-image:
    linear-gradient(118deg, transparent 41%, rgba(122,92,46,.10) 43%, transparent 45%),
    linear-gradient(112deg, transparent 61%, rgba(58,52,48,.14) 62.4%, transparent 64%),
    linear-gradient(126deg, transparent 22%, rgba(58,52,48,.08) 24%, transparent 27%),
    radial-gradient(ellipse 70% 50% at 30% 30%, rgba(255,255,255,.75), transparent 70%),
    linear-gradient(160deg, #F4F1EC 0%, #E4DFD7 100%);
}

/* Terrazzo — aggregate chip scatter */
.tex-terrazzo {
  background-color: #E7E3DC;
  background-image:
    radial-gradient(circle at 12% 18%, #7A5C2E 0 4px, transparent 5px),
    radial-gradient(circle at 68% 32%, #3A3430 0 5px, transparent 6px),
    radial-gradient(circle at 39% 61%, #B08D57 0 3.5px, transparent 4.5px),
    radial-gradient(circle at 87% 79%, #A69C90 0 4.5px, transparent 5.5px),
    radial-gradient(circle at 24% 88%, #3A3430 0 3px, transparent 4px),
    radial-gradient(circle at 55% 8%, #C8C0B4 0 5px, transparent 6px),
    linear-gradient(0deg, #EFEBE5, #E6E1D9);
  background-size: 131px 167px, 157px 113px, 119px 181px, 173px 127px, 149px 193px, 191px 137px, 100% 100%;
  background-position: 0 0, 43px 71px, 89px 23px, 17px 137px, 113px 59px, 61px 101px, 0 0;
}

/* Deep ink stone — for hero / dark cards */
.tex-ink {
  background-color: var(--ink);
  background-image:
    radial-gradient(ellipse 80% 60% at 25% 20%, rgba(176,141,87,.16), transparent 65%),
    linear-gradient(122deg, transparent 46%, rgba(176,141,87,.10) 47.6%, transparent 49%),
    linear-gradient(115deg, transparent 68%, rgba(248,246,243,.06) 69.4%, transparent 71%),
    linear-gradient(160deg, #221D19 0%, #14110F 60%, #0D0B0A 100%);
}

.media__scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(180deg, rgba(20,17,15,.55) 0%, rgba(20,17,15,.15) 38%, rgba(20,17,15,.78) 100%);
}

/* --------------------------------------------------------------------------
   6. MOTION
   Everything below is opt-in via [data-reveal]. The .is-in class is added by
   IntersectionObserver in main.js. If JS fails or reduced-motion is set,
   elements are painted in their final state — content is never hidden.
   -------------------------------------------------------------------------- */
.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity 900ms var(--ease-out-soft) var(--reveal-delay, 0ms),
    transform 900ms var(--ease-out-expo) var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}
.js [data-reveal].is-in { will-change: auto; }

/* Masked line reveal — the signature move */
.lines .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.lines .line > span {
  display: block;
}
.js .lines .line > span {
  transform: translate3d(0, 115%, 0);
  transition: transform 1150ms var(--ease-out-expo);
  transition-delay: calc(var(--i, 0) * 110ms + var(--reveal-delay, 0ms));
}
.js .lines.is-in .line > span { transform: none; }

/* Slow roster reveal — longer stagger, used on the project roll */
.js .lines--slow .line > span {
  transition-duration: 1400ms;
  transition-delay: calc(var(--i, 0) * 165ms);
}

/* Hairline that draws itself */
.js [data-draw] {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1100ms var(--ease-out-expo) var(--reveal-delay, 0ms);
}
.js [data-draw].is-in { transform: scaleX(1); }

/* Image mask-up — the photo slides out from behind a moving panel */
.js [data-reveal-media] { position: relative; }
.js [data-reveal-media] > img,
.js [data-reveal-media] > .media__inner {
  transform: scale(1.12);
  transition: transform 1600ms var(--ease-out-expo);
}
.js [data-reveal-media].is-in > img,
.js [data-reveal-media].is-in > .media__inner { transform: scale(1); }
.js [data-reveal-media]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--stone-50);
  transform-origin: bottom center;
  transition: transform 1200ms var(--ease-out-expo);
}
.js [data-reveal-media].is-in::before { transform: scaleY(0); }
.section--ink .js [data-reveal-media]::before,
.js .section--ink [data-reveal-media]::before { background: var(--ink); }

/* Parallax layer — driven by rAF, desktop pointer devices only */
.parallax { will-change: transform; }

/* Honour the user's system setting, always. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js [data-reveal],
  .js .lines .line > span,
  .js [data-draw],
  .js [data-reveal-media] > img,
  .js [data-reveal-media] > .media__inner {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .js [data-reveal-media]::before { display: none; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Coarse pointers / small screens: fades only, no parallax, no magnetism.
   Declared in JS too, but this is the CSS backstop. */
@media (max-width: 1023px), (pointer: coarse) {
  .js [data-reveal-media]::before { transition-duration: 900ms; }
  .js [data-reveal-media] > img,
  .js [data-reveal-media] > .media__inner { transform: none; transition: none; }
}

/* --------------------------------------------------------------------------
   7. COMPONENTS
   -------------------------------------------------------------------------- */

/* --- Buttons ------------------------------------------------------------- */
.btn {
  --btn-fg: var(--stone-50);
  --btn-bg: var(--ink);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  min-height: 48px;
  padding: 0.9rem 1.75rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-decoration: none;
  border: 1px solid var(--btn-bg);
  border-radius: 0;
  overflow: hidden;
  transition: background-color var(--dur-mid) var(--ease-out-soft),
              color var(--dur-mid) var(--ease-out-soft),
              border-color var(--dur-mid) var(--ease-out-soft),
              transform 500ms var(--ease-out-expo);
}
.btn > span { position: relative; z-index: 1; }
.btn svg { position: relative; z-index: 1; flex: none; transition: transform var(--dur-mid) var(--ease-out-soft); }
.btn:hover svg { transform: translateX(4px); }

.btn:hover { background: var(--brass-deep); border-color: var(--brass-deep); color: #fff; }

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  border-color: var(--stone-300);
}
.btn--ghost:hover { background: var(--ink); border-color: var(--ink); color: var(--stone-50); }

.on-dark .btn--ghost { --btn-fg: var(--stone-100); border-color: rgba(248,246,243,.28); }
.on-dark .btn--ghost:hover { background: var(--stone-50); border-color: var(--stone-50); color: var(--ink); }

.on-dark .btn--solid { --btn-bg: var(--stone-50); --btn-fg: var(--ink); }
.on-dark .btn--solid:hover { background: var(--brass); border-color: var(--brass); color: var(--ink); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
}

/* Text link with a rule that wipes on hover */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 44px;
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.03em;
  text-decoration: none;
  color: var(--ink);
  position: relative;
}
.link::after {
  content: "";
  position: absolute;
  left: 0; bottom: 10px;
  width: 100%;
  height: 1px;
  background: currentColor;
  opacity: .35;
  transform-origin: right center;
  transform: scaleX(1);
  transition: transform 420ms var(--ease-out-expo), opacity var(--dur-mid);
}
.link:hover::after { transform-origin: left center; opacity: 1; }
.link svg { transition: transform var(--dur-mid) var(--ease-out-soft); flex: none; }
.link:hover svg { transform: translateX(4px); }
.on-dark .link { color: var(--stone-100); }

/* --- Header -------------------------------------------------------------- */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  transition: background-color 500ms var(--ease-out-soft),
              border-color 500ms var(--ease-out-soft),
              transform 500ms var(--ease-out-expo);
  border-bottom: 1px solid transparent;
}
.header__inner {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
/* Over a dark hero */
.header--over { color: var(--stone-50); }
/* Scrolled / interior pages */
.header--solid {
  background: rgba(248, 246, 243, 0.86);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: var(--rule);
  color: var(--ink);
}
.header--hidden { transform: translateY(-100%); }

@supports not (backdrop-filter: blur(4px)) {
  .header--solid { background: var(--stone-50); }
}

/* Brand logo (image lockup) */
.brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  flex: none;
}
.brand__logo {
  display: block;
  height: 38px;
  width: auto;
}
/* Two variants stacked in the same link; CSS shows one per header state so
   the wordmark stays legible on both the dark hero and the light bar.
   The colour lockup is the default (interior pages are always header--solid);
   the white lockup only appears while the header sits over the dark hero. */
.brand__logo--white { display: none; }
.header--over .brand__logo--white { display: block; }
.header--over .brand__logo--color { display: none; }

@media (max-width: 600px) { .brand__logo { height: 34px; } }
@media (max-width: 380px) { .brand__logo { height: 30px; } }

/* Footer sits on ink — always the white lockup, a touch larger. */
.footer .brand__logo { height: 46px; }
@media (max-width: 600px) { .footer .brand__logo { height: 40px; } }

/* Desktop nav */
.nav { display: flex; align-items: center; gap: clamp(1.25rem, 2vw, 2.25rem); }
.nav a {
  position: relative;
  font-size: 0.875rem;
  font-weight: 450;
  letter-spacing: 0.02em;
  text-decoration: none;
  padding: 0.5rem 0;
  display: inline-block;
}
.nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform 420ms var(--ease-out-expo);
}
.nav a:hover::after,
.nav a[aria-current="page"]::after { transform: scaleX(1); transform-origin: left center; }
.nav a[aria-current="page"] { color: var(--brass-deep); }
.header--over .nav a[aria-current="page"] { color: var(--brass); }

.header__actions { display: flex; align-items: center; gap: var(--s-2); }
.header .btn { min-height: 44px; padding: 0.65rem 1.25rem; }
.header--over .btn--ghost { color: var(--stone-50); border-color: rgba(248,246,243,.3); }
.header--over .btn--ghost:hover { background: var(--stone-50); color: var(--ink); border-color: var(--stone-50); }

@media (max-width: 1023px) {
  .nav--desktop, .header__actions .btn { display: none; }
}

/* Menu button */
.menu-btn {
  display: none;
  width: 48px; height: 48px;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  padding: 0;
}
.menu-btn span {
  display: block;
  width: 22px; height: 1.5px;
  background: currentColor;
  position: relative;
  transition: background-color var(--dur-fast) linear;
}
.menu-btn span::before,
.menu-btn span::after {
  content: "";
  position: absolute;
  left: 0;
  width: 22px; height: 1.5px;
  background: currentColor;
  transition: transform 400ms var(--ease-out-expo);
}
.menu-btn span::before { top: -6px; }
.menu-btn span::after  { top: 6px; }
.menu-btn[aria-expanded="true"] span { background: transparent; }
.menu-btn[aria-expanded="true"] span::before { transform: translateY(6px) rotate(45deg); }
.menu-btn[aria-expanded="true"] span::after  { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 1023px) { .menu-btn { display: flex; } }

/* Mobile drawer */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 99;
  background: var(--ink);
  color: var(--stone-50);
  padding: calc(var(--header-h) + 2rem) var(--gutter) 2rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-2%);
  transition: opacity 480ms var(--ease-out-soft), transform 560ms var(--ease-out-expo), visibility 480ms;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.drawer.is-open { opacity: 1; visibility: visible; transform: none; }
.drawer__nav { display: flex; flex-direction: column; }
.drawer__nav a {
  font-family: var(--font-display);
  font-size: clamp(2rem, 1.2rem + 4vw, 3rem);
  font-weight: 300;
  line-height: 1;
  text-decoration: none;
  padding: 0.55em 0;
  border-bottom: 1px solid rgba(248,246,243,.12);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 520ms var(--ease-out-soft), transform 620ms var(--ease-out-expo);
  transition-delay: calc(var(--i) * 55ms + 120ms);
}
.drawer.is-open .drawer__nav a { opacity: 1; transform: none; }
.drawer__foot { padding-top: 2rem; display: grid; gap: 1rem; }

/* --- Hero ---------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  color: var(--stone-50);
  background: var(--ink);
}
.hero__bg {
  position: absolute;
  inset: -8% 0 -8% 0;   /* overscan gives the parallax room to travel */
  z-index: 0;
}
.hero__bg > img {
  width: 100%; height: 100%;
  object-fit: cover;
}
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(20,17,15,.72) 0%, rgba(20,17,15,.30) 34%, rgba(20,17,15,.55) 68%, rgba(20,17,15,.92) 100%),
    linear-gradient(90deg, rgba(20,17,15,.55) 0%, transparent 62%);
}
/* The hero is bottom-anchored, so its top padding only has to clear the
   header. Everything here is tuned so that on a 1280×720 laptop the primary
   CTA still sits above the fold — that is the constraint, not the aesthetics. */
.hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-top: calc(var(--header-h) + clamp(1.5rem, 1rem + 3vw, 4rem));
  padding-bottom: clamp(2.5rem, 1.5rem + 3vw, 5rem);
}
.hero__content { max-width: 21ch; margin-top: 1.25rem; }
.hero__body { margin-top: 1.5rem; }
/* Measure belongs on the paragraph, not the wrapper — otherwise it also
   constrains the button row and forces the CTAs to stack. */
.hero__body .lead { max-width: 40ch; font-size: clamp(1rem, 0.95rem + 0.3vw, 1.1875rem); }
.hero__body .btn-row { margin-top: 1.75rem; }
.hero h1 { color: #fff; font-size: clamp(2.75rem, 1.5rem + 5.2vw, 6.25rem); }

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2rem;
  padding-top: 1.5rem;
  margin-top: 2rem;
  border-top: 1px solid rgba(248,246,243,.18);
}

/* Short viewports (laptops, landscape phones): compress rather than clip. */
@media (max-height: 780px) {
  .hero__inner { padding-top: calc(var(--header-h) + 1.25rem); padding-bottom: 2rem; }
  .hero h1 { font-size: clamp(2.5rem, 1.2rem + 4.2vw, 5rem); }
  .hero__body { margin-top: 1.25rem; }
  .hero__body .btn-row { margin-top: 1.375rem; }
  .hero__meta { margin-top: 1.5rem; padding-top: 1.125rem; }
}
@media (max-height: 620px) {
  .hero__meta { display: none; }
}
.hero__meta li {
  font-size: var(--t-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--stone-300);
  font-weight: 500;
}

.scroll-cue {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(2.5rem, 1.5rem + 3vw, 5rem);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: var(--t-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--stone-300);
  text-decoration: none;
}
.scroll-cue__line {
  display: block;
  width: 1px; height: 54px;
  background: linear-gradient(180deg, var(--brass), transparent);
  transform-origin: top center;
  animation: cue 2.6s var(--ease-out-soft) infinite;
}
@keyframes cue {
  0%   { transform: scaleY(0); opacity: 0; }
  35%  { transform: scaleY(1); opacity: 1; }
  75%  { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1) translateY(54px); opacity: 0; }
}
@media (max-width: 767px) { .scroll-cue { display: none; } }

/* --- Project roster (typographic roll) -----------------------------------
   The <li> is the mask (.line, overflow hidden); the inner .roster__row is
   the thing that travels. The extra specificity below is deliberate — it
   has to beat `.lines .line > span { display: block }`.
   -------------------------------------------------------------------------- */
.roster { border-top: 1px solid rgba(248,246,243,.14); }
.roster__line { border-bottom: 1px solid rgba(248,246,243,.14); }
.roster .line > .roster__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s-3);
  padding-block: clamp(1rem, 0.5rem + 1.6vw, 1.9rem);
}
.roster__name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 1rem + 3.4vw, 4rem);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--stone-50);
  text-wrap: balance;
}
.roster__meta {
  display: block;
  font-size: var(--t-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--stone-400);
  text-align: right;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .roster .line > .roster__row { grid-template-columns: 1fr; gap: 0.35rem; }
  .roster__meta { text-align: left; white-space: normal; }
}

/* Roster row hover — ambient "list focus", the handcrafted touch.
   Per the hard rule this is COLOUR + OPACITY only, under 200ms: no drawn
   hairline (that needs a transform), no indent. The neighbours recede while
   the row you point at holds full brightness; its baseline rule lights to
   brass (brass's sanctioned decorative-rule use) and its location meta
   brightens. These rows are display type, not links — no arrow, no click
   affordance — so this reads as tone, not a button. Gated to fine-pointer
   hover so a touch never lands on a stuck state; the row-reveal is a
   transform on .roster__row, untouched here, so the two never collide. */
@media (hover: hover) and (pointer: fine) {
  .roster__line {
    transition: opacity 180ms var(--ease-out-soft),
                border-color 180ms var(--ease-out-soft);
  }
  .roster__meta { transition: color 180ms var(--ease-out-soft); }

  .roster:hover .roster__line { opacity: 0.4; }
  .roster:hover .roster__line:hover { opacity: 1; }
  .roster__line:hover { border-bottom-color: var(--brass); }
  .roster__line:hover .roster__meta { color: var(--stone-50); }
}

/* --- Material cards ------------------------------------------------------ */
.material-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
.material-card .media__inner { transition: transform 900ms var(--ease-out-expo); }
/* Declared after the motion block on purpose so hover wins the tie. */
.material-card:hover .media__inner,
.material-card:hover .media > img { transform: scale(1.045); }
.material-card__index {
  font-size: var(--t-label);
  letter-spacing: 0.2em;
  color: var(--brass-deep);
  font-weight: 500;
}
.material-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-top: var(--s-3);
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--rule);
  transition: border-color var(--dur-mid) var(--ease-out-soft);
}
.material-card h3 { font-size: var(--t-display-s); }
.material-card p { margin-top: 1rem; color: var(--fg-muted); font-size: var(--t-small); max-width: 38ch; }
.material-card__cta {
  margin-top: 1.25rem;
  font-size: var(--t-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ink);
  transition: color var(--dur-mid) var(--ease-out-soft);
}
.material-card__cta svg { transition: transform var(--dur-mid) var(--ease-out-soft); }
.material-card:hover .material-card__cta svg { transform: translateX(5px); }

/* Coordinated card hover — pull the existing motion (image lift + arrow travel)
   together with two COLOUR accents so the whole card responds as one gesture,
   not three separate effects. The title's rule and the CTA (text + its arrow,
   via currentColor) both warm to the text-safe --brass-deep (5.7:1 on the
   stone-50 ground these cards sit on). Colour only — no new transforms; the
   index-slide / rule-wipe those would need stay off, per the hover rule.
   Timed to --dur-mid to match the arrow that was already there. */
.material-card:hover .material-card__head { border-bottom-color: var(--brass-deep); }
.material-card:hover .material-card__cta { color: var(--brass-deep); }

/* --- Capability list ----------------------------------------------------- */
.capability {
  display: grid;
  gap: 0.85rem;
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}
.on-dark .capability { border-top-color: rgba(248,246,243,.16); }
.capability h3 { font-size: 1.375rem; font-family: var(--font-display); font-weight: 400; }
.capability p { font-size: var(--t-small); color: var(--fg-muted); }
.on-dark .capability p { color: var(--stone-400); }
.capability__num {
  font-size: var(--t-label);
  letter-spacing: 0.2em;
  color: var(--brass-deep);
  font-weight: 500;
}
.on-dark .capability__num { color: var(--brass); }

/* --- Statement block (pull-quote scale) ---------------------------------- */
.statement {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--t-display-m);
  line-height: 1.14;
  letter-spacing: -0.015em;
  max-width: 20ch;
}

/* --- Project grid -------------------------------------------------------- */
.project-card { display: block; text-decoration: none; color: inherit; }
.project-card .media { background-color: var(--stone-200); }
.project-card .media__inner { transition: transform 900ms var(--ease-out-expo); }
.project-card:hover .media__inner,
.project-card:hover .media > img { transform: scale(1.04); }
.project-card__body {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-top: 1.1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--rule);
}
.project-card h3 { font-size: 1.5rem; font-weight: 400; }
.project-card__loc { font-size: var(--t-label); letter-spacing: 0.16em; text-transform: uppercase; color: var(--fg-muted); white-space: nowrap; }
.project-card__type { margin-top: 0.75rem; font-size: var(--t-small); color: var(--fg-muted); }

/* --- Spec tables --------------------------------------------------------- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.spec {
  width: 100%;
  min-width: 520px;
  border-collapse: collapse;
  font-size: var(--t-small);
}
table.spec caption {
  text-align: left;
  font-family: var(--font-display);
  font-size: var(--t-display-s);
  font-weight: 400;
  padding-bottom: 1rem;
}
table.spec th, table.spec td {
  text-align: left;
  padding: 0.95rem 1rem 0.95rem 0;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
table.spec thead th {
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
  font-weight: 500;
  border-bottom-color: var(--stone-300);
}
table.spec tbody th { font-weight: 500; width: 38%; }
.tbc {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  background: var(--stone-200);
  color: var(--ink-soft);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
}

/* Qualifier inside a spec value — "(20 mm thickness)", "(honed 400 finish)".
   Subordinate to the number it modifies, never a second sentence. */
.spec__qual { color: var(--fg-muted); white-space: nowrap; }

/* Provenance line under a spec table — what the figures were taken from. */
.spec__source {
  margin-top: 1.5rem;
  max-width: 62ch;
  font-size: var(--t-small);
  color: var(--fg-muted);
}

.spec__download { margin-top: 1.75rem; }

/* --- Accordion (near-static, no JS dependency for content visibility) ---- */
.acc { border-top: 1px solid var(--rule); }
.acc details { border-bottom: 1px solid var(--rule); }
.acc summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.4rem 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 400;
  min-height: 48px;
  transition: color var(--dur-mid) var(--ease-out-soft);
}
.acc summary::-webkit-details-marker { display: none; }
.acc summary:hover { color: var(--brass-deep); }
.acc summary .plus {
  position: relative;
  width: 14px; height: 14px;
  flex: none;
}
.acc summary .plus::before,
.acc summary .plus::after {
  content: "";
  position: absolute;
  background: currentColor;
  transition: transform 420ms var(--ease-out-expo);
}
.acc summary .plus::before { top: 6.5px; left: 0; width: 14px; height: 1px; }
.acc summary .plus::after  { left: 6.5px; top: 0; width: 1px; height: 14px; }
.acc details[open] summary .plus::after { transform: rotate(90deg); }
.acc__body { padding-bottom: 1.6rem; }
.acc__body p { font-size: var(--t-small); color: var(--fg-muted); max-width: var(--measure); }

/* --- Forms --------------------------------------------------------------- */
.field { display: grid; gap: 0.5rem; }
.field > label {
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--ink-soft);
}
.field .hint { font-size: 0.8125rem; color: var(--fg-muted); }
.field input,
.field select,
.field textarea {
  font: inherit;
  font-size: var(--t-body);
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--stone-300);
  border-radius: 0;
  padding: 0.85rem 0;
  min-height: 48px;
  width: 100%;
  transition: border-color var(--dur-mid) var(--ease-out-soft);
  appearance: none;
}
.field textarea { min-height: 128px; resize: vertical; line-height: 1.6; }
.field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%237C7368' stroke-width='1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 2px center;
  padding-right: 2rem;
}
.field input:hover,
.field select:hover,
.field textarea:hover { border-bottom-color: var(--stone-400); }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--brass-deep);
  border-bottom-width: 1.5px;
}
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible { outline: 2px solid var(--brass-deep); outline-offset: 4px; }
.field .error {
  font-size: 0.8125rem;
  color: #A32020;
  display: none;
}
.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea { border-bottom-color: #A32020; }
.field.is-invalid .error { display: block; }
.req { color: var(--brass-deep); }

.form-grid { display: grid; gap: var(--s-4); }
@media (min-width: 720px) {
  .form-grid--2 { grid-template-columns: 1fr 1fr; }
  .form-grid .span-2 { grid-column: 1 / -1; }
}

.form-note {
  font-size: 0.8125rem;
  color: var(--fg-muted);
  max-width: 52ch;
}

/* --- Footer -------------------------------------------------------------- */
.footer {
  background: var(--ink);
  color: var(--stone-300);
  padding-block: var(--s-7) var(--s-4);
}
.footer__top {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  gap: var(--s-5) var(--s-4);
  padding-bottom: var(--s-6);
  border-bottom: 1px solid rgba(248,246,243,.14);
}
@media (max-width: 900px) { .footer__top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer__top { grid-template-columns: 1fr; gap: var(--s-4); } }

.footer h2 { color: var(--stone-50); }
.footer__col h3 {
  font-family: var(--font-sans);
  font-size: var(--t-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--stone-400);
  margin-bottom: 1.1rem;
}
.footer__col li + li { margin-top: 0.55rem; }
.footer__col a {
  text-decoration: none;
  font-size: var(--t-small);
  color: var(--stone-200);
  display: inline-block;
  padding: 0.2rem 0;
  transition: color var(--dur-mid) var(--ease-out-soft);
}
.footer__col a:hover { color: var(--brass); }
.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  justify-content: space-between;
  padding-top: var(--s-4);
  font-size: 0.8125rem;
  color: var(--stone-500);
}
.footer address { font-style: normal; font-size: var(--t-small); color: var(--stone-300); line-height: 1.8; }

/* --- Page hero (interior pages — near-static, fast) ---------------------- */
.page-hero {
  padding-top: calc(var(--header-h) + clamp(3rem, 2rem + 5vw, 7rem));
  padding-bottom: clamp(2.5rem, 2rem + 3vw, 4.5rem);
  border-bottom: 1px solid var(--rule);
}
.page-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(1.5rem, 1rem + 4vw, 5rem);
  align-items: end;
}
@media (max-width: 860px) { .page-hero__grid { grid-template-columns: 1fr; gap: var(--s-3); } }

/* --- Misc utilities ------------------------------------------------------ */
.stack-1 > * + * { margin-top: var(--s-1); }
.stack-2 > * + * { margin-top: var(--s-2); }
.stack-3 > * + * { margin-top: var(--s-3); }
.stack-4 > * + * { margin-top: var(--s-4); }
.stack-5 > * + * { margin-top: var(--s-5); }
.stack-6 > * + * { margin-top: var(--s-6); }

.full-bleed-media {
  position: relative;
  height: clamp(320px, 55vh, 640px);
  overflow: hidden;
}
/* Taller variant for photographs whose subject spans the full frame
   (e.g. the showroom break on index — wall logo top, sample boxes bottom).
   The standard 55vh letterbox would crop one or the other. */
.full-bleed-media--tall { height: clamp(440px, 80vh, 860px); }
/* On phones the 80vh band forces a portrait crop of the landscape
   sample-box photo — the boxes fall out of frame entirely. Size the band
   to the photo's own proportions instead (2400×1339 ≈ 56vw, plus a little
   for the parallax overscan). */
@media (max-width: 760px) {
  .full-bleed-media--tall { height: clamp(220px, 62vw, 420px); }
}
.full-bleed-media > .parallax {
  position: absolute;
  inset: -10% 0;
}

/* ==========================================================================
   MATERIALS SKU GALLERY  (materials.html only — rendered by gallery.js)
   Cards come from assets/img/materials/manifest.json; only present SKUs
   render. Thumbnails are lazy-loaded via IntersectionObserver so a 47-card
   quartz grid never loads eagerly.
   ========================================================================== */
.gallery__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--rule);
}
.gallery__note { font-size: var(--t-small); }

.gallery__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.5rem;
}
.gtab {
  min-height: 44px;
  padding: 0.5rem 1.1rem;
  background: transparent;
  border: 1px solid var(--stone-300);
  color: var(--ink-soft);
  font-size: var(--t-small);
  letter-spacing: 0.02em;
  border-radius: 0;
  transition: background-color var(--dur-mid) var(--ease-out-soft),
              border-color var(--dur-mid) var(--ease-out-soft),
              color var(--dur-mid) var(--ease-out-soft);
}
.gtab:hover { border-color: var(--ink); color: var(--ink); }
.gtab[aria-selected="true"] { background: var(--ink); border-color: var(--ink); color: var(--stone-50); }

.gallery__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1rem, 0.5rem + 1.5vw, 1.75rem);
  margin-top: 1.75rem;
}
@media (max-width: 1080px) { .gallery__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px)  { .gallery__grid { grid-template-columns: repeat(2, 1fr); } }

.sku-card {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.sku-card[hidden] { display: none; }
.sku-card .media { background-color: var(--stone-200); }
.sku-card .media img {
  opacity: 0;
  transition: opacity 600ms var(--ease-out-soft), transform 900ms var(--ease-out-expo);
}
.sku-card .media img.is-loaded { opacity: 1; }
.sku-card:hover .media img.is-loaded { transform: scale(1.045); }
.sku-card:focus-visible { outline: 2px solid var(--brass-deep); outline-offset: 4px; }

.sku-card__name {
  font-family: var(--font-display);
  font-size: 1.1875rem;
  font-weight: 400;
  line-height: 1.15;
  color: var(--ink);
}
.sku-card__cat {
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-top: 0.15rem;
}

.gallery__empty {
  margin-top: 1.75rem;
  font-size: var(--t-small);
  padding: 2rem 0;
  border-top: 1px solid var(--rule);
}

/* --- Lightbox ------------------------------------------------------------ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 0.5rem + 3vw, 3rem);
  background: rgba(20, 17, 15, 0.94);
  opacity: 0;
  visibility: hidden;
  transition: opacity 360ms var(--ease-out-soft), visibility 360ms;
}
@supports (backdrop-filter: blur(4px)) {
  .lightbox { backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
}
.lightbox.is-open { opacity: 1; visibility: visible; }
.lightbox__fig {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
  max-width: min(1100px, 94vw);
  margin: 0;
}
.lightbox__stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.lightbox__img {
  max-width: 100%;
  max-height: 76vh;
  object-fit: contain;
  background: var(--ink-2);
}
.lightbox__cap {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: center;
  color: var(--stone-100);
  text-align: center;
}
.lightbox__name { font-family: var(--font-display); font-size: clamp(1.375rem, 1rem + 1.6vw, 2rem); font-weight: 400; }
.lightbox__meta { font-size: var(--t-label); letter-spacing: 0.18em; text-transform: uppercase; color: var(--stone-400); }

/* detail thumbnail strip */
.lightbox__strip { display: flex; gap: 0.6rem; flex-wrap: wrap; justify-content: center; }
.lightbox__thumb {
  width: 56px; height: 56px; padding: 0; border: 1px solid rgba(248,246,243,.25);
  background: var(--ink-2); overflow: hidden; cursor: pointer;
}
.lightbox__thumb img { width: 100%; height: 100%; object-fit: cover; }
.lightbox__thumb[aria-current="true"] { border-color: var(--brass); }

.lightbox__btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px; height: 52px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(248,246,243,.08);
  border: 1px solid rgba(248,246,243,.2);
  color: var(--stone-50);
  transition: background-color var(--dur-mid) var(--ease-out-soft);
}
.lightbox__btn:hover { background: rgba(248,246,243,.2); }
.lightbox__btn--prev { left: -8px; }
.lightbox__btn--next { right: -8px; }
.lightbox__close {
  position: absolute;
  top: clamp(1rem, 0.5rem + 2vw, 2rem);
  right: clamp(1rem, 0.5rem + 2vw, 2rem);
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; color: var(--stone-100);
}
.lightbox__close:hover { color: var(--brass); }
@media (max-width: 620px) {
  .lightbox__btn { width: 44px; height: 44px; }
  .lightbox__btn--prev { left: -4px; } .lightbox__btn--next { right: -4px; }
}
body.lightbox-open { overflow: hidden; }

/* ==========================================================================
   HOMEPAGE HERO — text colour tokens
   Two tokens drive every piece of hero text, so swapping the frame sequence
   between light and dark footage is a one-class flip rather than a hunt
   through selectors.

   --hero-headline        the serif H1.
   --hero-text-secondary  supporting text: top nav links, hero sub-paragraph,
                          and the ghost "Request samples" button (text + border).

   CURRENT STATE: the powder-to-marble sequence is pale end to end, so
   index.html carries NO hero-stone--dark class — the hero runs ink-on-light
   and the "light sequence" patch rules below (scoped under
   html:not(.hero-stone--dark)) are the active set. Re-add the class on
   <html> to flip back for a future dark sequence; the dark scrim below is
   scoped to that flip and stands down with it.
   The flip class must sit above BOTH the header and the hero in the DOM,
   since the nav lives outside .hero-scrub.
   ========================================================================== */
:root {
  --hero-headline:       var(--ink);   /* #14110F — unchanged from before */
  --hero-text-secondary: #2a2a2a;      /* dark charcoal */
}

.hero-stone--dark {
  --hero-headline:       var(--stone-50);
  --hero-text-secondary: var(--stone-300);
}

/* Headline — same colour as before, now routed through the token. */
.hero-scrub h1 { color: var(--hero-headline); }

/* Hero sub-paragraph (overrides .on-dark .lead) */
.hero-scrub .lead { color: var(--hero-text-secondary); }

/* Showroom-still legibility patch (dark flip): over the floodlit floor the
   dark-mode secondary tones (stone-300 lead, stone-400 eyebrow/meta) sit
   borderline even with the deepened scrim — lift each one step. */
.hero-stone--dark .hero-scrub .lead { color: var(--stone-100); }
.hero-stone--dark .hero-scrub .label { color: var(--stone-200); }
.hero-stone--dark .hero-scrub .hero__meta li { color: var(--stone-200); }

/* ---- Light-sequence patch rules -------------------------------------
   Everything below in this group exists only for a PALE frame sequence,
   where the .on-dark defaults (near-white text, white logo) are invisible.
   All scoped under html:not(.hero-stone--dark): with the dark flip active
   they stand down and the .on-dark / .header--over defaults take over. */

/* Ghost "Request samples" — hero body copy (overrides .on-dark .btn--ghost) */
html:not(.hero-stone--dark) .hero-scrub .btn--ghost {
  --btn-fg: var(--hero-text-secondary);
  color: var(--hero-text-secondary);
  border-color: var(--hero-text-secondary);
}
html:not(.hero-stone--dark) .hero-scrub .btn--ghost:hover {
  background: var(--hero-text-secondary);
  border-color: var(--hero-text-secondary);
  color: var(--stone-50);
}

/* Top nav links while the header sits over the hero */
html:not(.hero-stone--dark) .header--over .nav a { color: var(--hero-text-secondary); }

/* Ghost "Request samples" — the one in the header, same light-on-light problem */
html:not(.hero-stone--dark) .header--over .btn--ghost {
  color: var(--hero-text-secondary);
  border-color: var(--hero-text-secondary);
}
html:not(.hero-stone--dark) .header--over .btn--ghost:hover {
  background: var(--hero-text-secondary);
  border-color: var(--hero-text-secondary);
  color: var(--stone-50);
}

/* Over pale stone the header--over defaults (white logo + light burger,
   meant for a dark hero) are invisible — show the colour logo and darken
   the burger. On scroll the header goes solid and the defaults take over. */
html:not(.hero-stone--dark) .header--over .brand__logo--white { display: none; }
html:not(.hero-stone--dark) .header--over .brand__logo--color { display: block; }
html:not(.hero-stone--dark) .header--over .menu-btn { color: var(--hero-text-secondary); }

/* On a pale sequence the .on-dark eyebrow/hairline/meta tones (stone-400 /
   brass / stone-300) fall to ~1.7–2.5:1 — lift them to legible muted values.
   With the dark flip active the .on-dark defaults are correct as-is. */
html:not(.hero-stone--dark) .hero-scrub .label { color: var(--stone-500); }
html:not(.hero-stone--dark) .hero-scrub .label-rule::before { background: var(--brass-deep); }
html:not(.hero-stone--dark) .hero-scrub .hero__meta li { color: var(--stone-500); }
html:not(.hero-stone--dark) .hero-scrub .hero__meta { border-top-color: var(--stone-200); }

/* Photo-legibility scrim behind the hero copy (load-bearing, per CLAUDE.md).
   The crystal footage is salt-and-pepper — lit white aggregate on near-black
   ground — so NO static text colour survives it unscreened. The sequence
   opens on black, and this vignette extends that native black ground in
   under the left-aligned copy, deepest at the text edge and gone by ~2/3
   width so the crystals keep their sparkle. Thin top and bottom bands carry
   the transparent header and the meta row. It rides on .hero-scrub__content,
   so it fades out together with the copy over the first ~60% of the scrub
   (see hero-scrub.js) and the compaction plays unobstructed.
   Scoped to the dark flip: the current pale sequence needs no scrim —
   ink text holds AA on the near-white frames unscreened. */
.hero-stone--dark .hero-scrub__content::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    /* left shadow under the copy block */
    linear-gradient(
      to right,
      rgba(16, 14, 12, 0.92) 0%,
      rgba(16, 14, 12, 0.74) 30%,
      rgba(16, 14, 12, 0.32) 56%,
      rgba(16, 14, 12, 0) 76%
    ),
    /* full-width bottom band under the meta row; the showroom still's
       floodlit floor fills the lower half, so this rises to ~58% to
       carry the lead/buttons as well — the slab display up-right stays
       unscreened */
    linear-gradient(
      to top,
      rgba(16, 14, 12, 0.85) 0%,
      rgba(16, 14, 12, 0.55) 32%,
      rgba(16, 14, 12, 0) 58%
    ),
    /* full-width top band under the transparent header (it goes solid on scroll) */
    linear-gradient(
      to bottom,
      rgba(16, 14, 12, 0.55) 0%,
      rgba(16, 14, 12, 0) 15%
    );
}
.hero-scrub__content > .wrap { position: relative; z-index: 1; }

/* ---- Mobile hero fixes (≤760px only; desktop untouched) ---------------
   1) The copy column is bottom-anchored inside an inset-0 box, so on short
      viewports its top edge can ride up under the transparent header —
      the eyebrow collided with the logo. Reserve the header zone with
      padding so the two can never meet, at any mobile width.
   2) The desktop vignette is a LEFT-edge wash; on a narrow portrait crop
      the copy sits over the bright marble floor mid-frame. Re-aim the
      scrim as a strong bottom-up wash under the whole copy block and
      lift the secondary text one step brighter. */
@media (max-width: 760px) {
  .hero-scrub__content {
    padding-top: calc(var(--header-h, 72px) + 1.25rem);
  }
  .hero-stone--dark .hero-scrub__content::before {
    background:
      /* bottom wash under the copy — strongest where the lead/buttons sit */
      linear-gradient(
        to top,
        rgba(16, 14, 12, 0.92) 0%,
        rgba(16, 14, 12, 0.78) 42%,
        rgba(16, 14, 12, 0.38) 68%,
        rgba(16, 14, 12, 0) 90%
      ),
      /* top band keeps the logo/burger legible over bright frames */
      linear-gradient(
        to bottom,
        rgba(16, 14, 12, 0.6) 0%,
        rgba(16, 14, 12, 0) 20%
      );
  }
  .hero-stone--dark .hero-scrub .lead { color: var(--stone-100); }        /* was stone-300 */
  .hero-stone--dark .hero-scrub .label { color: var(--stone-300); }       /* eyebrow, one step up */
  .hero-stone--dark .hero-scrub .hero__meta li { color: var(--stone-200); }
}

/* ---- Video hero variant (hero-scrub--video) ---------------------------
   The scroll-scrub is retired on the homepage in favour of a play-once
   background <video> cut from the 4K master (hero.mp4, ocean -> quartz;
   holds its final frame). The overlay copy and the ink scrim above are
   untouched — this modifier collapses the 260vh scrub track to a single
   viewport and swaps the background layer. hero-scrub.js / hero-frames/
   remain on disk, unwired, for easy revert.
   Doubled class: hero-scrub.css loads after main.css, so its 260vh height
   would win against a single-class selector. */
.hero-scrub.hero-scrub--video { height: 100vh; height: 100svh; }
.hero-scrub--video .hero-scrub__pin { position: relative; height: 100%; }
.hero-scrub__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Mobile: the copy block can be taller than a short viewport. A fixed
   100svh box would push the eyebrow up under the logo (bottom-anchored
   flex overflows upward), so let the hero GROW to fit its copy instead —
   full-screen on tall phones, taller-than-screen on short ones. The
   content leaves the absolute layer so it can size the section. */
@media (max-width: 760px) {
  .hero-scrub.hero-scrub--video { height: auto; min-height: 100svh; }
  .hero-scrub--video .hero-scrub__pin { height: auto; min-height: 100svh; }
  .hero-scrub--video .hero-scrub__content {
    position: relative;
    inset: auto;
    width: 100%;
    min-height: 100svh;
    /* doubled-class specificity: hero-scrub.css's `padding` shorthand loads
       after main.css and would flatten a single-class padding-top */
    padding-top: calc(var(--header-h, 72px) + 1.5rem);
  }
}

/* ==========================================================================
   SHOWROOM GALLERY  (gallery.html)
   Editorial masonry so the interior shots keep their natural aspect ratio —
   no uniform-tile cropping. CSS multicol, not grid: real masonry with wide
   browser support and no JS. Figures carry [data-reveal]; the 18px fade ends
   at transform:none, so nothing clips inside the columns after settling.
   ========================================================================== */
.showroom-gallery {
  columns: 3;
  column-gap: var(--s-4);
}
.showroom-gallery > figure {
  break-inside: avoid;
  margin: 0 0 var(--s-4);
}
.showroom-gallery img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 2px;
  background: var(--stone-200);
}
.showroom-gallery figcaption {
  margin-top: 0.7rem;
  font-size: var(--t-small);
  color: var(--fg-muted);
}
@media (max-width: 900px) { .showroom-gallery { columns: 2; } }
@media (max-width: 560px) { .showroom-gallery { columns: 1; } }

/* ==========================================================================
   WHATSAPP CONTACT — persistent floating link (all 7 pages)
   A plain <a>: never a popup, never auto-opens. Fixed bottom-right.
   Glyph is --brass-deep on --stone-50 (5.7:1, the text-safe pair per
   CLAUDE.md); a 1px --stone-200 border gives it an edge on same-tone
   backgrounds — no shadow. Hover is opacity only. No transform anywhere.

   Footer overlap (mobile rule): the button sits at z-index 90 — above page
   content but below the header/drawer/lightbox (99–200). The footer is given
   a higher stacking context so its opaque --ink background paints OVER the
   button at the page end. That keeps it clear of footer CTAs with pure CSS —
   no scroll listener, honouring the "no scroll animation" rule.
   ========================================================================== */
.wa-fab {
  position: fixed;
  right: max(1.25rem, env(safe-area-inset-right));
  bottom: max(1.25rem, env(safe-area-inset-bottom));
  z-index: 90;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border: 1px solid var(--stone-200);
  border-radius: 2px;
  background: var(--stone-50);
  color: var(--brass-deep);
  transition: opacity 160ms ease;
}
.wa-fab:hover { opacity: 0.82; }
.wa-fab:focus-visible {
  outline: 2px solid var(--brass-deep);
  outline-offset: 3px;
}
.wa-fab__glyph { display: block; width: 28px; height: 28px; }

/* Footer paints over the button at the page end (see note above). */
.footer { position: relative; z-index: 95; }

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

/* ==========================================================================
   MANUFACTURING NETWORK  (about.html #network)
   Static components (no reveal machinery) on an ink section. .pull-quote is
   the MD's positioning statement; .client-roll is the trusted-by strip.
   Both are dark-surface components for now — hairlines use the on-dark rule
   tone, text follows the .on-dark ladder (stone-300/400 = large/UI only).
   ========================================================================== */
.pull-quote {
  margin-top: clamp(2.75rem, 2rem + 3vw, 4.5rem);
  border-top: 1px solid rgba(248, 246, 243, 0.14);
  padding-top: clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
}
.pull-quote blockquote p {
  font-family: var(--font-display);
  font-weight: 300;
  font-style: italic;
  font-size: var(--t-display-s);
  line-height: 1.3;
  letter-spacing: -0.01em;
  max-width: 34ch;
  color: var(--stone-100);
}
.pull-quote figcaption {
  margin-top: 1.5rem;
  font-size: var(--t-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--stone-400);
}

.client-roll { margin-top: clamp(2.25rem, 1.75rem + 2vw, 3.5rem); }
.client-roll ul {
  list-style: none;
  margin: 1.1rem 0 0;
  padding: 1.25rem 0 0;
  border-top: 1px solid rgba(248, 246, 243, 0.14);
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2rem;
}
.client-roll li {
  font-size: var(--t-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--stone-300);
  font-weight: 500;
}
