/* ==========================================================================
   sections.css — Per-section layout
   Trin 1 establishes the structural shell: header, hero frame, footer, and
   the shared reveal mechanic. Section internals (work, services, about,
   contact) are filled in their own build steps.
   ========================================================================== */

/* --- Signature reveal ----------------------------------------------------
   ONE movement reused across the whole site: a weighty fade + short rise.
   Default state set in CSS so content is hidden before GSAP boots (no flash);
   JS adds .is-ready on <html> only when motion is allowed, then animates.
   If JS/motion is unavailable, the fallback below keeps everything visible.

   NB: no static `will-change` here on purpose. These reveals are once:true, so
   a permanent will-change would pin a compositor layer (with its image texture)
   on every [data-reveal] for the whole session — dozens of them — which made
   the image-heavy case sections jank on scroll. The brief reveal tween doesn't
   need a pre-promoted layer.                                                  */

html.is-ready [data-reveal] {
  opacity: 0;
  transform: translateY(var(--reveal-y));
}

/* No-JS / reduced-motion safety net: never leave content invisible.         */
html:not(.is-ready) [data-reveal] {
  opacity: 1;
  transform: none;
}

/* Wipe reveal (left→right via clip-path) — used on the Studio support copy.
   Clipped from the right only when motion is allowed + JS has booted; the full
   text shows otherwise. JS opens the clip on scroll (see initWipeReveals). */
html.is-ready [data-reveal-wipe] {
  -webkit-clip-path: inset(0% 100% 0% 0%);
  clip-path: inset(0% 100% 0% 0%);
}

/* ==========================================================================
   Site header
   ========================================================================== */

.site-header {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  /* Logo's left edge flush with the hero headline (shared --hero-indent); the
     right keeps the page gutter so the nav stays at the edge. */
  padding-block: var(--space-s);
  padding-inline: var(--hero-indent) var(--gutter);
  /* At the top the bar is invisible so the hero reads clean; once the page
     scrolls it condenses into a glassy strip (.is-scrolled, toggled by JS). */
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 0.3s ease, border-color 0.3s ease;
}

/* Scrolled state: a near-opaque dark bar with a hairline. We deliberately avoid
   backdrop-filter: blurring a fixed bar forces a re-blur of the (moving, video)
   content behind it every scroll frame, which made the hero→Studio scroll
   janky. A high-opacity solid reads just as clean and costs nothing per frame. */
.site-header.is-scrolled {
  background: rgba(13, 13, 15, 0.92);
  border-bottom-color: var(--line);
}

/* Soft burnt-orange glow behind the brand, lit only in the glass state so it
   never competes with a clean hero. It sits above the glass fill (no negative
   z-index — that would hide it behind the 0.72 dark) and below the header
   content, which is lifted to z-index 1. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse at left center,
    rgba(200, 100, 60, 0.08) 0%,
    transparent 60%
  );
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.site-header.is-scrolled::before {
  opacity: 1;
}

.site-header__brand {
  position: relative;
  z-index: 1; /* above the glow */
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-semi);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
}

/* Accent period after the wordmark — a small brand mark in the orange accent.
   inline-block so the intro can scale it (see initBrandDot in animations.js). */
.brand-dot {
  color: var(--accent);
  display: inline-block;
}

/* Hidden until the JS intro pops it in — motion only; visible by default so
   reduced-motion / no-JS keep the full "Nielsen Studio." */
html.is-ready .brand-dot {
  opacity: 0;
  transform: scale(0);
}

.site-nav {
  position: relative;
  z-index: 1; /* above the glow */
  display: flex;
  align-items: center;
  gap: var(--space-m);
}

.site-nav__link {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  /* Was --text-muted, which read too faint over the dark hero; the off-white
     --text (plus a slightly heavier weight) makes the links clearly legible
     while accent hover/active still stand out. */
  color: var(--text);
  transition: color var(--dur-fast) var(--ease-out);
}

/* Accent underline that grows from the left on hover and stays for the current
   section. The line is laid out at full width and revealed via scaleX, so the
   animation runs on the compositor (no layout work). */
.site-nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -4px;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}

.site-nav__link:hover,
/* Scroll-spy current-section state (set by nav.js) — burnt orange "you are
   here" that matches the site's accent. */
.site-nav__link.is-active {
  color: var(--accent);
}

.site-nav__link:hover::after,
.site-nav__link.is-active::after {
  transform: scaleX(1);
}

/* Hairline that separates the nav links from the Contact CTA — gives the right
   side rhythm instead of four items in a flat row. */
.site-nav__divider {
  width: 1px;
  height: 1.3em;
  background: var(--line-strong);
}

/* Contact CTA restyled as a text link in the nav's mono language rather than a
   boxed button — slightly larger and accent-coloured so it still reads as the
   primary action, with an arrow that nudges on hover. The .btn / .btn--ghost
   box styles from base.css are intentionally reset here. */
.site-nav .btn--ghost {
  border: none;
  background: none;
  padding: 0;
  border-radius: 0;

  display: inline-flex;
  align-items: center;
  gap: 0.4rem;

  font-family: var(--font-mono);
  font-size: 0.95rem; /* a touch larger than the nav links */
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
  text-shadow: 0 0 12px rgba(200, 100, 60, 0.35);
  transition: color var(--dur-fast) var(--ease-out),
    text-shadow var(--dur-fast) var(--ease-out);
}

.site-nav .btn--ghost::after {
  content: "→";
  transition: transform var(--dur-fast) var(--ease-out);
}

.site-nav .btn--ghost:hover {
  color: var(--text);
  text-shadow: 0 0 18px rgba(200, 100, 60, 0.55);
}

.site-nav .btn--ghost:hover::after {
  transform: translateX(4px);
}

/* Collapse nav links on small screens; brand + CTA remain (menu added later).*/
@media (max-width: 48rem) {
  /* The hero text falls back to the plain gutter here, so match the logo to it. */
  .site-header {
    padding-inline: var(--gutter);
  }
  .site-nav {
    gap: var(--space-s);
  }
  .site-nav__link,
  .site-nav__divider {
    display: none;
  }
}

/* ==========================================================================
   Hero — frame only (3D canvas + content fill in their build steps)
   ========================================================================== */

.hero {
  position: relative;
  box-sizing: border-box;
  min-height: 100svh;
  display: grid;
  /* Split layout: text fills the left column, the 3D canvas the right half (the
     canvas is pinned there absolutely — see .hero__canvas). The text column gets
     a touch more room (1.05 vs 0.95) since the headline is the priority. */
  grid-template-columns: 1.05fr 0.95fr;
  gap: var(--gutter);
  align-items: center;
  /* Reserve room for the fixed header so the vertically-centred content never
     tucks under it. With border-box + min-height, this padding lives INSIDE the
     one viewport height — the whole hero (text + object) stays on one screen. */
  padding-block: calc(var(--header-h) + var(--space-s)) var(--space-l);
  overflow: hidden;
  isolation: isolate; /* keep the hero's layers (video → canvas) self-contained */
}

/* Mobile: single column — the live canvas no-ops here, so the fallback image
   fills the hero full-bleed again behind the full-width text. */
/* Mobile hero layout overrides live at the END of the hero block (after the
   base .hero__canvas / .hero__content rules) so they win on source order.
   Placed here — above those bases — they were silently overridden, which left
   the desktop split on phones. See the @media (max-width: 48rem) below .hero__tech. */

/* --- Smoke background loop ------------------------------------------------
   Sits at the very bottom of the hero. The transparent 3D canvas renders the
   object over it; reduced-motion / mobile / no-WebGL hide the video and the
   canvas's static fallback image takes over (see .hero__fallback below). */
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.hero__bg-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 70% 50%; /* match the fallback framing */
  /* No CSS filter: the brightness/contrast/saturation lift is baked into the
     video file, so the browser doesn't run a filter pass on this full-bleed
     video every frame (removes a per-frame cost during scroll). */
}

/* Overlay between the video and the canvas: a light, vignette-leaning wash so
   the object's burnt-orange rim light stays the brightest thing on screen,
   while the smoke clearly shows through. Kept gentle — the scrim (below) does
   the heavy lifting for headline contrast on the left. */
.hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
      120% 115% at 68% 45%,
      transparent 0%,
      rgba(13, 13, 15, 0.20) 100%
    ),
    linear-gradient(to bottom, rgba(13, 13, 15, 0), rgba(13, 13, 15, 0.10));
}

/* Bottom fade-to-dark — the smoke resolves to pure base colour at the hero's
   lower edge so it blends seamlessly into the Studio section. Above the video,
   below the canvas object and the text. */
.hero__fade-bottom {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 180px;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--bg));
}

/* Reduced motion: no autoplay — fall back to the static hero image. */
@media (prefers-reduced-motion: reduce) {
  .hero__bg-video {
    display: none;
  }
}

/* Mobile: skip the video (performance + data); the static hero image carries
   the section. The 3D canvas already no-ops here (see hero3d.js). */
@media (max-width: 48rem) {
  .hero__bg-video {
    display: none;
  }
}

/* No live canvas (no-WebGL on desktop, etc.): the static fallback image fills
   the hero, so the video behind it is just wasted bytes — drop it. */
.hero__canvas[data-fallback] ~ .hero__bg .hero__bg-video {
  display: none;
}

/* Three.js canvas mounts here, pinned to the right of the hero. The object
   centres in this box, so its left edge (38%) sets how far toward the middle
   the object lands — keeping it off the far-right edge and shrinking the empty
   middle. Object SIZE is set by canvas height + FOV, not width.
   Full height so the renderer always has a definite size. (Full-bleed on
   mobile — see the media query above.)                                       */
.hero__canvas {
  position: absolute;
  inset: 0 0 0 32%; /* top right bottom 0, left 32% → object centres ~66% across */
  z-index: 3; /* above the smoke video (0), overlay (1) and scrim (2) */
  /* The injected <canvas> and the fallback <img> both fill this box.         */
}

.hero__canvas > canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

/* Scrim: a soft left-to-right wash so headline contrast holds over the
   brightest part of the render. Fixed layer, no animation, no input capture. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2; /* over the smoke/overlay for headline contrast, under the canvas */
  pointer-events: none;
  background: linear-gradient(
    100deg,
    var(--bg) 0%,
    rgba(13, 13, 15, 0.78) 32%,
    rgba(13, 13, 15, 0.30) 64%,
    rgba(13, 13, 15, 0) 100%
  );
}

/* Static fallback image — hidden until JS declares the fallback path.        */
.hero__fallback {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 70% 50%;
  opacity: 0;
}

.hero__canvas[data-fallback] .hero__fallback {
  opacity: 1;
}

/* When the live canvas mounts, the fallback img is redundant — hide it so it
   never peeks through the alpha canvas.                                      */
.hero__canvas[data-ready] .hero__fallback {
  display: none;
}

.hero__content {
  position: relative;
  z-index: var(--z-raised);
  grid-column: 1; /* left half of the 50/50 split */
  /* Nudge up from the grid's vertical centre — transform so it doesn't shift
     layout or fight the children's reveal tween. */
  transform: translateY(-2rem);
  /* Left edge aligns with the header wordmark — both use var(--gutter) — for one
     consistent margin down the page. max-width carries the headline toward the
     centre so there's calm space, not a dead gap, before the object. */
  max-width: 44rem;
  /* Set in from the edge so the text doesn't lean against the side; shares
     --hero-indent with the header logo so the two flush vertically. */
  padding-inline-start: var(--hero-indent);
  /* No vertical padding — the hero grid centres this block, and the hero's own
     padding reserves the header space. */
  padding-block: 0;
}

.hero__title {
  font-size: var(--step-4); /* a touch smaller so the whole block fits one screen */
  margin-block: var(--space-xs) var(--space-s);
  /* Words are never split. Override reset.css's break-word on headings: each
     word stays whole and lines break only between words. The column above is
     sized so even "collateral" at the max font fits on one line. */
  overflow-wrap: normal;
  word-break: normal;
  hyphens: none;
  text-wrap: balance;
}

/* Drag-to-rotate affordance, lower-right. Only meaningful with a live canvas;
   hidden whenever the static fallback is in play.                           */
.hero__hint {
  position: absolute;
  z-index: var(--z-raised);
  inset-block-end: var(--space-l);
  inset-inline-end: var(--gutter);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.hero__canvas[data-fallback] ~ .hero__hint {
  display: none;
}

/* Quiet technical signature for the hand-built 3D object — same mono treatment
   as the drag hint but dimmer, so it reads as a signature, not a label. Sits
   just above the hint, and hides with it when there's no live canvas. */
.hero__tech {
  position: absolute;
  z-index: var(--z-raised);
  inset-block-end: calc(var(--space-l) + 1.7rem);
  inset-inline-end: var(--gutter);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.6;
}

.hero__canvas[data-fallback] ~ .hero__tech {
  display: none;
}

.hero__hint-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--accent);
  /* Gentle pulse to draw the eye; disabled under reduced motion (reset.css). */
  animation: hero-hint-pulse 2.4s var(--ease-in-out) infinite;
}

@keyframes hero-hint-pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(0.7); }
}

@media (max-width: 48rem) {
  /* Single column; the object fills the hero full-bleed behind the copy, which
     spans the gutter. These MUST come after the base .hero__canvas /
     .hero__content rules above — equal specificity, so source order wins. With
     the overrides up near .hero (before those bases) they were dead, leaving the
     desktop left:32% split + 44rem copy on phones: a hard vertical seam across
     the object and an overflowing headline. */
  .hero {
    grid-template-columns: 1fr;
  }
  .hero__canvas {
    inset: 0;
  }
  .hero__content {
    max-width: none;
    padding-inline: var(--gutter);
  }

  /* On phones the fallback image carries the hero; drop the hint + signature. */
  .hero__hint,
  .hero__tech {
    display: none;
  }
}

/* ==========================================================================
   Intro / positioning
   Two calm columns: a soft mood image (left) beside the copy (right). The
   image is a quiet atmospheric layer — masked to fade out on every edge so it
   melts into the background with no hard rectangle anywhere.
   ========================================================================== */

.intro.section {
  position: relative;
  isolation: isolate; /* contain the background layers below the copy */
  /* No overflow:hidden — the parallax it clipped is gone, and clipping here was
     cropping the bottom of the facts grid. The bg layers are inset:0 anyway. */
  padding-block: var(--space-2xl);
}

/* Mobile-first: single column, image above the copy. */
.intro__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-l);
  align-items: center;
}

.intro__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  max-width: 46ch;
}

/* The positioning statement — editorial serif, not centered. */
.intro__statement {
  font-size: var(--step-2);
  font-weight: var(--weight-regular);
  line-height: 1.14;
  letter-spacing: -0.01em;
  max-width: 32ch;
}

/* The single accent moment in this section — one phrase, the differentiator. */
.intro__accent {
  color: var(--accent);
}

.intro__support {
  font-size: var(--step-1);
  line-height: var(--leading-normal);
  color: var(--text);
  max-width: 44ch;
}

/* Facts: label / value rows divided by hairlines. Mono labels for dev credibility. */
.intro__facts {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-2xs);
}

.intro__fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  padding-block: var(--space-xs);
  border-top: 1px solid var(--line);
}

.intro__fact:last-child {
  border-bottom: 1px solid var(--line);
}

.intro__fact dt {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.intro__fact dd {
  font-size: var(--step-0);
  color: var(--text);
  max-width: 42ch;
}

/* --- Full-section background loop -----------------------------------------
   A muted, looping studio video covers the whole section behind the copy.
   The still (intro-texture.jpg) is the poster + the fallback shown under
   reduced-motion and on mobile; a dark overlay guarantees text legibility. */
.intro__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.intro__bg-still,
.intro__bg-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Still sits underneath (gentle lift); the video layers over it once it plays.
   The footage is very dark, so the video gets a stronger lift to actually read. */
.intro__bg-still {
  z-index: 0;
  filter: brightness(1.12) contrast(1.03) saturate(1.06);
}

.intro__bg-video {
  z-index: 1;
  /* No CSS filter — the brightness/contrast/saturation is baked into the video
     file, so no per-frame filter pass runs while the section is on screen. */
}

/* Symmetric overlay: dimmed in the CENTRE so the centred copy stays crisp,
   feathering to near-clear at the edges so the smoke (which rises from both
   bottom corners) frames the text on both sides. A smooth radial — the
   readability backing is the feather itself, so there's no visible box edge. */
.intro__overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: radial-gradient(
    78% 90% at 50% 48%,
    rgba(13, 13, 15, 0.6) 0%,
    rgba(13, 13, 15, 0.32) 42%,
    rgba(13, 13, 15, 0.06) 76%,
    rgba(13, 13, 15, 0) 100%
  );
}

/* Top fade-to-dark — mirror of the hero's bottom fade. The Studio smoke starts
   from pure base colour at the top edge, so the two sections meet in a shared
   dark zone. Sits above the video + overlay (inside .intro__bg), below the copy. */
.intro__fade-top {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 180px;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(to bottom, var(--bg), transparent);
}

/* Bottom fade — the smoke toner ud i mørket so there's no hard lower edge.
   Taller than the top fade because the Work section pulls up over this zone
   (see .work.section), so the smoke should already be well into the dark. */
.intro__fade-bottom {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 42vh;   /* slow dissolve, short enough not to swallow the smoke */
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(to top, var(--bg), transparent);
}

/* Keep the copy above all background layers. */
.intro .container {
  position: relative;
  z-index: 1;
}

/* Reduced motion: never autoplay — leave the calm still in place. */
@media (prefers-reduced-motion: reduce) {
  .intro__bg-video {
    display: none;
  }
}

/* Mobile: skip the video (performance + data) and show the still instead. */
@media (max-width: 47.99rem) {
  .intro__bg-video {
    display: none;
  }
}

/* Desktop: editorial layering. The image is a large layer on the left whose
   right edge slides UNDER the copy; the copy sits on top with a strong dark
   backing so it stays razor-sharp. Section stays locked to one screen. */
@media (min-width: 48rem) {
  .intro.section {
    min-height: 80dvh; /* trimmed — less empty air above and below */
    display: grid;
    align-items: center;
    padding-top: var(--space-l);
    padding-bottom: 10vh; /* room for the BASED IN / WHAT I MAKE columns above the seam */
  }

  /* Single track — the image is absolute, the copy is placed on the right and
     overlaps it. Relative so the image positions against this box. */
  .intro__grid {
    position: relative;
    grid-template-columns: 1fr;
    gap: 0;
  }

  /* Centred editorial block — deliberately different from the hero's left
     anchor, to vary the rhythm down the page. The block sits in the calm dark
     middle; the smoke frames it on both sides. Text stays left-aligned inside. */
  .intro__text {
    position: relative;
    z-index: 2; /* above the background layers */
    justify-self: center;
    max-width: 640px; /* comfortable measure; lines never run too long */
    gap: var(--space-xs);
    text-align: left;
  }

  .intro__statement {
    font-size: 1.95rem;
    max-width: 560px; /* keep headline lines tight */
  }

  .intro__support {
    font-size: var(--step-0);
    max-width: 52ch;
  }

  /* Facts as three even columns in one balanced row (was 2 + 1 orphan). */
  .intro__facts {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    column-gap: var(--space-s);
    margin-top: var(--space-s);
  }

  .intro__fact {
    padding-block: var(--space-2xs);
  }

  .intro__fact:last-child {
    border-bottom: 0; /* grid uses only the top hairlines to structure cells */
  }

  /* A whisper of the hero's burnt-orange light at the base centre, where the
     smoke rises, to give it warmth. Plain radial (no mix-blend-mode — that
     recomposited every frame and caused jank). */
  .intro__overlay::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
      62% 46% at 50% 80%,
      rgba(200, 100, 60, 0.1),
      transparent 60%
    );
  }
}

/* ==========================================================================
   Section heading block (shared by work / services / about)
   ========================================================================== */

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  max-width: 52ch;
  margin-bottom: var(--space-xl);
}

/* Short context line under a section heading — muted, sits tight beneath it. */
.section-lead {
  font-size: var(--step-0);
  color: var(--text-muted);
  max-width: 48ch;
  margin-top: calc(var(--space-2xs) * -1);
}

/* ==========================================================================
   Selected work — cases
   Asymmetric zig-zag, never a row of equal cards. The primary case (NAS) is
   large and multi-part to show web + documents in one project; the secondary
   case (Royal Nectar) is a single quieter row.
   ========================================================================== */

/* Tighten the vertical air markedly: much less empty space above the heading,
   and a closer gap down to the first case.

   Overlap: Work pulls its solid dark background up over the Studio smoke's
   bottom fade, so the two sections share a dark seam instead of butting edge to
   edge. The negative margin only moves the box up; the matching extra top
   padding restores the heading's original gap, so the content rhythm above is
   unchanged — only the dark overlap is new. position/z-index keep this opaque
   layer painting over the smoke. */
.work.section {
  position: relative;
  z-index: 1;
  /* Transparent at the top so the smoke's long fade reads straight through the
     seam (no hard solid-dark edge chopping the dissolve), then resolves to the
     base colour for the cases below. The smoke is let to live further down
     before it tones out. */
  background: linear-gradient(
    to bottom,
    transparent 0%,
    transparent 15%,
    var(--bg) 50%
  );
  margin-top: -4vh;   /* gentle — just enough to layer over the smoke, not swallow it */
  padding-top: 14vh;  /* pushes the Work content back down, clear of the seam */
  /* Bottom pulled tighter than the other studio sections on purpose: the Work
     section ends in the case's media IMAGE (not text), so a hard image edge
     earns a shorter gap into Services than the text→text transitions elsewhere.
     --space-xl brings Work→Services to ~187px (vs ~228 for the rest). Top +
     margin unchanged (smoke overlap intact). */
  padding-bottom: var(--space-xl);
}

/* The work heading is the one centred block — a calm "now, the work" moment
   that introduces the cases, rather than a left-aligned section label. */
.work .section-head {
  margin-inline: auto;
  align-items: center;
  text-align: center;
  max-width: 54ch;
  margin-bottom: var(--space-2xl);
  margin-top: -8vh;   /* lift the heading up into the tail of the smoke's dissolve */
  position: relative;
  z-index: 4;         /* keep the heading above the fade layer */
}

/* Space between cases comes from rhythm, not boxes. */
.case + .case {
  margin-top: var(--space-3xl);
}

/* Relative so the vertical meta-marker can anchor to the case's side margin. */
.case {
  position: relative;
}

/* Vertical meta-marker living in the case's empty left margin. It reads the
   case's own scoped --accent, so NAS shows cyan and Royal Nectar gold with no
   per-case colour here. Decorative (aria-hidden) and never blocks interaction.
   transform-origin: center + translate(-50%, -50%) pins the label's centre to
   (left, top) regardless of its length, so rotation stays balanced. */
.case__marker {
  position: absolute;
  top: 50%;
  left: 1.5rem;
  transform: translate(-50%, -50%) rotate(-90deg);
  transform-origin: center;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: var(--accent);
  opacity: 0.4;
  pointer-events: none;
}

/* The side margin only opens up once the viewport is wider than the 72rem
   content band; below that the marker would sit on top of the text, so drop
   it entirely. */
@media (max-width: 78rem) {
  .case__marker {
    display: none;
  }
}

.case__head {
  max-width: 60ch;
  margin-bottom: var(--space-xl);
}

.case__title {
  font-size: var(--step-4);
  margin-block: var(--space-2xs) var(--space-s);
}

.case__title--sm {
  font-size: var(--step-3);
}

.case__summary {
  font-size: var(--step-1);
  line-height: var(--leading-normal);
  color: var(--text);
  max-width: 54ch;
}

/* Two-column case header: the existing prose on the left, a compact meta list
   on the right that balances the otherwise-empty space beside the title. Drops
   to a single stacked column on mobile so nothing gets crushed. */
.case__head--split {
  max-width: none;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(15rem, 20rem);
  gap: var(--space-2xl);
  align-items: start;
}

.case__head-main {
  max-width: 60ch; /* keep the headline + summary on a comfortable measure */
}

/* Meta list reuses the .facts rows; only the labels switch to the case accent
   (cyan in NAS via the scoped --accent), values stay normal text. */
.case__meta {
  margin: 0;
}

.case__meta .fact dt {
  color: var(--accent);
}

@media (max-width: 48rem) {
  .case__head--split {
    grid-template-columns: 1fr;
    gap: var(--space-l);
  }
  .case__head-main {
    max-width: none;
  }
}

/* NAS brand cyan — deliberately scoped to the NAS case only, a nod to the
   client's own accent (#3BB6E8 from their documents). Overriding --accent here
   means everything inside the case that reads the variable — the Case index,
   scope pills, part-labels and the live-link — inherits cyan automatically,
   while the rest of the site keeps its burnt orange. */
.case--primary {
  --nas-cyan: #3BB6E8;
  --accent: var(--nas-cyan);
  --accent-hover: #5cc4ed; /* cyan lifted for the live-link hover */
}

.nas-accent {
  color: var(--nas-cyan);
}

/* Royal Nectar brand gold — same scoped-accent treatment as the NAS case,
   using the client's own gold from their site. Overriding --accent here means
   the Case index, the "Nectar" keyword, the part-label and the status dot all
   inherit gold, while NAS stays cyan and the rest of the site stays orange. */
.case--royal {
  --accent: #C8860A;       /* Royal Nectar primary gold              */
  --accent-hover: #E7A829; /* their lighter gold, for hover/active   */
}

/* Scope tags — mono pills that live in the case's own colour world: a dimmed
   accent border at rest, filling with the full accent on hover. Because they
   read the scoped --accent, they're cyan in NAS and gold in Royal Nectar with
   no per-case colour here. */
.case__scope {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  list-style: none;
  padding: 0;
  margin-top: var(--space-m);
}

.case__scope li {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
  padding: var(--space-3xs) var(--space-xs);
  /* Dimmed accent border — color-mix derives the tint straight from the scoped
     --accent, so the pill tracks each case (cyan / gold) automatically. */
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px;
  background-color: transparent;
  transition: color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}

/* Hover: fill with the case accent, dark text for contrast, a small lift. */
.case__scope li:hover {
  color: var(--bg);
  background-color: var(--accent);
  border-color: var(--accent);
  transform: translateY(-2px);
}

/* Reusable facts list — label / value rows with hairline dividers, the same
   treatment used in the Studio section. */
.facts {
  display: flex;
  flex-direction: column;
}

.fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  padding-block: var(--space-xs);
  border-top: 1px solid var(--line);
}

.fact:last-child {
  border-bottom: 1px solid var(--line);
}

.fact dt {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.fact dd {
  font-size: var(--step-0);
  color: var(--text);
}

/* Facts + live-site link filling out the case text column. */
.case__facts {
  margin-top: var(--space-m);
  max-width: 36ch;
}

.case__live-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  align-self: flex-start;
  margin-top: var(--space-m);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
}

.case__live-link span {
  transition: transform var(--dur-fast) var(--ease-out);
}

.case__live-link:hover {
  color: var(--accent-hover);
}

.case__live-link:hover span {
  transform: translateX(4px);
}

/* Status marker — same mono treatment + placement as the live-site link, for a
   case whose site isn't on its real URL yet. Muted label, orange signal dot. */
.case__status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  align-self: flex-start;
  margin-top: var(--space-m);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.case__status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--accent);
}

/* Cover image — wide, sets up the case. */
.case__cover {
  margin-top: var(--space-l);
}

/* Cover that can hold a looping video over the static poster. Both fill the
   16:9 frame; the video sits on top once it can play. */
.case__cover-still,
.case__cover-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case__cover-video {
  z-index: 1;
}

/* Reduced motion + mobile: no autoplay — the static poster carries the cover.
   display:none also stops the IntersectionObserver from loading/playing it. */
@media (prefers-reduced-motion: reduce) {
  .case__cover-video {
    display: none;
  }
}

@media (max-width: 47.99rem) {
  .case__cover-video {
    display: none;
  }
}

/* Zig-zag rows: image one side, text the other; alternate via --reverse. */
.case__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-l);
  margin-top: var(--space-2xl);
}

@media (min-width: 48rem) {
  .case__row {
    grid-template-columns: 1.1fr 0.9fr;
    gap: var(--space-2xl);
    align-items: center;
  }

  /* Reverse: text moves to the left, media to the right. */
  .case__row--reverse .case__media-col {
    order: 2;
  }
}

.case__text-col {
  display: flex;
  flex-direction: column;
}

.case__text-col p {
  max-width: 46ch;
  line-height: var(--leading-normal);
}

/* Mono label that names each part of the project (web vs print). The one
   restrained accent on these labels reads as a wayfinding signal. */
.case__part-label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-xs);
}

/* Document pair — two pages side by side within the media column. */
.case__media-col--pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-s);
}

/* Stacked media — e.g. the browser frame above a flat blueprint detail. */
.case__media-col--stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
}

/* Documents block — a text lead-in above, then the two documents side by side
   across the full container width. No empty column, no dead space. */
.case__documents {
  margin-top: var(--space-2xl);
}

.case__doc-head {
  max-width: 56ch;
  margin-bottom: var(--space-xl);
}

.case__doc-title {
  font-size: var(--step-2);
  margin-bottom: var(--space-s);
}

.case__doc-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-l);
}

/* Two columns from 720px; the landscape deck spans the full width beneath the
   two A4 portraits. Below 720px everything stacks into one column. */
@media (min-width: 45rem) {
  .case__doc-grid {
    grid-template-columns: 1fr 1fr;
  }
  .case__doc-card--wide {
    grid-column: 1 / -1;
  }
}

/* Document card: a hairline frame around the cover, with a mono label beneath.
   Lifts on hover — a short rise, a growing shadow, and a faint orange edge. */
.case__doc-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background-color: var(--surface);
  transition: transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.case__doc-card:hover {
  transform: translateY(-8px);
  box-shadow: 0 18px 40px -10px rgba(0, 0, 0, 0.55);
  border-color: rgba(200, 100, 60, 0.3);
}

/* This hover glow is a hardcoded orange rgba, so it can't follow the scoped
   --accent override above — restate it in NAS cyan inside the case. */
.case--primary .case__doc-card:hover {
  border-color: rgba(59, 182, 232, 0.3);
}

/* The card supplies the frame + corners, so the inner cover drops its own. */
.case__doc-card .media {
  border-radius: 0;
}

/* --- Document cover lightbox ---------------------------------------------
   JS (lightbox.js) wires each card as a button and reuses one overlay. The
   zoom-in cursor is added only on JS-wired cards, so non-JS users don't see a
   misleading affordance. */
.case__doc-card.is-zoomable {
  cursor: zoom-in;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: grid;
  place-items: center;
  padding: var(--gutter);
  background: rgba(13, 13, 15, 0.9);
  /* Hidden + non-interactive until opened; visibility keeps it out of the tab
     order while closed, and transitions with the fade. */
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-fast) var(--ease-out),
    visibility var(--dur-fast) var(--ease-out);
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
}

.lightbox__img {
  max-width: min(90vw, 880px);
  max-height: 88vh;
  object-fit: contain;
  border-radius: var(--radius);
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.7);
  transform: scale(0.97);
  transition: transform var(--dur-base) var(--ease-out);
}

.lightbox.is-open .lightbox__img {
  transform: scale(1);
}

.lightbox__close {
  position: absolute;
  top: var(--space-m);
  right: var(--space-m);
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: rgba(13, 13, 15, 0.6);
  color: var(--text);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.lightbox__close:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* A4 portrait covers (Company Profile + datasheet); the deck stays .media--wide. */
.media--doc {
  aspect-ratio: 210 / 297;
}

/* Label under each cover — same mono label language as the nav / eyebrows. */
.case__doc-label {
  padding: var(--space-xs) var(--space-s);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ==========================================================================
   Media frame + placeholder
   Real assets cover the box; until a file exists, [data-missing] shows a
   labelled placeholder at the right aspect ratio (set by initMediaFallback).
   ========================================================================== */

.media {
  position: relative;
  overflow: hidden;
  background-color: var(--surface);
  border-radius: var(--radius);
}

.media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.media--wide {
  aspect-ratio: 16 / 9;
}

/* Placeholder state: hide the (absent) image, show a dashed slot + filename. */
.media[data-missing] {
  border: 1px dashed var(--line-strong);
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0 11px,
    rgba(244, 242, 236, 0.025) 11px 12px
  );
}

.media[data-missing] img {
  display: none;
}

.media[data-missing]::after {
  content: attr(data-label);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-m);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

/* --- Discreet browser frame for site screenshots ------------------------- */

.browser {
  background-color: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-soft);
}

.browser__bar {
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--space-2xs) var(--space-s);
  border-bottom: 1px solid var(--line);
}

.browser__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background-color: var(--accent); /* case accent — cyan (NAS) / gold (Royal) */
}

/* Tonal "traffic light" in the single accent hue — stepped opacity so it still
   reads as browser chrome rather than three identical accent dots. */
.browser__dot:nth-child(2) {
  opacity: 0.55;
}
.browser__dot:nth-child(3) {
  opacity: 0.3;
}

.browser__url {
  margin-left: var(--space-s);
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--text-muted);
  letter-spacing: 0.02em;
}

/* Discreet, neutral security padlock — inherits the muted URL colour (never the
   accent), so it reads like a real browser's lock glyph. */
.browser__lock {
  flex: none;
  opacity: 0.75;
}

/* The viewport is also a .media (placeholder-capable) but the frame supplies
   the corners, so drop its own radius. */
.browser__viewport {
  aspect-ratio: 16 / 9;
  border-radius: 0;
}

/* ==========================================================================
   Services — package tiers (04)
   Three pricing cards (A · B · C) in a grid that stacks to one column on
   mobile. This is Nielsen Studio's own section, so the accent stays the global
   burnt orange (var(--accent)) — never a case colour. Cards earn their surface
   here (a genuine card layout); kept quiet with hairline borders and the same
   mono labels / Fraunces headings as the rest of the site. Pakke C is brought
   gently forward: a subtle orange edge, a badge, and a small lift on desktop.
   ========================================================================== */

/* Tighten the section's vertical air so a whole card (name → price → all
   bullets → CTA) fits on one screen alongside the heading, keeping the three
   tiers comparable at a glance. One step down the spacing scale each — top air
   and the head's lower gap — so nothing feels cramped.

   Top padding reserves the fixed header's height plus a full --space-xl of
   clearance (same --header-h technique as .hero). --header-h slightly
   under-reads the rendered bar, so header-h + space-m was too tight and the
   label bled through the glass; header-h + space-xl lands the heading clearly
   below the bar with air to spare — in the same band as the Studio/Work/About
   sections that already clear it. Bottom keeps the tightened rhythm. */
.services.section {
  /* The top padding reserves the fixed header's height + --space-l of air, so an
     anchor jump to #services clears the bar on its own while landing the heading
     a bit higher in the viewport (so the card CTAs stay on the fold). No extra
     scroll-margin-top — that pushed the landing too far down. Bottom keeps the
     tightened rhythm. */
  padding-block: calc(var(--header-h) + var(--space-l)) var(--space-2xl);
}

.services .section-head {
  margin-bottom: var(--space-m);
  gap: var(--space-xs); /* tighter than the default --space-s, to recover fold */
}

.pricing {
  display: grid;
  grid-template-columns: 1fr; /* mobile-first: one column, cards stacked */
  gap: var(--space-m);
  align-items: start; /* so the featured card's lift doesn't stretch siblings */
}

/* Three across once there's room; below that they stack. */
@media (min-width: 58rem) {
  .pricing {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-s);
  }
}

.pricing__card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  /* Vertical padding tightened (--space-m → --space-s) so the full card fits on
     one screen; horizontal stays --space-m, keeping the card's width feel and
     the badge's inset alignment unchanged. */
  padding: var(--space-s) var(--space-m);
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition:
    border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

.pricing__card:hover {
  border-color: var(--line-strong);
}

/* Pakke C — the complete system. Subtle orange edge + faint inner wash so it
   reads as primary without shouting; a small lift on desktop (via margin, not
   transform, which the reveal tween animates and would otherwise clear). */
.pricing__card--featured {
  border-color: var(--accent);
  background-image: linear-gradient(
    to bottom,
    var(--accent-soft),
    transparent 40%
  );
  box-shadow: var(--shadow-soft);
}

.pricing__card--featured:hover {
  border-color: var(--accent-hover);
}

@media (min-width: 58rem) {
  .pricing__card--featured {
    margin-top: calc(var(--space-m) * -1); /* gentle lift above its siblings */
  }
}

/* "MOST COMPLETE" badge — a small mono pill straddling the top edge. */
.pricing__badge {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: var(--space-m); /* aligns with the card's inner padding */
  transform: translateY(-50%);
  padding: var(--space-3xs) var(--space-2xs);
  background-color: var(--accent);
  color: var(--bg);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  border-radius: var(--radius);
  line-height: 1;
}

/* Package index — mono label, matching the eyebrow / fact-dt voice. */
.pricing__index {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.pricing__letter {
  color: var(--accent);
}

.pricing__name {
  margin-top: var(--space-s);
  font-size: var(--step-2); /* matches h3 */
  line-height: var(--leading-snug);
}

.pricing__price {
  margin-top: var(--space-2xs);
  font-size: var(--step-0);
  color: var(--text-muted);
}

.pricing__amount {
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: var(--weight-medium);
  color: var(--text);
  letter-spacing: var(--tracking-tight);
}

/* "ex. VAT" qualifier — quiet mono note next to the price. */
.pricing__vat {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* Feature list — each line prefixed with a mono "+" in accent, echoing the
   additive A → B → C build-up. Hairline top rule separates it from the price. */
.pricing__features {
  margin-top: var(--space-2xs);
  padding-top: var(--space-2xs);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
}

.pricing__features li {
  position: relative;
  padding-left: var(--space-m);
  font-size: var(--step-0);
  line-height: var(--leading-normal);
  color: var(--text);
}

.pricing__features li::before {
  content: "+";
  position: absolute;
  left: 0;
  font-family: var(--font-mono);
  color: var(--accent);
}

/* CTA — reuses the case live-link motif: mono accent label, arrow nudges on
   hover. margin-top:auto pins it to the card foot so the cards align. */
.pricing__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  align-self: flex-start;
  margin-top: auto;
  padding-top: var(--space-s);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
}

.pricing__cta span {
  transition: transform var(--dur-fast) var(--ease-out);
}

.pricing__cta:hover {
  color: var(--accent-hover);
}

.pricing__cta:hover span {
  transform: translateX(4px);
}

/* ==========================================================================
   About / process (05)
   Nielsen Studio's own section — global burnt orange accent (no case scope).
   Mirrors the Studio (intro) voice and spacing: editorial serif statement, mono
   labels, hairline rhythm. Per-element reveals throughout; the process list is
   the one stagger group. Uses the shared .section padding (a tall, scroll-read
   section, not an anchor-landing) — no Services-style header clearance.
   ========================================================================== */

/* Hairline top divider — separates About from the section above. Sits at the
   section's top edge, centred at the container's content width. Pure 1px
   var(--line) via a ::before, so it adds no layout (no box shift). */
.about.section {
  position: relative;
  /* Match the other studio sections' rhythm (Services/Contact) instead of the
     default --space-3xl on both sides, which left About floating with more air
     than its neighbours. Header-cleared top also lands #about cleanly. */
  padding-block: calc(var(--header-h) + var(--space-l)) var(--space-2xl);
}

.about.section::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  margin-inline: auto;
  width: calc(100% - 2 * var(--gutter));
  max-width: calc(var(--container) - 2 * var(--gutter));
  border-top: 1px solid var(--line);
  pointer-events: none;
}

/* Opening block — constrained measure, left-aligned like the rest of the site. */
.about__intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  max-width: 52ch;
}

/* Short editorial statement — Fraunces inherited from h2; kept on a tight measure. */
.about__statement {
  max-width: 18ch;
  line-height: var(--leading-tight);
}

.about__lead {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  max-width: 50ch;
}

.about__lead p {
  font-size: var(--step-1);
  line-height: var(--leading-normal);
  color: var(--text);
}

/* --- Process ----------------------------------------------------------- */
.about__process {
  margin-top: var(--space-2xl);
}

/* Its own small mono label, so the process reads as a distinct part of About. */
.about__process-label {
  margin-bottom: var(--space-l);
}

/* Vertical stack with calm air between steps (mobile-first). */
.about__steps {
  position: relative; /* anchor for the desktop column divider */
  display: flex;
  flex-direction: column;
  gap: var(--space-l);
  max-width: 56ch;
}

/* Desktop: two columns filling the full width (drops the 56ch cap, closing the
   void on the right). Column flow → 01/02 in the left column, 03/04 in the
   right. Generous column-gap; the calm row-gap (--space-l) is unchanged.
   align-items:start so 01 and 03 top-align. Reveal contract is untouched —
   the steps still cascade via [data-reveal-stagger], just reflowed. */
@media (min-width: 58rem) {
  .about__steps {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: repeat(2, auto);
    grid-auto-flow: column;
    column-gap: var(--space-2xl);
    row-gap: var(--space-l);
    align-items: start;
    max-width: none;
  }

  /* Thin vertical hairline running down the centre of the column-gap (the two
     1fr columns make the grid centre the gap centre). Absolute, so it adds no
     column and never shifts the layout. Desktop only — absent on the mobile
     stacked list. */
  .about__steps::before {
    content: "";
    position: absolute;
    inset-block: 0;
    left: 50%;
    width: 1px;
    background: var(--line);
    transform: translateX(-50%);
    pointer-events: none;
  }
}

/* Mono accent number (the .eyebrow__index / pricing__letter voice) at left,
   title + line at right — baseline-aligned so the number sits with the title. */
.about__step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-m);
  align-items: baseline;
}

.about__step-num {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  letter-spacing: var(--tracking-label);
  color: var(--accent);
}

.about__step-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
}

.about__step-title {
  font-size: var(--step-1); /* smaller than the h3 default — calm process items */
  line-height: var(--leading-snug);
}

.about__step-text {
  font-size: var(--step-0);
  line-height: var(--leading-normal);
  color: var(--text-muted);
  max-width: 44ch;
}

/* ==========================================================================
   Contact — enquiry form + details (06)
   Two columns on desktop (form left, details right), stacked on mobile at the
   same 58rem breakpoint as the pricing grid. Nielsen Studio's own section, so
   the accent is the global burnt orange. Hairline borders, no heavy shadows —
   the same quiet language as the rest of the site.
   ========================================================================== */

/* Same header-clearance + bottom rhythm as Services. */
.contact.section {
  padding-block: calc(var(--header-h) + var(--space-l)) var(--space-2xl);
}

/* Tighten the heading→form gap to match Services (the base .section-head's
   --space-xl was looser than the rest, and .contact__grid's own margin-top
   stacked on top of it — redundant). */
.contact .section-head {
  margin-bottom: var(--space-m);
}

/* The Contact invite reads too faint in the default muted lead colour — lift it
   to the off-white --text so the "get in touch" line is clearly legible. */
.contact .section-lead {
  color: var(--text);
}

.contact__grid {
  display: grid;
  grid-template-columns: 1fr; /* mobile-first: stacked */
  gap: var(--space-xl);
}

/* Form hidden until Resend is configured (see api/contact.js). Code stays intact;
   remove this class from the <form> to bring it back. Scoped specificity (0,2,0)
   so it beats .contact__form's display:flex without !important. */
.contact__form.is-hidden {
  display: none;
}

/* Direct-contact layout — used WHILE the form is hidden. One calm centred column
   built around the email CTA, so the section never looks empty without the form.
   Restore by removing --direct here and .is-hidden on the form. */
.contact__grid--direct {
  display: block; /* override the two-column grid */
  max-width: 44rem;
  margin-inline: auto;
  margin-top: var(--space-l);
  text-align: center;
}

.contact__grid--direct .contact__details {
  align-items: center;
  gap: var(--space-l);
}

.contact__grid--direct .contact__detail {
  align-items: center;
}

.contact__grid--direct .contact__email-row,
.contact__grid--direct .contact__links {
  justify-content: center;
}

/* The email is the primary action now — make it larger/more prominent. */
.contact__grid--direct .contact__email {
  font-size: var(--step-2);
}

/* Form wider than the details rail once there's room; stacked below 58rem. */
@media (min-width: 58rem) {
  .contact__grid {
    grid-template-columns: minmax(0, 38rem) 1fr;
    gap: var(--space-xl);
    align-items: start;
  }
}

/* --- Form --------------------------------------------------------------- */
.contact__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
}

/* Stack label OVER its field (vs the browser-default inline). */
.contact__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* Labels in the mono eyebrow voice. */
.contact__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Shared field styling — input / select / textarea. */
.contact__input,
.contact__select,
.contact__textarea {
  width: 100%;
  padding: var(--space-xs) var(--space-s);
  font-family: var(--font-body);
  font-size: var(--step-0);
  color: var(--text);
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

.contact__input::placeholder,
.contact__textarea::placeholder {
  color: var(--text-muted);
}

/* Keep a visible focus ring (a11y) while dropping the default outline: accent
   border + a faint accent wash. */
.contact__input:focus,
.contact__select:focus,
.contact__textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.contact__textarea {
  resize: vertical;
  min-height: 8rem;
  line-height: var(--leading-normal);
}

/* Select: native dropdown arrow kept; set colours explicitly so the control and
   its options never render dark-on-dark. */
.contact__select {
  cursor: pointer;
}

.contact__select option {
  color: var(--text);
  background-color: var(--surface);
}

/* --- Submit (the pricing__cta text-CTA motif, not a boxed button) ------- */
.contact__actions {
  margin-top: var(--space-xs);
}

.contact__submit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  align-self: flex-start;
  /* Light button affordance — border + padding so the only submit control reads
     as a button, but no filled orange box in idle (border + text only). */
  padding: var(--space-xs) var(--space-m);
  background: none;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
  transition: color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.contact__submit span {
  transition: transform var(--dur-fast) var(--ease-out);
}

.contact__submit:hover {
  background-color: var(--accent-soft);
  color: var(--accent-hover);
  border-color: var(--accent-hover);
}

.contact__submit:hover span {
  transform: translateX(4px);
}

/* In-flight: dim + inert while the request is sending. */
[data-contact-form][data-state="loading"] .contact__submit {
  opacity: 0.5;
  pointer-events: none;
}

.contact__status {
  margin-top: var(--space-2xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  color: var(--text-muted);
}

/* --- Details rail ------------------------------------------------------- */
.contact__details {
  display: flex;
  flex-direction: column;
  gap: var(--space-l);
  align-self: start; /* flush with the form's top */
}

.contact__detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.contact__detail-label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.contact__detail-value {
  font-size: var(--step-0);
  color: var(--text);
}

.contact__email-row {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  flex-wrap: wrap;
}

/* Big, copyable address — Fraunces, accent, lifts on hover. */
.contact__email {
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--accent);
  transition: color var(--dur-fast) var(--ease-out);
}

.contact__email:hover {
  color: var(--accent-hover);
}

/* Small, discreet mono "Copy" button — hairline, not a grey box. */
.contact__copy {
  padding: var(--space-3xs) var(--space-2xs);
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.contact__copy:hover,
.contact__copy.is-copied {
  color: var(--text);
  border-color: var(--line-strong);
}

.contact__links {
  display: flex;
  gap: var(--space-m);
}

.contact__link {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
  transition: color var(--dur-fast) var(--ease-out);
}

.contact__link:hover {
  color: var(--accent-hover);
}

/* ==========================================================================
   Site footer
   ========================================================================== */

.site-footer {
  border-top: 1px solid var(--line);
  padding-block: var(--space-xl);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
}

.site-footer__brand {
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--text);
}

.site-footer__meta {
  font-size: var(--step--1);
  color: var(--text-muted);
}
