/* ==========================================================================
   HOME — page-specific layout only. Shared components live in system.css.
   Source: Master Specification §4.1.
   ========================================================================== */
.arrival{ min-height:100svh; display:flex; flex-direction:column; justify-content:center; position:relative; overflow:hidden; }
/* Faint blueprint grid behind the epigraph — supports the type, never
   competes with it (Master Spec §1.4 opacity/contrast discipline).
   Hero → Visibility → Architectural grid. */
.arrival::before{
  content:""; position:absolute; inset:0; pointer-events:none; opacity:.05;
  background-image:linear-gradient(var(--ink) 1px, transparent 1px), linear-gradient(90deg, var(--ink) 1px, transparent 1px);
  background-size:64px 64px;
  animation:nx-grid-drift 70s linear infinite;
  transform:translate(var(--grid-px,0), var(--grid-py,0));
  transition:transform .8s var(--ease-instrument);
}
.arrival.no-grid::before{ display:none; }
@keyframes nx-grid-drift{ from{ background-position:0 0; } to{ background-position:64px 32px; } }
/* Typology-driven sketch watermark (Master Spec §4.1 — follows the most
   recently completed project's typology). Renders only when no Hero →
   Background image is set — the one remaining quiet fallback device,
   never a stand-in for the real background photography below. */
.arrival-sketch{ position:absolute; inset:0; margin:auto; width:min(60vw,760px); height:min(60vw,760px); opacity:.05; color:var(--ink); pointer-events:none; }
.arrival-sketch path{ fill:none; stroke:currentColor; stroke-width:.6; stroke-linecap:round; stroke-linejoin:round; }

/* ============================== BRAND HERO ==============================
   The current Hero presentation: a large centred logo mark, the studio
   identity line, and a category strip generated from the real typology
   taxonomy (template-parts/home/arrival.php). Dark, quiet, no photography
   or animated icon — the .arrival base rule / blueprint grid above are
   shared with the preserved legacy Hero, so the grid is switched off here
   rather than edited. ======================================================================== */
.arrival--brand::before{ display:none; }
/* padding-bottom is the dark section's own trailing breathing space before
   the light Trusted By section begins — kept here (not on Trusted By's own
   top) so the extra space stays part of THIS section's dark background. */
.arrival--brand{ min-height:0; display:flex; flex-direction:column; justify-content:flex-start; gap:0; padding-bottom:50px; }
/* The Hero frame — full-bleed, edge-to-edge, no letterboxing, no crop of
   its own: width is always 100% of the viewport, height is capped
   independently via --hero-h (desktop only; mobile restores natural
   aspect-ratio sizing below). It no longer shares the artwork's own
   1774:887 ratio, and — unlike an earlier pass — nothing here forces
   that ratio back via a cropped inner canvas either: .hero-canvas below
   is a plain full-fill passthrough, so the X and logo are positioned as
   percentages of THIS frame's own actual shape, whatever it currently
   is, never a zoomed/cropped stand-in for it. Only the background photo
   (.hero-bg, via object-fit:cover) crops — a photo has no other way to
   fill a box it wasn't shot at the exact ratio of, and that crop is
   invisible since it's a continuous plaster wall with no fixed feature
   near its own top/bottom edge. */
.arrival-hero-stage{
  position:relative;
  --hero-h:clamp(620px, 76vh, 820px);
  width:100%;
  height:var(--hero-h);
  margin:0;
  background:var(--paper); overflow:hidden;
}
.hero-canvas{ position:absolute; inset:0; }
.arrival-brand-logo--fallback{
  display:block; width:100%; padding-block:14vh; text-align:center;
  font-family:var(--f-mono); font-weight:600; letter-spacing:.14em;
  font-size:clamp(48px,9vw,116px); line-height:1; color:var(--brass);
}

/* ============================== HERO CONSTRUCTION ==============================
   .hero-bg is the supplied final composite photo (architectural wall,
   the X, and the logo lockup all baked in by the studio) — shown once
   and never touched again for the entire lifetime of the page: never
   cropped, translated, or blended with anything else. There is no CSS-
   built X, and no separate animated logo overlay, any more — both
   systems have been deleted, not just disabled/hidden. The baked-in
   logo visible in the photo itself is the only logo shown in the Hero.
   ============================== */
/* object-position is right-aligned, not centred: the supplied image
   (2076×757, a much wider ratio than this full-bleed frame typically
   renders at) needs roughly a quarter of its width cropped by
   object-fit:cover to fill the frame, and a CENTRED crop cuts the
   timber strip on the image's own right edge off completely (measured:
   the timber sits in only the last ~110px of the source — a centred
   crop excludes ~250px from each side, well past it). Right-aligning
   keeps the timber fully intact at the cost of cropping more off the
   left (mostly the foliage-shadow area). */
.hero-bg{ position:absolute; inset:0; width:100%; height:100%; display:block; object-fit:cover; object-position:100% center; }

/* ============================== MINIMAL REACTIVE LIGHT ==============================
   Still the same one real image underneath (see "HERO CONSTRUCTION" above)
   — no PNG layers, no cutouts, no "clean background" reconstruction;
   nothing is ever moved, cropped, or duplicated, so there are no seams/
   alpha edges to show. Everything below sits purely on top as a non-
   destructive overlay:
     - .hero-light: a soft, very-low-opacity gradient following the
       pointer (assets/js/hero-puzzle.js sets --lx/--ly).
     - .hero-sweep: a single short light sweep, played once on click.
     - .hero-logo-hotspot: an invisible hit target over the logo's own
       small X, with a very subtle hover brightness cue.
   Mobile and prefers-reduced-motion never run any of it, so those Heroes
   stay the plain, static image at all times.
   ====================================================================================== */
/* Off by default; JS fades it in only once the pointer is actually over
   the Hero. Two stacked radial gradients, both keyed to the same --lx/--ly
   point: a tighter white core (.13 peak, smaller radius — concentrated
   enough to read as movement, not a flat wash) plus a softer, broader
   brass halo (.035 peak) for warmth. Both fall off to fully transparent —
   no hard edge at any point, so it never reads as a spotlight. */
.hero-light{
  position:absolute; inset:0; pointer-events:none; opacity:0;
  background:
    radial-gradient(circle at var(--lx,50%) var(--ly,50%), rgba(255,255,255,.13) 0%, transparent 30%),
    radial-gradient(circle at var(--lx,50%) var(--ly,50%), rgba(168,132,94,.035) 0%, transparent 42%);
  transition:opacity .4s var(--ease-instrument);
}
.hero-light.is-visible{ opacity:1; }
/* One-shot click sweep — a single soft diagonal band, plays once; JS
   removes the class when it finishes so it can be re-triggered later but
   never loops on its own. */
.hero-sweep{ position:absolute; inset:0; overflow:hidden; pointer-events:none; }
.hero-sweep::before{
  content:""; position:absolute; top:-20%; bottom:-20%; left:-30%; width:16%;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.13) 45%, transparent 100%);
  opacity:0;
}
.hero-sweep.is-sweeping::before{ animation:hero-sweep-move .9s var(--ease-instrument) forwards; }
@keyframes hero-sweep-move{
  0%{ transform:translateX(0); opacity:0; }
  15%{ opacity:1; }
  85%{ opacity:1; }
  100%{ transform:translateX(360%); opacity:0; }
}
/* An invisible hit target near the logo's small X in the background
   photo — fully invisible at rest: no border, background, fill, or
   outline of any kind. Hover/click feedback is a very subtle
   brightness-style bloom only, never a visible shape. */
.hero-logo-hotspot{
  position:absolute; left:36.63%; top:48.07%; width:3.7%; height:7.9%; transform:translate(-50%,-50%);
  background:none; border:0; outline:0; box-shadow:none; padding:0; margin:0; cursor:pointer;
  appearance:none; -webkit-appearance:none;
}
.hero-logo-hotspot:focus{ outline:0; }
.hero-logo-hotspot::before{
  content:""; position:absolute; inset:-60%; border-radius:50%; pointer-events:none;
  background:radial-gradient(circle, rgba(255,255,255,.16) 0%, transparent 70%);
  opacity:0; transition:opacity .25s var(--ease-instrument);
}
.hero-logo-hotspot:hover::before, .hero-logo-hotspot:focus-visible::before{ opacity:1; }
.hero-logo-hotspot.is-pulsing::before{ animation:hero-logo-pulse .8s var(--ease-instrument); }
@keyframes hero-logo-pulse{ 0%{ opacity:0; } 35%{ opacity:.7; } 100%{ opacity:0; } }
/* Services Reel — a small secondary typographic element, never a UI
   component: no box, border, background, scrollbar, arrows or buttons.
   Position and font-size below are STATIC FALLBACK values only (a
   reasonable approximation for a common ~1440px-wide desktop) — the
   precise values are computed and applied at runtime by
   positionServicesReel() in assets/js/hero-puzzle.js, because the exact
   correct numbers depend on how much of .hero-bg's own 2076×757 canvas
   object-fit:cover has scaled/cropped at the CURRENT viewport, which a
   plain CSS percentage cannot express (verified: the correct left%
   differs by ~8 percentage points between 1440px and 1920px widths,
   since the crop amount changes with viewport height too, not just
   width). JS measures the real rendered .hero-bg box against its
   naturalWidth/naturalHeight and two fixed reference points measured
   directly on the artwork:
     - ARCHITECTS' own horizontal centre: x=790 of the image's native
       2076px width (0.3805), found by scanning the actual dark ink
       pixels of "— ARCHITECTS —" (including its flanking rule lines,
       which are symmetric, so the word-only centre and the full-line
       centre are the same point).
     - ARCHITECTS' own bottom edge: y=407 of the image's native 757px
       height (0.5376), same scanning method, used as the reference the
       requested ~22–30px gap is measured down from.
   Font-size is likewise computed from TIMELESS's own measured cap-height
   in the artwork (8.5px of 757, i.e. 0.0112) divided by Courier Prime's
   actual cap-height ratio (measured via canvas.measureText: 0.58 at
   normal weight) — not guessed — so the reel's rendered cap-height
   matches TIMELESS's own, not just an arbitrary font-size number. Item
   height/reel box scale with it, same proportions as before (2:1 height:
   font-size). JS (hero-puzzle.js) also drives the loop/opacity every
   frame as before; the flat .5 opacity below is the static fallback for
   prefers-reduced-motion (JS never runs there, so items just sit still,
   still legible, at the approximate fallback size/position). */
.hero-services-reel{
  position:absolute; left:var(--reel-left, 18.45%); top:var(--reel-top, 57.56%); transform:translateX(-50%);
  width:280px; height:var(--reel-box-h, 130px); overflow:hidden; pointer-events:none;
  mask-image:linear-gradient(to bottom, transparent 0%, black 35%, black 65%, transparent 100%);
  -webkit-mask-image:linear-gradient(to bottom, transparent 0%, black 35%, black 65%, transparent 100%);
}
.hero-services-track{ display:flex; flex-direction:column; align-items:center; will-change:transform; }
.hero-services-item{
  height:var(--reel-item-h, 26px); display:flex; align-items:center; justify-content:center;
  font-family:var(--f-mono); font-size:var(--reel-font-size, 13px); letter-spacing:.06em; text-transform:uppercase;
  color:var(--ink); white-space:nowrap; text-align:center; opacity:.5;
}
@media (max-width:639px){
  .hero-logo-hotspot{ display:none; }
  /* Hidden rather than a single-line replacement — cleanest option per
     the brief; the reel's own JS keeps running harmlessly on a hidden
     element, no markup/JS change needed. */
  .hero-services-reel{ display:none; }
  /* Mobile keeps the full, uncropped composition at its natural 1774:887
     aspect ratio (same as desktop) rather than a fixed-height crop: the
     architectural X and the logo are positioned as percentages measured
     against that exact aspect ratio (see "HERO CONSTRUCTION" above and
     .hero-logo-hotspot/.hero-services-reel below) — cropping to an
     arbitrary fixed height here would silently misalign all of them
     against whatever the crop actually shows. The construction intro
     never runs on mobile anyway (arrival.php's inline gate), so mobile
     always renders the finished, static composition immediately, simply
     scaled down to fit the viewport width. The desktop height cap above
     (--hero-h) is a "full-screen feels too tall" fix specific to wide
     screens — overridden back to natural aspect-ratio height here so
     mobile is completely unaffected by it (leaving --hero-h in place
     would make .hero-canvas's cover-crop trick kick in on mobile too,
     zooming/cropping the composition hard at narrow widths). */
  .arrival-hero-stage{ height:auto; aspect-ratio:1774/887; }

  /* Mobile-only Header: solid, hide-on-scroll-down / reveal-on-scroll-up —
     [data-front="1"] scopes this to the homepage only (this stylesheet
     itself only loads there). Other pages' Header is untouched. JS
     (assets/js/system.js, updateMobileNavVisibility()) toggles the
     .nx-nav-hidden class only on this element; the transform is the only
     thing that ever moves, so the transition below is what animates it. */
  .site-nav[data-front="1"]{
    position:fixed; top:0; left:0; right:auto; inset:auto; width:100%; height:54px;
    transform:translateY(0);
    transition:transform 300ms cubic-bezier(.22,.8,.32,1);
  }
  .site-nav[data-front="1"].nx-nav-hidden{ transform:translateY(-100%); }
  .site-nav[data-front="1"] .nav-bg{ opacity:1; background:var(--paper); }
  .site-nav[data-front="1"] .nav-bg-hero{ display:none; }
  .site-nav[data-front="1"] .nav-border{ background:var(--nav-border-color, var(--line)); opacity:var(--nav-border-opacity,1); }
  .site-nav[data-front="1"]{ --nav-text-color:var(--nav-color-scroll,#4A4842); }

  /* The Header is fixed again above, so it no longer reserves its own
     54px of flow — reserve the exact same amount here instead, so the
     Hero sits in precisely the same place whether the Header is shown or
     hidden (it never jumps) and is never covered at page top. */
  .arrival--brand{ padding-top:54px; }
}
/* Tablet / narrow-desktop only (640–1023px — the same band already used
   for the legacy Hero's own tablet query below). Above 1023px the plain
   desktop rules above apply unchanged; at 639px and under, the mobile
   block above already takes over first. Neither is touched here.
   At these widths the fixed --hero-h clamp (620–820px) forces a much
   taller-than-wide box than it was tuned for, and object-fit:cover then
   crops far more off the LEFT of the 2076×757 source than at desktop —
   enough to push the NEMA X logo and TIMELESS text out of frame while
   the timber panel (the source's rightmost ~110px, kept fully intact by
   the desktop object-position:100%) stays fully visible and dominates.
   A shorter box plus a re-tuned object-position measured against the
   same source (ARCHITECTS' own centre sits at x=790 of 2076 = 38.05% —
   see hero-puzzle.js's ARCHITECTS_X_FRAC) brings the logo/X/TIMELESS
   trio back into frame with the timber reduced to a restrained edge
   rather than removed outright. */
@media (min-width:640px) and (max-width:1023px){
  .arrival-hero-stage{ height:clamp(320px, 46vw, 420px); }
  .hero-bg{ object-position:75% center; }
  /* Both are calibrated (fixed %/coverScale() math in hero-puzzle.js)
     specifically for the desktop object-position:100% crop — at this
     crop they'd sit over blank wall (hotspot) or drift to the wrong
     spot (reel), same reasoning mobile already hides them for. */
  .hero-logo-hotspot{ display:none; }
  .hero-services-reel{ display:none; }
}
@media (prefers-reduced-motion:reduce){ .hero-light, .hero-sweep, .hero-logo-hotspot{ display:none; } }
/* Dark category strip — starts immediately below the image (no gap); the
   label and the horizontal rail together read as one continuous band.
   Section 22 (Explore Our Project Categories) redesign: a horizontal
   architectural-index carousel, not a grid. The heading stays centred and
   untouched (Master Spec typography); only the rail below it is
   asymmetric — it begins flush with the site's own .container edge, not
   centred, so the next card always peeks at the right and implies more
   content, and it never fights normal page scroll (see assets/js/
   category-rail.js: vertical wheel only redirects to horizontal while the
   rail hasn't reached either end). */
.arrival-categories{ margin-top:0; padding-top:12px; text-align:center; }
.arrival-categories-label{ font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--on-dark-soft); }

.arrival-cat-rail{ position:relative; margin-top:16px; }
.arrival-cat-nav{
  display:flex; flex-wrap:nowrap; justify-content:flex-start; align-items:flex-start;
  gap:13px; width:100%; overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x proximity; cursor:grab; user-select:none;
  padding-block:4px 8px;
  /* Flush with .container's own content edge (system.css: max-width:var(--max);
     margin-inline:auto; padding-inline:var(--edge)) on both sides, computed
     the same way rather than wrapped in .container itself — a real
     .container would also cap the right edge, which is exactly the "peek"
     this rail depends on. */
  padding-inline-start:max(var(--edge), calc((100% - var(--max)) / 2 + var(--edge)));
  padding-inline-end:max(var(--edge), calc((100% - var(--max)) / 2 + var(--edge)));
  scroll-padding-inline-start:max(var(--edge), calc((100% - var(--max)) / 2 + var(--edge)));
  scrollbar-width:none; -ms-overflow-style:none;
}
.arrival-cat-nav::-webkit-scrollbar{ display:none; height:0; }
.arrival-cat-nav.is-dragging{ cursor:grabbing; scroll-snap-type:none; }
.arrival-cat-seg{
  position:relative; flex:0 0 auto; display:flex; align-items:flex-start; justify-content:flex-start;
  width:200px; height:280px; padding:16px; overflow:hidden; text-decoration:none;
  scroll-snap-align:start;
  border:1px solid color-mix(in srgb, var(--on-dark) 22%, transparent);
  transition:border-color var(--i3) var(--ease-instrument), transform 550ms var(--ease-contact);
}
/* The 3px hover lift is a mouse-only response — (hover:hover) and
   (pointer:fine) keeps it off touch devices entirely, where :hover can
   otherwise stick after a tap instead of a real pointer resting there. */
@media (hover:hover) and (pointer:fine){
  .arrival-cat-seg:hover{ transform:translateY(-3px); }
}
.arrival-cat-seg-bg{
  position:absolute; inset:0; background-size:cover; background-position:center;
  opacity:1; transform:scale(1);
  transition:transform 650ms var(--ease-instrument);
}
/* The crossfade "incoming" layer (assets/js/category-rail.js) — sits
   directly on top of .arrival-cat-seg-bg, transparent until a rotation
   picks a new category-valid image; once its own fade-in finishes, the JS
   copies its image onto .arrival-cat-seg-bg and resets this back to
   transparent, ready for the next cycle. Never present on the All
   Projects card (no rotation there). */
.arrival-cat-seg-bg-next{
  position:absolute; inset:0; background-size:cover; background-position:center;
  opacity:0; pointer-events:none;
  transition:opacity 850ms var(--ease-instrument);
}
.arrival-cat-seg-bg-next.is-active{ opacity:1; }
.arrival-cat-seg-bg--icon{ background-size:contain; background-repeat:no-repeat; opacity:.4; transition:opacity 650ms var(--ease-instrument); }
/* Readability scrim — localized to the top ~36% of the card (where the
   title/rule actually sit, .arrival-cat-seg's own align-items:flex-start),
   not a flat wash over the whole photograph: darkest at the top edge,
   still strong through the title band, then fading out well before the
   card's midpoint so the photograph itself is untouched everywhere the
   type isn't. Kept stable on hover (no opacity swing) so readability never
   dips right when a visitor is engaging with the card — see the All
   Projects override below for the one card this doesn't apply to. */
.arrival-cat-seg-overlay{
  position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to bottom,
    color-mix(in srgb, var(--ink) 80%, transparent) 0%,
    color-mix(in srgb, var(--ink) 55%, transparent) 16%,
    color-mix(in srgb, var(--ink) 18%, transparent) 28%,
    transparent 36%
  );
}
/* All Projects has no photograph to protect text against — its own dark
   card + faint NX mark (.arrival-cat-seg-bg--icon) is the existing
   approved look, so it keeps the original flat wash (and its own hover
   fade) instead of the gradient above. */
.arrival-cat-seg--all .arrival-cat-seg-overlay{ background:var(--ink); opacity:.58; transition:opacity 650ms var(--ease-instrument); }
/* Bottom-left index — positioned absolutely so it reads at the foot of
   the card regardless of where it sits in the (unchanged) markup below;
   the category name (.arrival-cat-seg-content, a flex child of the card
   above) now takes the top-left instead (.arrival-cat-seg's own
   align-items:flex-start). Kept secondary to the category name: small,
   quiet, wide-set mono type. */
.arrival-cat-seg-index{
  display:block; position:absolute; bottom:16px; left:16px; z-index:1;
  font-family:var(--f-mono); font-size:11px; letter-spacing:.14em;
  color:var(--on-dark); opacity:.6;
}
.arrival-cat-seg-content{ position:relative; display:flex; align-items:flex-end; }
.arrival-cat-seg-label{
  font-family:var(--f-mono); font-size:12px; line-height:1.35; letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:#fff; text-align:left; opacity:.95;
  transition:opacity 320ms var(--ease-instrument);
}
/* The "very small architectural line/detail near the title" — a plain
   1px rule, not an icon/asset; it's what extends 8-12px on hover below. */
.arrival-cat-seg-label::after{
  content:""; display:block; width:18px; height:1px; margin-top:8px;
  background:currentColor; transition:width var(--i3) var(--ease-instrument);
}
.arrival-cat-seg:hover{ border-color:var(--brass); }
.arrival-cat-seg--all:hover .arrival-cat-seg-overlay{ opacity:.22; }
.arrival-cat-seg:hover .arrival-cat-seg-bg{ transform:scale(1.02); }
/* All Projects' NEMA X mark stays still on hover — a subtle brightness
   lift instead of the photo cards' scale, per the brief. Same specificity
   as the shared -bg hover rule above; source order after it wins for the
   elements that carry both classes. */
.arrival-cat-seg:hover .arrival-cat-seg-bg--icon{ transform:none; opacity:.55; }
.arrival-cat-seg:hover .arrival-cat-seg-label{ opacity:1; }
.arrival-cat-seg:hover .arrival-cat-seg-label::after{ width:29px; }
.arrival-cat-seg:focus-visible{ outline:2px solid var(--brass); outline-offset:2px; border-color:var(--brass); }

/* Minimal position indicator — a thin track, never pagination dots. */
.arrival-cat-progress{
  display:flex; align-items:center; gap:12px;
  margin-top:8px; padding-inline:max(var(--edge), calc((100% - var(--max)) / 2 + var(--edge)));
}
.arrival-cat-progress-track{ position:relative; flex:0 0 200px; height:1px; background:color-mix(in srgb, var(--on-dark) 20%, transparent); }
.arrival-cat-progress-fill{ position:absolute; inset-block:0; left:0; width:0%; background:var(--brass); transition:width var(--i2) var(--ease-instrument); }
.arrival-cat-progress-count{ font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide); color:var(--on-dark-soft); white-space:nowrap; }
@media (prefers-reduced-motion:no-preference){
  .arrival--brand > *{ opacity:0; transform:translateY(10px); transition:opacity var(--i5) var(--ease-instrument), transform var(--i5) var(--ease-instrument); }
  .arrival--brand > *:nth-child(2){ transition-delay:var(--i2); }
  .arrival--brand > *:nth-child(3){ transition-delay:var(--i3); }
  .arrival--brand > *:nth-child(4){ transition-delay:var(--i3); }
  .arrival--brand.is-visible > *{ opacity:1; transform:none; }
}
@media (max-width:639px){
  /* A dedicated mobile rail, not the desktop dimensions shrunk down: wider/
     shorter cards (landscape-leaning) sized for a thumb swipe, no arrows,
     no wheel JS (assets/js/category-rail.js gates all of that to >=640px),
     no progress bar — kept compact so the section reads quickly before
     Selected Clients below. Native touch-scroll + scroll-snap only. */
  .arrival-categories{ padding-top:8px; }
  .arrival-cat-rail{ margin-top:14px; }
  .arrival-cat-nav{
    padding-block:4px 12px;
    padding-inline-start:16px; padding-inline-end:16px;
    gap:10px; cursor:auto; scroll-padding-inline-start:16px;
  }
  .arrival-cat-seg{ width:70vw; max-width:280px; height:200px; padding:14px; }
  .arrival-cat-progress{ display:none; }
}
@media (min-width:1200px){
  /* All 8 cards fit one fixed row at this width — no more overflow-x, no
     "peek", no wheel/drag (assets/js/category-rail.js gates that JS to
     below this breakpoint), no scroll position to indicate. Below 1200px
     (tablet) the base rail rules above still apply unchanged. */
  .arrival-cat-nav{
    display:grid; grid-template-columns:repeat(8, 1fr); gap:9px;
    overflow:visible; scroll-snap-type:none; cursor:auto;
    padding-block:4px 4px;
    max-width:var(--max); margin-inline:auto; padding-inline:var(--edge);
  }
  .arrival-cat-seg{ width:100%; }
  .arrival-cat-progress{ display:none; }
}

/* ============================== HERO BACKGROUND ==============================
   Four independently configurable layers (Hero → Background), painted in
   this order — image, then a flat colour wash at its own opacity, then an
   optional gradient, then a legibility veil protecting the headline band —
   never one hardcoded "opacity:.05" rule doing all four jobs at once.
   ======================================================================== */
.arrival-bg-image{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:var(--bg-focal-x,50%) var(--bg-focal-y,50%); pointer-events:none; filter:var(--bg-img-filter, none); }
/* NEMA X → Header → Background Image Control — an additional flat tint,
   independent of the Hero's own colour-wash/gradient layers above (those
   are Hero → Background; this is the Header's own legibility lever). */
.arrival-bg-overlay{ position:absolute; inset:0; pointer-events:none; background-color:var(--bg-overlay-color,var(--ink)); opacity:var(--bg-overlay-opacity,0); }
.arrival-bg-color{ position:absolute; inset:0; pointer-events:none; }
.arrival-bg-gradient{ position:absolute; inset:0; pointer-events:none; }
.arrival-bg-gradient--to-bottom{ background:linear-gradient(to bottom, transparent 0%, var(--ink) 100%); }
.arrival-bg-gradient--to-top{ background:linear-gradient(to top, transparent 0%, var(--ink) 100%); }
.arrival-bg-gradient--to-right{ background:linear-gradient(to right, transparent 0%, var(--ink) 100%); }
.arrival-bg-gradient--to-left{ background:linear-gradient(to left, transparent 0%, var(--ink) 100%); }
.arrival-bg-gradient--radial{ background:radial-gradient(circle at center, transparent 0%, var(--ink) 100%); }
/* Same vertical-band technique as .nx-image-scrim below, scaled to the
   full section — protects the headline from a genuinely visible
   background photograph, not just from the icon's own imagery. */
.arrival-legibility-scrim{
  position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to bottom, transparent 0%, transparent 32%, var(--paper) 48%, var(--paper) 58%, transparent 74%, transparent 100%);
  opacity:.55;
}

/* ============================== NX INSTALLATION ==============================
   The icon (Hero → Branding → Header Compact Icon, the official uploaded
   asset — never a hand-drawn interpretation) is a stable, non-rotating
   architectural frame; it never rotates, bounces, or repeatedly scales.
   Three qualities overlap on independent, un-round cycles rather than
   stepping through a sequential state machine — that overlap is what
   keeps the visitor from ever perceiving a start or end:
     - a solid ink-filled silhouette, breathing in and out
     - an "outline" ring (the same silhouette, plus a second, slightly
       inset duplicate painted in the background colour on top — the
       closest achievable stroke-only read from a filled icon asset)
     - real project photography (NEMA X → Hero Media), when configured,
       cross-fading through the same mask on its own unrelated rhythm
   A <mask> — not clipPath, which clips by fill geometry and shows
   nothing on a source with a transparent background — confines every
   layer to the icon's own silhouette via --nx-icon-url, set once on
   .nx-monogram and inherited by every layer below it.
   ======================================================================== */

/* ---- Drawing layer (axis lines, dimension marks) — furthest layer,
   largest footprint, smallest parallax, faintest presence.
   Hero → Visibility → Drawing overlays. ---- */
.nx-drawing{
  position:absolute; inset:0; margin:auto; width:min(64vw,860px); height:min(64vw,860px);
  pointer-events:none; color:var(--ink-soft);
  opacity:var(--nx-scroll-fade,1);
  transform:translate(var(--drawing-px,0), var(--drawing-py,0));
}
.nx-drawing-svg{ width:100%; height:100%; overflow:visible; }
.nx-axis{
  stroke:currentColor; stroke-width:.5; fill:none;
  stroke-dasharray:200; stroke-dashoffset:200;
  animation:nx-axis-draw 97s linear infinite, nx-axis-fade 61s ease-in-out infinite;
}
.nx-axis--v{ animation-delay:-31s, -9s; }
@keyframes nx-axis-draw{ 0%,100%{ stroke-dashoffset:200; } 50%{ stroke-dashoffset:0; } }
@keyframes nx-axis-fade{ 0%,100%{ opacity:0; } 50%{ opacity:.045; } }

.nx-dim{ stroke:currentColor; stroke-width:.5; fill:none; opacity:0; animation:nx-dim-fade 71s ease-in-out infinite; }
.nx-dim--b{ animation-delay:-37s; }
@keyframes nx-dim-fade{ 0%,100%{ opacity:0; } 50%{ opacity:.1; } }

/* ---- NX icon frame — fixed geometry, never rotated or scaled ---- */
.nx-monogram{
  position:absolute; inset:0; margin:auto; width:min(42vw,520px); height:min(42vw,520px);
  pointer-events:none;
  opacity:calc(var(--nx-scroll-fade,1) * var(--nx-mouse-opacity,1) * var(--nx-opacity-ceiling,1));
  transform:
    translate(var(--nx-px,0), calc(var(--nx-py,0) + var(--nx-scroll-y,0px)))
    perspective(1400px)
    rotateX(var(--nx-rx,0deg))
    rotateY(var(--nx-ry,0deg))
    scale(calc(var(--nx-scale,1) * var(--nx-scroll-scale,1)));
  will-change:transform, opacity;
}
.nx-monogram-inner{ position:relative; width:100%; height:100%; }

.nx-plate-border{ position:absolute; inset:9px; border:1px solid var(--bronze-deep); opacity:0; animation:nx-plate-breathe 53s ease-in-out infinite; }
@keyframes nx-plate-breathe{ 0%,100%{ opacity:0; } 50%{ opacity:.5; } }

/* Solid + outline share the same mask source (the real icon file) and the
   same sizing — every rule below is deliberately generic across both. */
.nx-icon-solid, .nx-icon-outline-base, .nx-icon-outline-erase{
  position:absolute; inset:0;
  -webkit-mask-image:var(--nx-icon-url); mask-image:var(--nx-icon-url);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
  -webkit-mask-size:contain; mask-size:contain;
}
.nx-icon-solid{
  background:var(--bronze-deep); opacity:.06;
  animation-name:nx-icon-solid-breathe; animation-timing-function:ease-in-out; animation-iteration-count:infinite;
}
@keyframes nx-icon-solid-breathe{ 0%,100%{ opacity:.06; } 50%{ opacity:calc(.06 + .44 * var(--nx-icon-intensity,.6)); } }

.nx-icon-outline{
  position:absolute; inset:0; opacity:.05;
  animation-name:nx-icon-outline-breathe; animation-timing-function:ease-in-out; animation-iteration-count:infinite; animation-delay:-19s;
}
@keyframes nx-icon-outline-breathe{ 0%,100%{ opacity:.05; } 50%{ opacity:calc(.05 + .55 * var(--nx-icon-intensity,.6)); } }
.nx-icon-outline-base{ background:var(--bronze-deep); }
.nx-icon-outline-erase{ background:var(--paper); transform:scale(.93); }

/* ---- Image layer — real project photography masked to the icon. Both
   -webkit- and unprefixed mask forms are set for Safari. ---- */
.nx-image-layer{
  position:absolute; inset:0; width:100%; height:100%; overflow:hidden; z-index:1;
  -webkit-mask-image:var(--nx-icon-url); mask-image:var(--nx-icon-url);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
  -webkit-mask-size:contain; mask-size:contain;
  opacity:var(--nx-scroll-fade,1);
  transform:translate(var(--image-px,0), var(--image-py,0));
}

.nx-hero-image{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  filter:sepia(.15) saturate(.55) brightness(1.05) contrast(.92);
  opacity:0;
  animation-name:nx-hero-crossfade; animation-timing-function:ease-in-out; animation-iteration-count:infinite;
  /* animation-duration and animation-delay are set inline per image
     (template-parts/home/arrival.php) — both are a function of how many
     images are actually configured and each one's position in the
     sequence, so they can't be fixed values here. */
}
/* Restrained grain — one shared static texture over the whole sequence,
   not per-image, since its job is paper-quality, not per-photo detail. */
.nx-image-grain{
  position:absolute; inset:0; width:100%; height:100%; opacity:.04; mix-blend-mode:overlay; pointer-events:none;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
/* Legibility veil — same mask as the images above (a child of the same
   masked layer), so it stays confined to the glyph too. Suppresses the
   photography specifically in the vertical band .arrival-body occupies,
   regardless of exact alignment between the centered glyph and the
   centered text block — never moves either element to avoid it. */
.nx-image-scrim{
  position:absolute; inset:0; width:100%; height:100%;
  background:linear-gradient(to bottom, transparent 0%, transparent 36%, var(--paper) 50%, transparent 64%, transparent 100%);
  opacity:.94;
}

/* Set by hero.js — mostly out of the viewport, or the tab is hidden. */
.arrival.hero-media-paused .nx-hero-image{ animation-play-state:paused; }

@media (min-width:640px) and (max-width:1023px){
  /* Tablet — the drawing layer's fine axis/dimension marks read as noise
     at this size range rather than as an architectural trace; everything
     else (image crossfade, NX line-work) stays. */
  .nx-drawing{ display:none; }
}

@media (max-width:639px){
  /* Mobile — a genuinely lighter animated treatment, not a frozen desktop
     effect: the crossfade keeps running, but focal drift, the outline
     ring, the drawing layer, border-breathing and mouse parallax (which
     touch input never drives anyway) are all dropped for real
     battery/CPU savings — solid icon + photography stay alive. */
  .nx-drawing{ display:none; }
  .nx-plate-border{ animation:none; opacity:.3; }
  .nx-icon-outline{ display:none; }
  /* nx-hero-crossfade-mobile is generated inline alongside nx-hero-crossfade
     (template-parts/home/arrival.php) — an opacity-only variant sharing
     the exact same computed timing, just without the scale-drift step. */
  .nx-hero-image{ animation-name:nx-hero-crossfade-mobile; }
}

@media (prefers-reduced-motion:reduce){
  /* The universal reduced-motion rule (system.css) forces every animation
     to complete one iteration instantly and stop on its final keyframe —
     left alone, every layer above would land on an empty/undrawn state.
     Hold a deliberate static composition instead: the engraved border and
     drawing layer at a fixed quiet opacity, the icon solid (not
     breathing) at a calm mid-opacity, and — when photography is
     configured — exactly the first image, fully still, as the one
     art-directed frame (Master brief: "one static composition," not an
     empty installation). */
  .nx-plate-border{ animation:none !important; opacity:.35 !important; }
  .nx-axis{ animation:none !important; stroke-dashoffset:0 !important; opacity:.045 !important; }
  .nx-dim{ animation:none !important; opacity:.1 !important; }
  .nx-icon-solid{ animation:none !important; opacity:.32 !important; }
  .nx-icon-outline{ animation:none !important; opacity:0 !important; }
  .nx-image-layer{ transform:none !important; }
  .nx-hero-image{ animation:none !important; opacity:0 !important; }
  .nx-hero-image:first-of-type{ opacity:1 !important; }
  .nx-image-grain{ opacity:.03; }
}
/* Hero → Animation → Enable, off — the identical static composition
   reduced-motion already needs above, just reached via a class instead of
   the media query, so there's one authored "calm" state, not two. */
.arrival.animation-off .nx-plate-border{ animation:none; opacity:.35; }
.arrival.animation-off .nx-axis{ animation:none; stroke-dashoffset:0; opacity:.045; }
.arrival.animation-off .nx-dim{ animation:none; opacity:.1; }
.arrival.animation-off .nx-icon-solid{ animation:none; opacity:.32; }
.arrival.animation-off .nx-icon-outline{ animation:none; opacity:0; }
.arrival.animation-off .nx-image-layer{ transform:none; }
.arrival.animation-off .nx-hero-image{ animation:none; opacity:0; }
.arrival.animation-off .nx-hero-image:first-of-type{ opacity:1; }

/* ---- Hero → Interaction: alternative modes ----
   Positioned in DOM right after the background photography and before the
   NX installation/text (system.css convention for this section: no
   z-index, pure DOM order), so both overlays sit above the Hero photo but
   under the NX mark and every text layer. Only one is ever rendered by PHP
   — whichever style is currently selected — so an idle mode adds nothing
   to the DOM at all. Set by hero.js via --fx/--fy (cursor position) and a
   toggled .is-active class; the mask-image default (50% 50%, opacity 0)
   is the resting/no-JS state. */
.hero-focus-zone,
.hero-tech-reveal{
  position:absolute; inset:0; width:100%; height:100%; pointer-events:none; opacity:0;
  transition:opacity .6s var(--ease-instrument);
}
.hero-focus-zone.is-active,
.hero-tech-reveal.is-active{ opacity:1; }

/* Architectural Focus — a real enhanced duplicate of the Hero photograph
   (not backdrop-filter: masking + backdrop-filter together is an
   unreliable combination across browser compositing paths, see the
   inc/... investigation this replaces) sits above the base photo, masked
   to a soft circle around the cursor so only that zone reads visibly
   sharper/brighter — never a hard-edged spotlight (the mask fade is wide,
   35%→78%). Rendered only when a Background image is set (arrival.php);
   reuses that same attachment, no separate upload. */
.hero-focus-zone{
  object-fit:cover;
  object-position:var(--bg-focal-x,50%) var(--bg-focal-y,50%);
  filter:contrast(1.35) brightness(1.15) saturate(1.08);
  mask-image:radial-gradient(var(--focus-radius,380px) circle at var(--fx,50%) var(--fy,50%), #000 0%, #000 35%, transparent 78%);
  -webkit-mask-image:radial-gradient(var(--focus-radius,380px) circle at var(--fx,50%) var(--fy,50%), #000 0%, #000 35%, transparent 78%);
}

/* Technical Reveal — a set of datum lines and registration ticks (same
   graphite/terracotta language as the Practice drawing layer) surfaces
   locally around the cursor, never covering the whole Hero. Stroke weight
   and opacity are deliberately well above "quiet" — thin/faint hairlines
   read as literally invisible against real photography at normal viewing
   distance; this is the production value that was hiding the effect. */
.hero-tech-reveal{
  mask-image:radial-gradient(var(--reveal-radius,220px) circle at var(--fx,50%) var(--fy,50%), #000 0%, #000 40%, transparent 82%);
  -webkit-mask-image:radial-gradient(var(--reveal-radius,220px) circle at var(--fx,50%) var(--fy,50%), #000 0%, #000 40%, transparent 82%);
}
.hero-tech-reveal-svg{ width:100%; height:100%; }
.htr-line{ stroke:var(--ink-soft); stroke-width:1.3; fill:none; opacity:.85; vector-effect:non-scaling-stroke; }
.htr-line--datum{ stroke-dasharray:2 5; opacity:.62; }
.htr-tick{ fill:none; stroke:var(--stamp); stroke-width:1.8; vector-effect:non-scaling-stroke; }

@media (max-width:639px){
  /* Cursor-based reveal never fires on touch anyway (hero.js also gates
     this), but keep the mobile treatment authored in one place same as
     every other Hero layer above. */
  .hero-focus-zone,
  .hero-tech-reveal{ display:none; }
}
@media (prefers-reduced-motion:reduce){
  .hero-focus-zone,
  .hero-tech-reveal{ display:none !important; }
}

.arrival-mark{ font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--ink-soft); position:absolute; top:100px; left:var(--edge); }
.arrival-mark .estd{ color:var(--brass); display:block; margin-top:4px; }
.arrival-body{ max-width:600px; padding-inline:var(--edge); }
h1.epigraph{ font-family:var(--f-display); font-weight:380; font-style:italic; font-optical-sizing:auto; font-size:clamp(24px,3vw,36px); line-height:var(--lh-read); letter-spacing:var(--ls-read); color:var(--ink); text-wrap:balance; font-variant-numeric:oldstyle-figures; }
h1.epigraph .line-mark{ display:inline-block; width:34px; height:1px; background:var(--stamp); vertical-align:middle; margin:0 4px; }
.arrival-foot{ position:absolute; bottom:64px; left:var(--edge); right:var(--edge); display:flex; justify-content:space-between; align-items:flex-end; font-family:var(--f-mono); font-size:11px; color:var(--ink-soft); letter-spacing:var(--ls-tight); }
.arrival-foot b{ color:var(--ink); font-weight:500; }
.begin-cue{ display:flex; align-items:center; gap:10px; }
.begin-cue .stem{ width:1px; height:0; background:var(--stamp); transition:height var(--i6) var(--ease-instrument) var(--i2); }
.arrival.is-visible .begin-cue .stem{ height:40px; }

/* ---- Hero → Text colour: Light — over a photographic Background image
   the default dark ink disappears into the photography. The only change
   here is colour + a restrained per-glyph shadow for local contrast (no
   overlay panel, no gradient div, no wash across the image itself). ---- */
.arrival--text-light .arrival-mark,
.arrival--text-light .arrival-foot{
  color:var(--ivory-warm);
  text-shadow:0 1px 3px rgba(0,0,0,.35);
}
.arrival--text-light .arrival-mark .estd{ color:var(--stamp-on-dark); }
.arrival--text-light .arrival-foot b{ color:var(--ivory-warm); }
.arrival--text-light h1.epigraph{
  color:var(--ivory-warm);
  text-shadow:0 1px 4px rgba(0,0,0,.35);
}
.arrival--text-light h1.epigraph .line-mark{ background:var(--stamp-on-dark); }
.arrival--text-light .begin-cue .stem{ background:var(--stamp-on-dark); }

/* ============================== TRUST ==============================
   An architectural client field, not a logo wall. Every client — Featured
   or not — lives in one continuous two-row field (assets/js/trust.js);
   Featured only earns placement priority and a touch more presence
   (.trust-client--priority), never a separate static tier. JS drives the
   field's transforms every frame (row translateX for drift; per-card
   translate+scale for gravity/focus) — the rules below only ever supply
   resting values, the colour system, and the fully static fallback for
   Animation off / prefers-reduced-motion / no JS at all (the same static
   rules are authored once and reached two ways, matching the
   .animation-off / reduced-motion pairing already used for the Hero). ====
   ==================================================================== */
.trust{ padding:calc(var(--pad-lead) * .72) 0; border-bottom:1px solid var(--line); }
.trust-head{ text-align:center; max-width:520px; margin-inline:auto; }
.trust-title{ font-family:var(--f-display); font-weight:400; font-style:italic; font-size:clamp(24px,3vw,32px); margin-top:14px; letter-spacing:var(--ls-read); }
.trust-note{ color:var(--ink-soft); font-size:14px; line-height:var(--lh-body); margin-top:14px; font-variant-numeric:oldstyle-figures; }

/* ---- Field — two counter-moving rows, edge-masked so the field reads
   larger than the visible viewport. ---- */
.trust-field{
  position:relative; margin-top:clamp(40px,6vw,64px);
  display:flex; flex-direction:column; gap:clamp(32px,3.5vw,40px);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 4%,#000 96%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 4%,#000 96%,transparent);
}
.trust-row{ overflow:hidden; }
.trust-row-track{ display:flex; width:max-content; will-change:transform; }
.trust-row-set{ display:flex; align-items:center; gap:clamp(40px,4.5vw,72px); padding-right:clamp(40px,4.5vw,72px); flex-shrink:0; }

/* ---- Client card — the logo is the primary object, name secondary ---- */
.trust-client{ position:relative; display:flex; flex-direction:column; align-items:center; text-align:center; gap:8px; will-change:transform; transition:opacity var(--i3) var(--ease-instrument); }
a.trust-client{ cursor:pointer; }

/* Logo frame — an invisible alignment system, not a fixed box: every logo
   is centered and object-fit:contain'd inside a shared-height (never
   distorted, never cropped), but its own max-width/max-height come from
   its shape class (nemax_client_logo_shape(), inc/clients.php) so a thin
   wide wordmark and a dense square mark can read as roughly equal VISUAL
   weight without forcing identical pixel dimensions. Height stays fixed
   across all three shapes so every card's name label still starts at the
   same Y position regardless of which shape its logo resolved to. */
.trust-client-logo{ --logo-max-w:132px; --logo-max-h:40px; --logo-scale:1; height:50px; display:flex; align-items:center; justify-content:center; }
.trust-client-logo img{ width:auto; height:auto; max-width:calc(var(--logo-max-w) * var(--logo-scale)); max-height:calc(var(--logo-max-h) * var(--logo-scale)); object-fit:contain; filter:grayscale(1) opacity(.78); transition:filter var(--i2) var(--ease-instrument); }
.trust-client-logo--compact{ --logo-max-w:108px; --logo-max-h:44px; }
.trust-client-logo--standard{ --logo-max-w:132px; --logo-max-h:40px; }
.trust-client-logo--wide{ --logo-max-w:155px; --logo-max-h:36px; }
/* --logo-scale (not a rewrite of --logo-max-w/h itself) — reading and
   multiplying a custom property's own cascaded value on the very same
   element is a self-reference cycle per the CSS spec (this element is
   where .trust-client-logo, .trust-client-logo--standard and this rule
   all resolve, not a parent/child inheritance case), which silently
   invalidates the property rather than compounding it. A second
   variable, multiplied in at the point of use, avoids that entirely.
   The combined optical-compensation × priority scale is now computed
   once in PHP (nemax_client_render_metrics(), inc/clients.php) and
   emitted inline per card — the class default above is only the resting
   value for a card with no logo at all (the wordmark fallback never gets
   an inline override). */

.trust-client-wordmark{ font-family:var(--f-display); font-weight:500; font-size:15px; letter-spacing:var(--ls-head); color:var(--ink-soft); opacity:.72; white-space:nowrap; transition:opacity var(--i2) var(--ease-instrument), color var(--i2) var(--ease-instrument); }

.trust-client-name{ font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-tight); text-transform:uppercase; color:var(--ink-soft); opacity:.75; white-space:nowrap; transition:opacity var(--i2) var(--ease-instrument), color var(--i2) var(--ease-instrument); }

/* Active/focused client — stronger presence, never brand colour (grayscale
   stays at 1; only opacity increases) */
.trust-client.is-active .trust-client-logo img{ filter:grayscale(1) opacity(.97); }
.trust-client.is-active .trust-client-wordmark,
.trust-client.is-active .trust-client-name{ opacity:1; color:var(--ink); }

/* Editorial focus — everything else recedes while one client is active */
.trust-field.has-active .trust-client:not(.is-active){ opacity:.6; }

/* Original brand colour mode (NEMA X → Trusted By → Logo visual mode) */
.trust-field--original .trust-client-logo img{ filter:none; opacity:.9; }
.trust-field--original .trust-client.is-active .trust-client-logo img{ opacity:1; }

/* Client names off (NEMA X → Trusted By → Client names) */
.trust-field--no-names .trust-client-name{ display:none; }

/* ---- Annotation — a technical drawing note, not a tooltip: no card, no
   bubble, no drop shadow. One shared element; assets/js/trust.js
   repositions it under whichever client is active and fills its text
   from that card's own data-* attributes — never generated copy. ---- */
.trust-annotation{
  position:absolute; top:0; left:0; pointer-events:none;
  font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide); text-transform:uppercase;
  opacity:0; transition:opacity var(--i3) var(--ease-instrument);
}
.trust-annotation.is-visible{ opacity:1; }
.trust-annotation-line{
  position:absolute; top:-14px; left:0; width:1px; height:0; background:var(--stamp);
  transition:height var(--i4) var(--ease-instrument);
}
.trust-annotation.is-visible .trust-annotation-line{ height:14px; }
.trust-annotation-index{ display:block; color:var(--bronze-deep); }
.trust-annotation-name{ display:block; color:var(--ink); margin-top:3px; font-size:11px; letter-spacing:var(--ls-tight); text-transform:none; }
.trust-annotation-category{ display:block; color:var(--ink-soft); margin-top:2px; }

/* ---- Static fallback — Animation off (.trust-field--static) or
   prefers-reduced-motion: no transform loop, no duplicate set, the field
   reads as a calm wrapped arrangement. The annotation is JS-positioned
   only, so it stays hidden here rather than attempting a lesser CSS-only
   version — nothing essential is lost, since category is always
   supplementary to the always-visible logo/name/link. ---- */
.trust-field--static{ mask-image:none; -webkit-mask-image:none; }
.trust-field--static .trust-row{ overflow:visible; }
.trust-field--static .trust-row-track{ transform:none; flex-wrap:wrap; width:auto; }
.trust-field--static .trust-row-set[aria-hidden]{ display:none; }
.trust-field--static .trust-row-set{ flex-wrap:wrap; justify-content:center; padding-right:0; }
.trust-field--static .trust-client{ transform:none; }
.trust-field--static .trust-annotation{ display:none; }

@media (prefers-reduced-motion:reduce){
  .trust-field{ mask-image:none; -webkit-mask-image:none; }
  .trust-field .trust-row{ overflow:visible; }
  .trust-row-track{ transform:none !important; flex-wrap:wrap; width:auto; }
  .trust-row-set[aria-hidden]{ display:none; }
  .trust-row-set{ flex-wrap:wrap; justify-content:center; padding-right:0; }
  .trust-client{ transform:none !important; }
  .trust-annotation{ display:none; }
}

.archive-intro{ padding:var(--pad-standard) 0 var(--pad-lead); }
.archive-intro h2{ font-family:var(--f-display); font-weight:400; font-size:clamp(28px,4vw,46px); max-width:15ch; letter-spacing:var(--ls-head); margin-top:16px; line-height:var(--lh-mega); text-wrap:balance; }
.archive-intro p{ max-width:44ch; color:var(--ink-soft); font-size:15px; line-height:var(--lh-body); margin-top:20px; font-variant-numeric:oldstyle-figures; }

.room{ position:relative; overflow:hidden; padding:var(--pad-tight) 0; }
.room-ghost{ position:absolute; font-family:var(--f-display); font-weight:600; font-size:clamp(180px,32vw,420px); line-height:var(--lh-watermark); top:-2%; user-select:none; z-index:0; pointer-events:none; }
.room--a{ background:var(--ink); color:var(--paper); }
.room--a .room-ghost{ color:var(--paper); opacity:.05; left:0; }
.room--b{ background:var(--ink-2); color:var(--paper); }
.room--b .room-ghost{ color:var(--paper); opacity:.05; right:0; }
.room--c{ background:var(--paper-2); color:var(--ink); }
.room--c .room-ghost{ color:var(--ink); opacity:.05; left:0; }
.room-inner{ position:relative; z-index:1; max-width:var(--max); margin-inline:auto; padding-inline:var(--edge); }
.room--a .room-inner{ display:grid; grid-template-columns:1fr; gap:40px; align-items:center; }
@media (min-width:900px){ .room--a .room-inner{ grid-template-columns:.95fr 1.05fr; gap:70px; } }
.room--b .room-inner{ position:relative; display:flex; flex-direction:column; align-items:center; gap:44px; }
.room--b .plate{ margin-inline:auto; max-width:1100px; }
.room--c .room-inner{ display:grid; grid-template-columns:1fr; gap:40px; align-items:center; }
@media (min-width:900px){ .room--c .room-inner{ grid-template-columns:.8fr 1.2fr; gap:80px; max-width:1200px; } }
.room--a .plate{ max-width:460px; }
.room--c .plate{ max-width:420px; }
/* Rooms are the homepage's single biggest visual moment — give the
   placeholder sketch a touch more presence here than its quiet default
   elsewhere, without changing the shared Plate component itself. */
.room .plate-sketch{ opacity:.22; inset:10%; }

/* Slight stagger on the Ledger rows as they reveal — same reveal system,
   same durations, just offset so the list settles in rather than snapping
   in as one block. */
.ledger-cols .index-row:nth-child(1){ transition-delay:0ms; }
.ledger-cols .index-row:nth-child(2){ transition-delay:40ms; }
.ledger-cols .index-row:nth-child(3){ transition-delay:80ms; }
.ledger-cols .index-row:nth-child(4){ transition-delay:120ms; }
.ledger-cols .index-row:nth-child(5){ transition-delay:160ms; }

.walltext{ display:flex; flex-direction:column; gap:18px; }
.wt-index{ font-family:var(--f-mono); font-size:12px; letter-spacing:var(--ls-tight); color:var(--stamp); }
.wt-title{ font-family:var(--f-display); font-weight:400; font-size:clamp(40px,6.2vw,88px); line-height:var(--lh-mega); letter-spacing:var(--ls-mega); text-wrap:balance; }
.wt-note{ font-family:var(--f-display); font-style:italic; font-weight:360; font-size:clamp(16px,1.6vw,19px); line-height:var(--lh-read); max-width:36ch; opacity:.88; font-variant-numeric:oldstyle-figures; }
.wt-spec{ font-family:var(--f-mono); font-size:11px; line-height:var(--lh-data); letter-spacing:var(--ls-tight); opacity:.7; border-top:1px solid; border-color:inherit; padding-top:14px; margin-top:6px; max-width:40ch; }
.room--c .wt-spec, .room--a .wt-spec{ border-color:var(--line-dark); }
.room--b .walltext{ max-width:520px; text-align:center; }
.room--b .wt-index{ justify-content:center; }
.room--b .wt-note, .room--b .wt-spec{ margin-inline:auto; }
.room--b .wt-spec{ border-top:none; padding-top:0; }

.room--a .stamp{ top:clamp(-14px,-2vw,-18px); right:clamp(4px,2vw,-14px); }
.room--b .stamp{ bottom:26px; right:clamp(10px,4vw,26px); }
.room--c .stamp{ top:clamp(-14px,-2vw,-16px); left:clamp(4px,2vw,-12px); }

/* ---- Ledger — the "Selected Archive" preview. A register, not a card
   grid: rows carry the content, a single shared image slot (no duplicate
   markup — every row's own .ir-preview is absolutely positioned to the
   same box on .ledger-body, see below) reveals only on interaction. ---- */
.ledger{ padding:var(--pad-tight) 0 var(--pad-standard); background:var(--paper); }
.ledger-head{ display:flex; justify-content:space-between; align-items:baseline; gap:20px; margin-bottom:32px; flex-wrap:wrap; }
.ledger-head h3{ font-family:var(--f-display); font-style:italic; font-weight:400; font-size:20px; }
.ledger-note{ flex-basis:100%; font-family:var(--f-display); font-style:italic; font-weight:360; font-size:14px; color:var(--ink-soft); margin-top:-18px; opacity:.85; font-variant-numeric:oldstyle-figures; }
@media (min-width:640px){ .ledger-note{ flex-basis:auto; margin-top:0; order:1; flex:1; } .ledger-head h3{ order:0; } .ledger-link{ order:2; } }

/* "View all N →" — a fine line extension, not a button (§20). */
.ledger-link{ position:relative; padding-bottom:2px; text-decoration:none !important; }
.ledger-link::after{ content:""; position:absolute; left:0; right:0; bottom:0; height:1px; background:currentColor; transform:scaleX(.35); transform-origin:left; transition:transform var(--i3) var(--ease-instrument); }
.ledger-link:hover::after, .ledger-link:focus-visible::after{ transform:scaleX(1); }
.ledger-link:hover, .ledger-link:focus-visible{ opacity:1; }

.ledger-body{ position:relative; }
.ledger-cols{ display:grid; grid-template-columns:1fr; border-top:1px solid var(--ink); }
@media (min-width:800px){ .ledger-cols{ grid-template-columns:1fr 1fr; column-gap:60px; } }
@media (min-width:1024px){ .ledger-cols{ max-width:640px; } }
.ledger-cols > div:first-child{ border-top:1px solid var(--ink); }
@media (min-width:800px){ .ledger-cols > div{ border-top:none; } }

/* Preview — one frame per row, always in the DOM (so it lazy-loads
   ahead of interaction), positioned very differently by breakpoint. */
.ir-preview{
  display:block; width:100%; max-height:0; overflow:hidden;
  transition:max-height var(--i5) var(--ease-instrument);
}
.index-row.is-active .ir-preview{ max-height:420px; }
.ir-preview .plate{ margin:16px 0 4px; max-width:420px; }

@media (min-width:1024px){
  .ir-preview{
    position:absolute; top:0; right:0; width:min(36vw,440px); max-height:none; overflow:visible;
    opacity:0; transform:translateY(6px) scale(1);
    transition:opacity var(--i4) var(--ease-instrument), transform var(--i4) var(--ease-instrument);
    pointer-events:none;
  }
  .index-row.is-active .ir-preview{ opacity:1; transform:translateY(0) scale(1.025); }
  .ir-preview .plate{ margin:0; max-width:none; }
}

/* Active row: neighbours recede (never disappear), the divider and name
   pick up a small accent — the same restrained "editorial focus" language
   already used by the Trusted By field. */
.ledger.has-active .index-row:not(.is-active){ opacity:.6; }

/* Reduced motion / Animation off — state changes stay (the preview still
   opens), but scale/position settle instantly and never via cursor. */
.ledger--static .ir-preview{ transition:opacity var(--i2) linear, max-height var(--i2) linear; transform:none !important; }
@media (prefers-reduced-motion:reduce){
  .ir-preview{ transition:opacity var(--i2) linear, max-height var(--i2) linear !important; transform:none !important; }
}

/* ==========================================================================
   Archive — Editorial layout (NEMA X → Intro → Archive layout = Editorial)
   One continuous chapter sequence — every selector here is scoped under
   .arc-* / .archive-chapters so none of it can ever touch the legacy
   .room/.ledger/.walltext/.ir-* rules above, which stay completely
   untouched as the Current fallback. Reuses the shared .plate/.plate-tick/
   .plate-caption component as-is (system.css) — only new, additive
   descendant selectors are added below, never a rewrite of .plate itself.
   ========================================================================== */
.archive-chapters{ position:relative; }

.arc-chapter{ position:relative; overflow:hidden; padding:var(--pad-tight) 0; }
.arc-chapter--dark{ background:var(--ink); color:var(--paper); }
.arc-chapter--dark2{ background:var(--ink-2); color:var(--paper); }
.arc-chapter--light{ background:var(--paper-2); color:var(--ink); }

/* Oversized numbers — editorial folio indexing, not decoration: very low
   contrast, sits behind everything, never intercepts clicks or reading
   order. Same clamp()-driven sizing idiom as the legacy .room-ghost, so it
   already scales down gracefully on narrower viewports with no dedicated
   breakpoint required. */
.arc-ghost{
  position:absolute; left:var(--edge); top:50%; transform:translateY(-50%);
  font-family:var(--f-display); font-weight:600;
  font-size:clamp(90px,14vw,220px); line-height:1; letter-spacing:-.02em;
  opacity:.07; user-select:none; pointer-events:none; z-index:0;
}
.arc-chapter--reverse .arc-ghost{ left:auto; right:var(--edge); }

.arc-inner{ position:relative; z-index:1; max-width:var(--max); margin-inline:auto; padding-inline:var(--edge); }

/* Text ~42% / image ~58% — inside the brief's 45–60% image-width target.
   .arc-chapter--reverse swaps reading order via `order`, not by
   duplicating markup, so DOM/tab order always matches the text→image
   source order regardless of which side the image lands on visually. */
.arc-grid{ display:grid; grid-template-columns:1fr; gap:36px; align-items:center; }
@media (min-width:900px){
  .arc-grid{ grid-template-columns:.85fr 1.15fr; gap:64px; }
  .arc-chapter--reverse .arc-grid{ grid-template-columns:1.15fr .85fr; }
  .arc-chapter--reverse .arc-text{ order:2; }
  .arc-chapter--reverse .arc-media{ order:1; }
}

.arc-text{ display:flex; flex-direction:column; gap:16px; max-width:46ch; }
.arc-index{ display:flex; align-items:baseline; gap:14px; font-family:var(--f-mono); font-size:12px; letter-spacing:var(--ls-tight); color:var(--stamp); }
.arc-progress{ opacity:.55; font-size:11px; }
.arc-title{ font-family:var(--f-display); font-weight:400; font-size:clamp(30px,4.4vw,56px); line-height:var(--lh-mega); letter-spacing:var(--ls-mega); text-wrap:balance; transition:transform var(--i2) var(--ease-instrument); }
.arc-title a{ color:inherit; text-decoration:none; }
.arc-note{ font-family:var(--f-display); font-style:italic; font-weight:360; font-size:15px; line-height:var(--lh-read); max-width:38ch; opacity:.85; font-variant-numeric:oldstyle-figures; }
.arc-spec{ font-family:var(--f-mono); font-size:11px; line-height:var(--lh-data); letter-spacing:var(--ls-tight); opacity:.68; border-top:1px solid; border-color:inherit; padding-top:12px; margin-top:2px; max-width:36ch; }

.arc-media{ position:relative; }
.arc-media .plate{ max-width:640px; margin-inline:auto; }
@media (min-width:900px){ .arc-media .plate{ max-width:none; } }

/* "VIEW PROJECT →" cue — a quiet text affordance, not a button; hidden
   until interaction, never the only way to discover the project is a link
   (the image and title are already real <a> elements underneath). */
.arc-cue{
  position:absolute; left:16px; bottom:16px; z-index:1;
  font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-label); text-transform:uppercase;
  color:var(--paper); background:rgba(28,27,23,.55); padding:6px 10px;
  opacity:0; transform:translateY(6px);
  transition:opacity var(--i3) var(--ease-instrument), transform var(--i3) var(--ease-instrument);
  pointer-events:none;
}

/* Hover tiers — Archive → Presentation → Project hover. Always image-only
   at "subtle"; "editorial" adds the title micro-shift, metadata lift and
   the cue. Reacting to :hover/:focus-within on the whole chapter (not just
   the image) keeps the "quietly reacts to the visitor" feeling coherent —
   moving the mouse anywhere over the chapter, not one exact pixel, wakes
   it up. */
.archive-chapters[data-hover="subtle"] .arc-chapter:hover .arc-media .plate img,
.archive-chapters[data-hover="subtle"] .arc-chapter:focus-within .arc-media .plate img,
.archive-chapters[data-hover="editorial"] .arc-chapter:hover .arc-media .plate img,
.archive-chapters[data-hover="editorial"] .arc-chapter:focus-within .arc-media .plate img{
  transform:scale(1.02);
  filter:contrast(1.04) brightness(1.02);
}
.arc-media .plate img{ transition:transform var(--i4) var(--ease-instrument), filter var(--i4) var(--ease-instrument); }

.archive-chapters[data-hover="editorial"] .arc-chapter:hover .arc-title,
.archive-chapters[data-hover="editorial"] .arc-chapter:focus-within .arc-title{ transform:translateX(4px); }
.archive-chapters[data-hover="editorial"] .arc-chapter:hover .arc-spec,
.archive-chapters[data-hover="editorial"] .arc-chapter:focus-within .arc-spec{ opacity:.95; }
.arc-spec{ transition:opacity var(--i3) var(--ease-instrument); }
.archive-chapters[data-hover="editorial"] .arc-chapter:hover .arc-cue,
.archive-chapters[data-hover="editorial"] .arc-chapter:focus-within .arc-cue{ opacity:1; transform:none; }

/* Touch/coarse-pointer never gets a hover-only affordance — the cue simply
   never appears there (image/title links still work by tap, unaffected). */
@media (hover:none){
  .arc-cue{ display:none; }
}

/* Image reveal — a controlled architectural wipe + very small scale settle
   (brief: "≈600–900ms, editorial, not theatrical").
   PROGRESSIVE ENHANCEMENT — the Portfolio image must never depend on JS to
   be visible. This default (unscoped) rule is the resting state whenever
   <html> does NOT carry .js-reveal-ready: fully open, fully visible. Only
   assets/js/system.js's own dedicated Archive-reveal init (not the
   generic sitewide .reveal system) adds .js-reveal-ready, and only after
   it has confirmed IntersectionObserver exists and prefers-reduced-motion
   is off. If JS never runs, throws, IntersectionObserver is unsupported,
   or reduced motion is on, .js-reveal-ready is never added and every
   rule below it is simply inert — the image stays visible unconditionally.
   Intentionally has its own class (not the shared .reveal/.reveal--fade
   class) so this fix can't touch any other reveal element on the page. */
.arc-media.reveal--mask{
  opacity:1; transform:none; clip-path:inset(0 0 0% 0);
}
.arc-media.reveal--mask .plate img{ transform:scale(1); }

.js-reveal-ready .arc-media.reveal--mask{
  opacity:0; transform:translateY(10px); clip-path:inset(0 0 100% 0);
  transition:opacity var(--i4) var(--ease-instrument), transform var(--i4) var(--ease-instrument), clip-path 800ms var(--ease-instrument);
}
.js-reveal-ready .arc-media.reveal--mask.is-visible{ opacity:1; transform:none; clip-path:inset(0 0 0% 0); }
.js-reveal-ready .arc-media.reveal--mask .plate img{ transform:scale(1.03); }
.js-reveal-ready .arc-media.reveal--mask.is-visible .plate img{ transform:scale(1); transition-delay:80ms; }
/* Typography resolves slightly after the image, per the brief's
   progression — a short delay on the already-shared .reveal--fade rule,
   scoped to this component only. Untouched: still the generic sitewide
   .reveal/.reveal--fade system, unrelated to the image fix above. */
.arc-chapter .arc-text.reveal--fade{ transition-delay:120ms; }

@media (prefers-reduced-motion:reduce){
  .arc-media.reveal--mask{ clip-path:none !important; opacity:1 !important; transform:none !important; }
  .arc-media.reveal--mask .plate img{ transform:none !important; }
  .arc-chapter .arc-text.reveal--fade{ transition-delay:0ms !important; }
}

/* Tablet — same two-column composition, but the folio number backs off
   further so it can never crowd the text/image pair at this width. */
@media (min-width:900px) and (max-width:1179px){
  .arc-ghost{ font-size:clamp(70px,11vw,140px); opacity:.06; }
}

/* Mobile — clean vertical editorial archive: number (small, inline, not
   background-scale), image, title, metadata, next chapter. No hover
   dependency (the tiers above are already gated by (hover:none) too),
   no reversed reading order, no absolute-positioned ghost numeral. */
@media (max-width:899px){
  .arc-chapter{ padding:var(--pad-lead) 0; }
  .arc-ghost{
    position:static; display:block; transform:none; float:none;
    font-family:var(--f-mono); font-size:12px; font-weight:400;
    letter-spacing:var(--ls-tight); opacity:.55; margin-bottom:14px;
  }
  .arc-inner{ padding-inline:var(--edge); }
  .arc-grid{ gap:24px; }
  /* Mobile is always Number → Image → Title → Metadata (brief §17) — the
     alternating text/image composition is a desktop-only art-direction
     device, never carried down here regardless of --reverse. */
  .arc-media{ order:-1; }
  .arc-media .plate{ max-width:none; }
}

/* Top padding intentionally lighter than the bottom: Studio Introduction's
   own rail + Bridge (immediately above, home.css STUDIO — INTRODUCTION
   block) already supply the run-up into this section, so a second full
   --pad-loose gap here would just duplicate that separation on top of it
   (Studio refinement brief §12). Bottom stays --pad-loose — that gap leads
   into studio.php's own "One Continuous Process" reopening, out of scope
   for this pass. */
.practice{ padding:var(--pad-standard) 0 var(--pad-loose) 0; background:var(--paper); scroll-margin-top:96px; }
.practice-head h2{ font-family:var(--f-display); font-weight:400; font-size:clamp(30px,4.4vw,52px); max-width:16ch; margin-top:16px; letter-spacing:var(--ls-head); line-height:var(--lh-mega); text-wrap:balance; }
.manuscript{ margin-top:clamp(60px,9vw,100px); max-width:1040px; }
.stage{ position:relative; display:flex; gap:28px; padding-block:36px; }
.stage:last-child{ border-bottom:1px solid var(--line); }
/* The rule is a real, drawable element now (assets/js/practice.js), not a
   static border — same pixel, same colour at rest. --stage-line defaults
   to 1 (fully drawn) so a stage the script never touches (Animation off,
   reduced-motion, or this stage's own Animate flag is off) simply reads
   as a normal, complete line — the JS-absent state IS the resting state,
   no separate static override needed anywhere in this block. */
.stage-rule{ position:absolute; top:0; left:0; right:0; height:1px; background:var(--line); transform:scaleX(var(--stage-line,1)); transform-origin:left center; }
/* Construction's rule resolves with a touch more weight/presence than
   Design's or Outcome's — still a hairline, just a shade darker once the
   process reaches assembly. */
.stage[data-stage-role="construction"] .stage-rule{ background:var(--ink-soft); }
/* Generalized from the original fixed nth-child(1/2/3) so the same
   editorial stagger keeps working for any number of stages — a 3-stage
   install (today's default) renders identically, a 4th+ stage repeats
   the cycle rather than piling up on one margin. */
@media (min-width:760px){
  .stage:nth-child(3n+1){ margin-left:0; }
  .stage:nth-child(3n+2){ margin-left:8vw; }
  .stage:nth-child(3n+3){ margin-left:16vw; }
}
/* Scroll-driven process choreography (assets/js/practice.js) — one
   continuous progress value per stage drives all of the below via CSS
   custom properties; the fallbacks are each property's fully-resolved
   resting value, so a stage the script skips just renders complete and
   static, the same "JS-absent state is the resting state" idiom as the
   rule above. No transition here — JS already writes a new value every
   frame, so a CSS transition would only add lag on top of its own easing. */
.stage-num{ font-family:var(--f-mono); font-size:13px; color:var(--stamp); padding-top:6px; flex-shrink:0; width:24px; opacity:var(--stage-num-opacity,1); }
.stage-body{ flex:1; min-width:0; opacity:var(--stage-text-opacity,1); transform:translateY(calc(var(--stage-text-y,0px) + var(--stage-text-parallax,0px))); }
.stage-body h3{ font-family:var(--f-display); font-weight:400; font-size:clamp(24px,3vw,32px); margin-bottom:12px; }
.stage-body p{ color:var(--ink-soft); font-size:15px; line-height:var(--lh-body); max-width:44ch; font-variant-numeric:oldstyle-figures; }
.stage-long{ margin-top:12px; }

/* ---- Stage media — real, uploaded process documents, rendered through
   the same Plate component used everywhere else on the site. Density is
   never chosen by the editor: --tile and the featured item's span are the
   only per-count differences, so any real number of uploads composes
   itself without new per-count layouts. ---- */
.stage-media{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(var(--tile,160px),1fr)); gap:16px; margin-top:28px;
  opacity:var(--stage-media-opacity,1);
  transform:translateY(var(--stage-media-parallax,0px)) scale(var(--stage-media-scale,1));
  /* Design reveals left→right (--stage-reveal-x), Construction top-down in
     a band (--stage-reveal-y) — the "drawing" and "assembly" languages
     from the same clip-path mechanism, just a different edge each; both
     default to 100 (nothing clipped) so Outcome, and any state this
     script never reaches, simply shows the media at full clarity. */
  clip-path:inset(0 calc(100% - var(--stage-reveal-x,100%)) calc(100% - var(--stage-reveal-y,100%)) 0);
}
/* Design's registration marks resolve into place as its media reveals;
   every other role/state keeps the Plate's own sitewide resting opacity
   (system.css) unchanged, since --stage-tick-opacity is only ever
   animated for the design role — this rule just needs enough specificity
   to read that value first. */
.stage-media .plate-tick{ opacity:var(--stage-tick-opacity,.55); }
.stage-media--quiet{ --tile:220px; max-width:560px; }
.stage-media--gallery{ --tile:170px; }
.stage-media--dense{ --tile:120px; gap:10px; }
/* Reveal-on-scroll reuses the site's one existing mechanism — each tile
   carries the shared .reveal class (template-parts/home/practice.php),
   picked up by the same revealIO observer in system.js; nothing new here. */
.stage-media-item{ position:relative; }
.stage-media-item.is-featured{ grid-column:span 2; grid-row:span 2; }
/* Focus Point (NEMA X → Practice Stages → a stage's Media rows) — scoped
   to Practice's own tiles only; the shared .plate img rule (system.css)
   is untouched, so every other Plate on the site keeps its default center
   crop unless a stage media row sets its own focus point. */
.stage-media-item .plate img{ object-position:var(--tile-focal-x,50%) var(--tile-focal-y,50%); }
.stage-media-video{ display:block; width:100%; aspect-ratio:16/9; background:var(--ink); }
.stage-media-caption{ display:block; margin-top:6px; font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-tight); color:var(--ink-soft); }

@media (max-width:899px){
  .stage-media--gallery{ --tile:140px; }
  .stage-media--dense{ --tile:100px; }
}
@media (max-width:599px){
  .stage-media{ grid-template-columns:repeat(2,1fr); }
  .stage-media-item.is-featured{ grid-column:span 2; grid-row:span 1; }
}

/* ==========================================================================
   Practice Scrollytelling v3 — one continuous LINE → STRUCTURE → MATERIAL →
   SPACE transformation ahead of the .manuscript above. Ivory/paper language
   throughout: no dark takeover, no cinematic surface swap. `--story-vh`
   (set inline by practice.js from the Story Length preset) is the only
   thing that changes how tall .pt-scroll-wrap is; every other value here
   is fixed. Decorative only — the whole subtree is aria-hidden in markup,
   real content lives in .manuscript below it.
   ========================================================================== */
.pt-scroll{ margin-top:clamp(48px,7vw,80px); }
.pt-scroll-wrap{ position:relative; height:auto; }
.pt-scroll.is-sticky .pt-scroll-wrap{ height:var(--story-vh,280vh); }

/* The stage is a single flex column now, not a two-column grid — the
   architectural visual is the dominant object of the viewport, not one
   half of an even split. align-items positions the frame (Stage Layout →
   Horizontal Alignment); the chapter register below it inherits the same
   alignment by default, keeping the composition's asymmetry coherent
   rather than mechanically centred. */
.pt-scroll-stage{ position:relative; display:flex; flex-direction:column; gap:clamp(14px,1.6vw,22px); }
.pt-scroll.is-sticky .pt-scroll-stage{ position:sticky; top:0; min-height:100vh; padding-block:64px; justify-content:center; }
.pt-scroll[data-stage-align="left"] .pt-scroll-stage{ align-items:flex-start; }
.pt-scroll[data-stage-align="center"] .pt-scroll-stage{ align-items:center; }
.pt-scroll[data-stage-align="right"] .pt-scroll-stage{ align-items:flex-end; }

.pt-scroll-frame{
  /* Size comes from three admin-set custom properties (Stage Layout →
     Stage Width / Max Width / Max Height, inline on this element from
     practice.php) and one derived one: --pt-stage-aspect is the uploaded
     Story Image's own real width/height ratio, computed server-side —
     the stage's SHAPE is always the photograph's own shape, never a fixed
     guess. Every overlay (draw/construct/mass/photo) is inset:0 and
     percentage-positioned, so all of them resize with this box exactly
     together — one coordinate system, one rectangle, at any size. */
  position:relative; width:min(var(--pt-stage-width-vw,66vw), var(--pt-stage-max-width,1200px));
  max-width:100%; aspect-ratio:var(--pt-stage-aspect,4/5); max-height:var(--pt-stage-max-height,76vh);
  border:1px solid var(--line); background:var(--paper-2); overflow:hidden;
  transform:translate(var(--pt-parallax,0px),var(--pt-parallax,0px));
}

/* -- The Story Image — ONE unclipped layer filling the frame exactly,
   always present (practice.js drives its opacity/grayscale continuously
   from Design through Outcome, never toggling it in or out as separate
   regions). This is what the visitor is looking at throughout the whole
   sequence: faint and monochrome at first, resolving toward full colour
   as Construction proceeds, completely clear by Outcome. Position/scale/
   focal point come from the admin's Image Position controls, unchanged. -- */
.pt-scroll-photo-base{ position:absolute; inset:0; z-index:0; overflow:hidden; opacity:.18; }
.pt-scroll-photo-img{
  position:absolute; inset:0; width:100%; height:100%; display:block; object-fit:cover;
  object-position:var(--pt2-focal-x,50%) var(--pt2-focal-y,50%);
  transform:scale(var(--pt2-scale,1)) translate(var(--pt2-offset-x,0),var(--pt2-offset-y,0));
}

/* -- Solid mass — the same bay boundaries read as opaque material while a
   bay is assembling, then dissolving away with the rest of Construction
   to reveal more of the base photograph beneath. -- */
.pt-scroll-mass{ position:absolute; inset:0; z-index:2; }
.pt-scroll-mass-region{ position:absolute; background:var(--ink-soft); opacity:0; }

/* -- Construction: the SAME bay boundaries again, now read as a structural
   frame outline (always) plus a glazing tint (Façade/Full only) — the
   drawing becoming a built object, not a second unrelated animation.
   Depth/settle comes from practice.js writing translateY()+scaleY() here
   per bay per frame (shared with the mass fill too, so the two move as
   one volume instead of the frame drifting above a static rectangle);
   Construction Depth only changes the amplitude of that JS-driven offset.
   From CONSTRUCT_PEAK on, frame/glazing/mass all dissolve together on one
   shared curve — architecture gaining presence, then the technical
   language stepping aside for it, never fading against its own separately
   clipped photo copy. -- */
.pt-scroll-construct{ position:absolute; inset:0; z-index:3; }
/* A single thin graphite line, opacity-capped well below solid (see the
   tRise*0.55 ceiling in practice.js) — the previous version paired this
   with an inset double-line (a --paper-2 gap banded by a second --ink-soft
   line) meant to read as "frame thickness," but at any real opacity that
   reads as a bordered UI card instead. Depth now comes from the same
   translateY()/scaleY() transform already driving the bay into place,
   plus — Subtle/Spatial only — a soft, undirected drop shadow suggesting
   the plane sits slightly forward of the frame behind it. No inset, no
   second border, no light-coloured gap. */
.pt-bay-frame{
  position:absolute; opacity:0; transform-origin:bottom center;
  border:1px solid var(--ink-soft); box-sizing:border-box;
}
.pt-scroll[data-construct-depth="subtle"] .pt-bay-frame{ box-shadow:0 5px 12px -11px rgba(30,26,22,.3); }
.pt-scroll[data-construct-depth="spatial"] .pt-bay-frame{ box-shadow:0 8px 18px -11px rgba(30,26,22,.4); }
.pt-bay-glass{
  position:absolute; opacity:0; transform-origin:bottom center;
  background:linear-gradient(180deg, rgba(161,61,44,.12), rgba(161,61,44,.03));
}

.pt-scroll-nx{ position:absolute; inset:0; z-index:1; display:flex; align-items:center; justify-content:center; opacity:var(--pt-nx-opacity,0); pointer-events:none; }
.pt-scroll-nx-img{ width:38%; height:auto; object-fit:contain; }

.pt-scroll-frame .plate-tick{ opacity:var(--pt-tick-opacity,.55); z-index:6; }

/* The drawing is the scene's own tracing-paper sheet — derived from the
   Story Image itself (see practice.js), drawn over everything else and
   spanning the whole stage (not just the frame). Every path is populated
   at runtime, so styling is keyed by class, not by a fixed set of IDs.
   Weight forms a hierarchy rather than treating every line the same:
   primary boundaries (outer axes, roof/ground datum) read strongest,
   secondary internal divisions (floor lines, bay axes) read lighter,
   technical annotations (the one diagonal, dimension ticks) read
   quietest of all and carry the only terracotta in the drawing. */
.pt-scroll-draw{ position:absolute; inset:0; width:100%; height:100%; z-index:5; pointer-events:none; opacity:var(--pt-overlay-opacity,0); }
.pt-scroll-draw path{ fill:none; stroke:var(--ink-soft); stroke-width:.75; stroke-dasharray:1; vector-effect:non-scaling-stroke; }
.pt-draw-axis{ stroke-width:1.2; }
.pt-draw-datum{ stroke-width:1.3; }
.pt-draw-axis-v{ stroke:var(--ink-soft); }
.pt-draw-diagonal{ stroke:var(--stamp); opacity:.35; stroke-width:.75; }
.pt-draw-tick{ stroke:var(--stamp); stroke-width:1; opacity:.7; }

/* ---- Chapter register — one compact line, not a tall rail: a thin
   architectural progress rule running through three restrained labels,
   sized to match the stage it sits under (or beside) rather than floating
   independently. The fill itself needs no JS: --pt-master is already set
   on .pt-scroll every frame, and this reads it directly. Only which
   chapter reads active/passed is JS-driven (practice.js), since that
   depends on the mode-conditional Construction/Outcome thresholds. ---- */
.pt-chapters{
  position:relative; display:flex; align-items:flex-start; justify-content:space-between;
  width:min(var(--pt-stage-width-vw,66vw), var(--pt-stage-max-width,1200px)); max-width:100%;
  gap:24px;
}
.pt-progress-track{ position:absolute; left:0; right:0; top:8px; height:1px; background:var(--line); }
.pt-progress-fill{ position:absolute; left:0; top:8px; height:1px; width:100%; background:var(--stamp); transform:scaleX(var(--pt-master,0)); transform-origin:left; }

.pt-progress-item{
  position:relative; display:flex; flex-direction:column; gap:3px; max-width:32%; padding-top:20px;
  font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-label); text-transform:uppercase;
  color:var(--ink-soft); opacity:.4;
  transition:opacity var(--i3) var(--ease-instrument), color var(--i3) var(--ease-instrument);
}
.pt-progress-line{ font-family:var(--f-display); text-transform:none; letter-spacing:0; font-weight:400; font-size:12px; line-height:1.3; color:inherit; opacity:.7; }
.pt-progress-item.is-passed{ opacity:.55; color:var(--ink); }
.pt-progress-item.is-active{ opacity:1; color:var(--stamp); }
.pt-progress-item.is-active .pt-progress-line{ color:var(--ink); opacity:.92; }

/* Minimal: only the active chapter reads at all — no rule, no upcoming/
   passed labels competing for attention. */
.pt-scroll[data-chapter-style="minimal"] .pt-progress-track,
.pt-scroll[data-chapter-style="minimal"] .pt-progress-fill{ display:none; }
.pt-scroll[data-chapter-style="minimal"] .pt-progress-item{ padding-top:0; opacity:0; }
.pt-scroll[data-chapter-style="minimal"] .pt-progress-item.is-active{ opacity:1; }

/* Vertical style, or "beside" position (too narrow for a horizontal row
   regardless of style) — same component, the rule and stack simply turn
   90°. */
.pt-scroll[data-chapter-style="vertical"] .pt-chapters,
.pt-scroll[data-chapter-position="beside"] .pt-chapters{
  flex-direction:column; align-items:flex-start; width:auto; max-width:200px; gap:22px;
}
.pt-scroll[data-chapter-style="vertical"] .pt-progress-track,
.pt-scroll[data-chapter-position="beside"] .pt-progress-track{ left:0; right:auto; top:0; bottom:0; height:auto; width:1px; }
.pt-scroll[data-chapter-style="vertical"] .pt-progress-fill,
.pt-scroll[data-chapter-position="beside"] .pt-progress-fill{ left:0; top:0; height:100%; width:1px; transform:scaleY(var(--pt-master,0)); transform-origin:top; }
.pt-scroll[data-chapter-style="vertical"] .pt-progress-item,
.pt-scroll[data-chapter-position="beside"] .pt-progress-item{ padding-top:0; padding-left:16px; max-width:none; }

/* Beside: sits in whichever side the stage's own alignment leaves open —
   "remaining area = intentional whitespace" is exactly where this goes,
   never fighting the stage for the same space. */
.pt-scroll[data-chapter-position="beside"] .pt-chapters{ position:absolute; top:50%; transform:translateY(-50%); }
.pt-scroll[data-stage-align="right"][data-chapter-position="beside"] .pt-chapters,
.pt-scroll[data-stage-align="center"][data-chapter-position="beside"] .pt-chapters{ left:0; }
.pt-scroll[data-stage-align="left"][data-chapter-position="beside"] .pt-chapters{ right:0; }

@media (max-width:1199px){
  .pt-scroll-frame{ width:min(88vw, var(--pt-stage-max-width,1200px)); max-height:none; }
  .pt-chapters{ width:min(88vw, var(--pt-stage-max-width,1200px)); }
}

/* ---- Mobile / "Sticky Scene: Off" — no pin, no reveal choreography.
   The frame still plays its Design→Construction→Outcome sequence in
   normal flow, just shorter and unpinned (keep the transitions, remove
   the pinning, shorten the distance, simplify the overlay). Chapter
   register always falls back to its simple row-below layout here,
   regardless of the admin's Position/Style choice — "beside" has nowhere
   to go on a narrow screen. ---- */
@media (max-width:767px){
  .pt-scroll.is-sticky .pt-scroll-wrap{ height:auto; }
  .pt-scroll.is-sticky .pt-scroll-stage{ position:static; min-height:0; padding-block:0; }
  .pt-scroll-frame{ width:calc(100vw - (var(--edge) * 2)); max-height:none; }
  .pt-scroll[data-chapter-position="beside"] .pt-chapters{ position:static; transform:none; }
  .pt-scroll[data-chapter-position="beside"] .pt-chapters,
  .pt-scroll[data-chapter-style="vertical"] .pt-chapters{ flex-direction:row; width:100%; max-width:none; }
  .pt-scroll[data-chapter-position="beside"] .pt-progress-track,
  .pt-scroll[data-chapter-style="vertical"] .pt-progress-track{ left:0; right:0; top:8px; bottom:auto; height:1px; width:auto; }
  .pt-scroll[data-chapter-position="beside"] .pt-progress-fill,
  .pt-scroll[data-chapter-style="vertical"] .pt-progress-fill{ left:0; top:8px; height:1px; width:100%; transform:scaleX(var(--pt-master,0)); transform-origin:left; }
  .pt-scroll[data-chapter-position="beside"] .pt-progress-item,
  .pt-scroll[data-chapter-style="vertical"] .pt-progress-item{ padding-top:20px; padding-left:0; max-width:32%; }
  .pt-chapters{ width:100%; }
}
.pt-scroll:not(.is-sticky) .pt-scroll-stage{ position:static; min-height:0; padding-block:40px 0; }
.pt-scroll:not(.is-sticky) .pt-scroll-wrap{ height:auto; }
.pt-scroll:not(.is-sticky) .pt-scroll-draw{ opacity:.25; }

@media (prefers-reduced-motion:reduce){
  /* No JS runs in this case (practice.js exits early), so every var()
     above is already reading its fallback (0) — this block only needs to
     stop the pin/layout itself and force every photo region fully
     visible, showing the plain, calm, completed Story Image with no
     residual overlay — "a clean static composition using the final
     image," exactly as asked. */
  .pt-scroll-wrap{ height:auto !important; }
  .pt-scroll-stage{ position:static !important; min-height:0; padding-block:40px 0; }
  .pt-scroll-frame{ transform:none; }
  .pt-scroll-photo-base{ opacity:1 !important; filter:none !important; }
  .pt-scroll-mass, .pt-scroll-construct, .pt-scroll-nx, .pt-scroll-draw{ display:none; }
  .pt-progress-track, .pt-progress-fill{ display:none; }
  .pt-progress-item{ opacity:1; color:var(--ink-soft); }
  .pt-progress-item[data-role="outcome"]{ color:var(--stamp); }
}

/* ============================== STUDIO — INTRODUCTION ==============================
   Light, editorial, left-aligned — closer to an architecture-publication
   spread than a website hero (Studio expansion brief §5). Renders on the
   Homepage directly before Practice; template-parts/home/studio.php picks
   the narrative back up directly after it. No image, no card. ==== */
.studio-intro{ padding:var(--pad-standard) 0 var(--pad-tight) 0; background:var(--paper); scroll-margin-top:96px; }
.si-head{ max-width:56ch; }
.si-statement{ font-family:var(--f-display); font-weight:400; font-size:clamp(30px,4.4vw,52px); letter-spacing:var(--ls-head); line-height:var(--lh-mega); margin-top:16px; text-wrap:balance; }
.si-body{ color:var(--ink-soft); font-size:15px; line-height:var(--lh-body); max-width:48ch; margin-top:18px; font-variant-numeric:oldstyle-figures; }

/* ---- Design / Construction / Outcome — ONE continuous process rail, not
   three cards: a hairline runs behind all three stages (.dco-rail-line),
   an accent overlay resolves across it as the visitor scrolls
   (.dco-rail-progress, driven by assets/js/system.js), and each stage
   lights up in sequence (.is-active, toggled by the same scroll read).
   All of that is a progressive enhancement layered ONLY under .js-scrub
   (added by JS after confirming no prefers-reduced-motion) — the
   unscoped rules below are the complete, fully-resolved, no-motion-
   required default, so nothing here can ever depend on JS to be legible
   (Studio refinement brief §8/§24). Same roles/order as Practice's own
   Scrollytelling chapters (design/construction/outcome — practice.php's
   $chapters), so the two read as one sequence, not two systems. ---- */
.dco-grid{ margin-top:clamp(56px,8vw,88px); }
.dco-eyebrow{ display:block; font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--ink-soft); margin-bottom:28px; }
.dco-rail{ position:relative; display:grid; grid-template-columns:repeat(3,1fr); column-gap:clamp(28px,4vw,48px); }
.dco-rail-line{ position:absolute; top:3px; left:0; right:0; height:1px; background:var(--line); }
.dco-rail-progress{ position:absolute; top:3px; left:0; right:0; height:1px; background:var(--stamp); transform:scaleX(1); transform-origin:left center; }
.dco-stage{ position:relative; padding-top:22px; opacity:1; }
.dco-node{ position:absolute; top:0; left:0; width:7px; height:7px; margin:-3px 0 0 -3px; border-radius:50%; background:var(--stamp); }
.dco-ref{ font-family:var(--f-mono); font-size:12px; color:var(--stamp); display:block; }
.dco-title{ font-family:var(--f-display); font-weight:400; font-size:clamp(20px,2.4vw,28px); margin-top:8px; letter-spacing:var(--ls-head); transition:color .4s ease; }
.dco-body{ color:var(--ink-soft); font-size:14px; line-height:var(--lh-body); max-width:32ch; margin-top:10px; font-variant-numeric:oldstyle-figures; }

/* JS-enhanced state only — starts muted, resolves stage-by-stage as the
   rail scrolls through view. window.matchMedia('(prefers-reduced-motion)')
   gates the .js-scrub class itself, so this branch is simply never
   reached for a visitor who has asked for reduced motion. */
.dco-rail.js-scrub .dco-rail-progress{ transform:scaleX(var(--dco-progress, 0)); }
.dco-rail.js-scrub .dco-stage{ opacity:.5; transition:opacity .5s ease; }
.dco-rail.js-scrub .dco-stage.is-active{ opacity:1; }
.dco-rail.js-scrub .dco-node{ background:var(--line); transition:background .4s ease; }
.dco-rail.js-scrub .dco-stage.is-active .dco-node{ background:var(--stamp); }
.dco-rail.js-scrub .dco-title{ color:var(--ink-soft); }
.dco-rail.js-scrub .dco-stage.is-active .dco-title{ color:var(--ink); }

@media (max-width:900px){
  .dco-rail{ grid-template-columns:1fr; row-gap:32px; }
  .dco-rail-line{ top:0; left:3px; right:auto; width:1px; height:100%; }
  .dco-rail-progress{ top:0; left:3px; right:auto; width:1px; height:100%; transform:scaleY(1); transform-origin:top center; }
  .dco-rail.js-scrub .dco-rail-progress{ transform:scaleY(var(--dco-progress, 0)); }
  .dco-stage{ padding-top:0; padding-left:26px; }
  .dco-node{ top:6px; left:0; margin:0 0 0 -3px; }
}

/* ---- Bridge — the compact transition into Practice; Practice's own
   heading renders immediately below this, so the bridge deliberately
   never repeats it (see bridge_statement's empty default, options.php). ---- */
.dco-bridge{ margin-top:clamp(40px,6vw,64px); margin-bottom:clamp(8px,2vw,16px); max-width:52ch; }
.dco-bridge-eyebrow{ display:block; font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--ink-soft); }
.dco-bridge-statement{ font-family:var(--f-display); font-weight:400; font-size:clamp(20px,2.4vw,26px); letter-spacing:var(--ls-head); line-height:var(--lh-mega); margin-top:10px; }
.dco-bridge-body{ color:var(--ink-soft); font-size:14px; line-height:var(--lh-body); margin-top:8px; font-variant-numeric:oldstyle-figures; }
.dco-bridge-cue{ display:block; width:1px; height:20px; background:var(--line); margin:18px 0 0 1px; }

.studio{ padding:var(--pad-standard) 0; }
.studio-head h2{ color:var(--paper); font-family:var(--f-display); font-weight:400; font-size:clamp(28px,4vw,44px); max-width:15ch; margin-top:16px; letter-spacing:var(--ls-head); line-height:var(--lh-mega); text-wrap:balance; }
.studio-process-body{ color:var(--on-dark); font-size:15px; line-height:var(--lh-body); max-width:52ch; margin-top:16px; font-variant-numeric:oldstyle-figures; }

/* Shared mono block label — identical treatment to .register-label /
   .practice-index-group b, reused rather than a new eyebrow style. */
.studio-block-label{ font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--bronze-deep); display:block; margin-bottom:26px; }

/* ---- Manifesto — the giant low-opacity numeral is the same device as
   Rooms' .room-ghost (assets/css/pages/home.css), reused here instead of
   a new texture; the first paragraph reads as a pull-line, the rest as
   quieter body copy. ---- */
.studio-manifesto{ position:relative; overflow:hidden; margin-top:clamp(60px,9vw,100px); max-width:720px; }
.studio-block-ghost{ position:absolute; top:-6%; right:-4%; font-family:var(--f-display); font-weight:600; font-size:clamp(120px,18vw,260px); line-height:1; color:var(--paper); opacity:.05; user-select:none; pointer-events:none; z-index:0; }
.studio-manifesto-text{ position:relative; z-index:1; }
.studio-manifesto-text p{ color:var(--on-dark); font-size:15px; line-height:var(--lh-body); max-width:52ch; margin-top:18px; font-variant-numeric:oldstyle-figures; }
.studio-manifesto-text p:first-child{ margin-top:0; font-family:var(--f-display); font-style:italic; font-weight:360; font-size:clamp(17px,1.8vw,21px); color:var(--paper); }

/* ---- Timeline ---- */
.studio-timeline{ margin-top:clamp(70px,10vw,110px); max-width:640px; }
.studio-timeline ul{ border-top:1px solid var(--line-dark); }
.studio-timeline li{ display:flex; align-items:baseline; gap:24px; padding:16px 0; border-bottom:1px solid var(--line-dark); }
.studio-timeline-year{ font-family:var(--f-mono); font-size:13px; color:var(--stamp); width:80px; flex-shrink:0; }
.studio-timeline-title{ font-family:var(--f-display); font-size:16px; color:var(--paper); }

/* ---- Numbers — architectural typography, no animated counters. ---- */
.studio-numbers{ display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:32px 24px; margin-top:clamp(70px,10vw,110px); border-top:1px solid var(--line-dark); padding-top:32px; }
.studio-number{ display:flex; flex-direction:column; gap:8px; }
.studio-number-value{ font-family:var(--f-display); font-weight:400; font-size:clamp(32px,4vw,48px); color:var(--paper); font-variant-numeric:oldstyle-figures; line-height:1; }
.studio-number-label{ font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--on-dark); opacity:.7; }

/* ---- Working Principles — icons are the theme's own existing sketch
   symbols (footer.php's #sketch-arch/ridge/house/plan), never a generic
   icon set. ---- */
.studio-principles{ margin-top:clamp(70px,10vw,110px); max-width:760px; }
.principle{ display:flex; gap:20px; align-items:flex-start; padding:28px 0; border-top:1px solid var(--line-dark); }
.principle:last-child{ border-bottom:1px solid var(--line-dark); }
.principle-ref{ font-family:var(--f-mono); font-size:12px; color:var(--stamp); flex-shrink:0; width:22px; padding-top:4px; }
.principle-icon{ --icon-size:28px; flex-shrink:0; color:var(--paper); opacity:.55; }
.principle-body h3{ font-family:var(--f-display); font-weight:400; font-size:19px; color:var(--paper); margin-bottom:6px; }
.principle-body p{ color:var(--on-dark); font-size:14px; line-height:var(--lh-body); max-width:48ch; font-variant-numeric:oldstyle-figures; }

/* ---- Material Library — small swatches, never a photograph. ---- */
.studio-materials{ margin-top:clamp(70px,10vw,110px); max-width:900px; }
.material-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:24px; }
.material{ display:flex; flex-direction:column; gap:6px; }
.material-swatch{ display:block; width:64px; height:64px; border:1px solid var(--line-dark); overflow:hidden; }
.material-swatch img{ width:100%; height:100%; object-fit:cover; display:block; }
.material-ref{ font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-tight); color:var(--stamp); }
.material-name{ font-family:var(--f-display); font-weight:500; font-size:15px; color:var(--paper); }
.material-desc{ font-family:var(--f-mono); font-size:11px; color:var(--on-dark); opacity:.7; }

/* ---- How We Work — a restrained architectural process line, not five
   boxed cards. Desktop runs horizontally when width allows; mobile stacks
   vertically (brief §14). ---- */
.hww{ margin-top:clamp(70px,10vw,110px); }
.hww-line{ display:grid; grid-template-columns:repeat(5,1fr); gap:20px; border-top:1px solid var(--line-dark); padding-top:28px; }
.hww-stage{ display:flex; flex-direction:column; }
.hww-num{ font-family:var(--f-mono); font-size:12px; color:var(--stamp); }
.hww-rule{ display:block; width:24px; height:1px; background:var(--line-dark); margin:12px 0; }
.hww-title{ font-family:var(--f-display); font-weight:500; font-size:16px; color:var(--paper); display:block; }
.hww-desc{ color:var(--on-dark); font-size:12.5px; line-height:var(--lh-body); margin-top:8px; font-variant-numeric:oldstyle-figures; }
@media (max-width:1023px){ .hww-line{ grid-template-columns:repeat(3,1fr); gap:28px 20px; } }
@media (max-width:640px){ .hww-line{ grid-template-columns:1fr; gap:28px; } }

/* ---- Material & Detail — typography-led (brief §18); never a Materials
   archive. Statement left, four technical labels right as a small
   structured index. ---- */
.material-detail{ margin-top:clamp(70px,10vw,110px); }
.md-grid{ display:grid; grid-template-columns:1.4fr .6fr; gap:clamp(32px,5vw,64px); align-items:start; }
.md-statement{ font-family:var(--f-display); font-weight:400; font-size:clamp(22px,2.8vw,30px); color:var(--paper); letter-spacing:var(--ls-head); max-width:20ch; }
.md-body{ color:var(--on-dark); font-size:14px; line-height:var(--lh-body); max-width:44ch; margin-top:14px; font-variant-numeric:oldstyle-figures; }
.md-labels{ border-top:1px solid var(--line-dark); }
.md-labels li{ display:flex; align-items:baseline; gap:14px; padding:14px 0; border-bottom:1px solid var(--line-dark); }
.md-label-index{ font-family:var(--f-mono); font-size:11px; color:var(--stamp); flex-shrink:0; }
.md-label-text{ font-family:var(--f-mono); font-size:12px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--paper); }
@media (max-width:900px){ .md-grid{ grid-template-columns:1fr; } }

/* ---- Principles — typographic only, deliberately no icons (brief §21). ---- */
.principles-grid{ display:grid; grid-template-columns:repeat(2,1fr); gap:0 clamp(32px,5vw,64px); border-top:1px solid var(--line-dark); }
.principle--typographic{ padding:26px 0; border-bottom:1px solid var(--line-dark); }
.principle-title{ font-family:var(--f-display); font-weight:500; font-size:17px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--paper); display:block; }
.principle-rule{ display:block; width:28px; height:1px; background:var(--line-dark); margin:12px 0; }
.principle-desc{ color:var(--on-dark); font-size:14px; line-height:var(--lh-body); max-width:34ch; font-variant-numeric:oldstyle-figures; }
@media (max-width:700px){ .principles-grid{ grid-template-columns:1fr; } }

/* ---- Final Studio Statement + CTA — deliberate, always the true end of
   the Studio narrative regardless of legacy-block order (brief §22/§23). ---- */
.studio-final{ margin-top:clamp(90px,12vw,140px); padding-top:clamp(50px,7vw,80px); border-top:1px solid var(--line-dark); text-align:left; }
.studio-final-statement{ font-family:var(--f-display); font-weight:400; font-size:clamp(34px,5.5vw,64px); letter-spacing:var(--ls-head); line-height:var(--lh-mega); color:var(--paper); text-wrap:balance; }
.studio-final-subline{ color:var(--on-dark); font-size:15px; margin-top:16px; font-variant-numeric:oldstyle-figures; }
.studio-final-cta{ display:flex; flex-wrap:wrap; gap:clamp(24px,4vw,48px); margin-top:32px; }
.studio-final-cta .link-mono{ color:var(--paper); }

@media (max-width:640px){
  .studio-block-ghost{ font-size:clamp(80px,28vw,140px); }
  .principle{ flex-wrap:wrap; }
}

.contact{ padding:var(--pad-loose) 0 var(--pad-lead); position:relative; background:var(--paper); }
.contact-top{ text-align:left; }
.contact-top .eyebrow{ margin-bottom:20px; }
.contact-email-wrap{ position:relative; display:inline-block; }
/* The studio's signature line — set in the same editorial serif used for
   headline moments elsewhere (--f-display), but kept in its roman/book
   weight at a moderate, fluidly-scaled size (not the larger italic
   headline treatment those other moments use) so it still reads as a
   quiet signature rather than a second heading. Reuses the existing
   external-link icon (NEMA X → Icons) for the arrow — no new asset. */
.contact-email-eyebrow{ display:block; font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--ink-soft); margin-bottom:14px; }
.contact-email{
  position:relative; display:inline-flex; align-items:center; gap:12px;
  font-family:var(--f-display); font-weight:400; font-style:normal; font-size:clamp(20px,4vw,28px);
  letter-spacing:var(--ls-mega); line-height:1.1; padding-bottom:10px;
}
.contact-email::after{
  content:""; position:absolute; left:0; bottom:0; height:1px; width:33%;
  background:var(--ink); transition:width 380ms var(--ease-instrument), background-color var(--i2) var(--ease-instrument);
}
.contact-email:hover::after{ width:60%; background:var(--stamp); }
.contact-email-arrow{ --icon-size:15px; opacity:.55; flex-shrink:0; }
.contact-email-wrap .stamp{ position:absolute; top:-40px; right:-14px; transform:rotate(var(--stamp-rotation,10deg)) translateY(-8px) scale(.92); }
.contact-email-wrap .stamp.is-stamped{ transform:rotate(var(--stamp-rotation,10deg)) translateY(0) scale(.92); }
@media (max-width:600px){ .contact-email-wrap .stamp{ position:static; display:inline-block; margin:20px 0 0; transform:rotate(0) translateY(0) scale(.92); } .contact-email-wrap .stamp.is-stamped{ transform:rotate(0) translateY(0) scale(.92); } }
.contact-grid{ display:grid; grid-template-columns:1fr; gap:56px; margin-top:clamp(70px,10vw,110px); }
@media (min-width:900px){ .contact-grid{ grid-template-columns:.85fr 1.15fr; gap:100px; } }
.contact-info dl > div{ margin-bottom:27px; }
.contact-info dt{ font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-label); text-transform:uppercase; color:var(--bronze-deep); margin-bottom:6px; }
.contact-dt-icon{ --icon-size:12px; margin-right:6px; opacity:.7; }
.contact-info dd{ font-size:15px; color:var(--ink-soft); line-height:var(--lh-body); font-variant-numeric:oldstyle-figures; }
.submit-row{ display:flex; align-items:center; gap:20px; margin-top:10px; }

/* ---- Form refinement — scoped to the Homepage Contact form specifically
   (.contact-form), never the shared .field/.chip base other forms
   (template-contact.php) still use unmodified. ---- */
.contact-form .field label{ font-size:10px; }
.contact-form .field input, .contact-form .field textarea{
  border-color:color-mix(in srgb, var(--line) 65%, transparent);
  transition:border-color 300ms var(--ease-instrument);
}
.contact-form .field input:focus, .contact-form .field textarea:focus{ border-color:var(--ink); }
/* A little extra separation right before the two points where the form
   changes register (free-text fields -> selector -> message) so it reads
   as a few short groups rather than one continuous list of lines. */
.contact-form .field--type-group{ margin-top:16px; }
.contact-form .field--message-group{ margin-top:16px; }

/* ---- Project Type — an architectural label row for the Homepage Contact
   section specifically (#contact): thin hairline dividers between plain
   labels instead of the shared .chip's bordered/filled pill, dark text +
   a thin underline for the selected one instead of a tinted fill. */
#contact .chips{ gap:0; }
#contact .chip{
  border:none; border-right:1px solid var(--line); border-bottom:1px solid transparent;
  padding:2px 20px 6px 0; margin-right:20px; background:none; color:var(--ink-soft);
}
#contact .chip:last-child{ border-right:none; margin-right:0; padding-right:0; }
#contact .chip:hover{ color:var(--ink); }
#contact .chip[aria-pressed="true"]{ border-bottom-color:var(--ink); color:var(--ink); background:none; }

/* ---- Hero email: copy confirmation ---- */
.contact-email-copied{ position:absolute; left:0; bottom:-22px; font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--stamp); opacity:0; transform:translateY(4px); transition:opacity var(--i2) var(--ease-instrument), transform var(--i2) var(--ease-instrument); pointer-events:none; }
.contact-email-copied.is-visible{ opacity:1; transform:translateY(0); }

/* ---- Studio info panel: social row ---- */
.contact-social{ display:flex; flex-wrap:wrap; gap:16px; margin-top:28px; padding-top:22px; border-top:1px solid var(--line); }
.contact-social a{ font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-tight); color:var(--ink-soft); transition:color var(--i2) var(--ease-instrument); }
.contact-social a:hover{ color:var(--stamp); }
.contact-external-icon{ --icon-size:10px; margin-left:4px; opacity:.6; }

/* ---- Project-type icon chips — extends the existing .chip base (system.css) ---- */
.chip{ display:inline-flex; align-items:center; gap:8px; }
.chip-icon{ --icon-size:14px; flex-shrink:0; opacity:.6; }
.chip[aria-pressed="true"] .chip-icon{ opacity:1; }

/* ---- Honeypot — clipped to 1x1px, not display:none (some bots specifically
   skip hidden fields) and not a large negative offset either: that escapes
   any ancestor's overflow:hidden (resolves against the viewport, not a
   clipping parent) and was quietly inflating documentElement.scrollWidth —
   real horizontal overflow once dir="rtl" flips which edge it escapes on. ---- */
.hp-field{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; }

/* ---- Mobile "+ Add more details" disclosure — same grid-template-rows
   technique already proven for the Register accordion (Studio). ---- */
.contact-form-optional-toggle{ display:none; }
@media (max-width:639px){
  .contact-form-optional{ display:grid; grid-template-rows:0fr; overflow:hidden; transition:grid-template-rows var(--i3) var(--ease-instrument); }
  .contact-form-optional.is-open{ grid-template-rows:1fr; }
  .contact-form-optional-inner{ min-height:0; overflow:hidden; }
  .contact-form-optional-toggle{ display:inline-block; font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-tight); text-transform:uppercase; color:var(--stamp); margin-bottom:24px; padding:0; border:0; background:none; cursor:pointer; }
}

/* ---- Validation error ---- */
.field-error{ font-family:var(--f-mono); font-size:12px; color:var(--stamp); min-height:1em; margin-bottom:6px; }
.field-error:empty{ display:none; }

/* ---- Trust line ---- */
.contact-trust{ font-family:var(--f-mono); font-size:11px; letter-spacing:var(--ls-tight); color:var(--ink-soft); opacity:.7; margin-bottom:20px; }

/* ---- Success panel — the stamp is the same shared component, just
   static/centered here rather than corner-pinned like the Hero email's. ---- */
.contact-success-stamp-wrap{ position:relative; height:70px; margin-bottom:8px; }
.contact-success-stamp-wrap .stamp{ position:static; display:inline-block; transform:rotate(var(--stamp-rotation,10deg)) translateY(-8px); }
.contact-success-stamp-wrap .stamp.is-stamped{ transform:rotate(var(--stamp-rotation,10deg)) translateY(0); }
.contact-success-ref{ font-family:var(--f-display); font-weight:400; font-style:italic; font-size:clamp(24px,3vw,32px); margin-top:14px; letter-spacing:var(--ls-read); }
.contact-success-message{ color:var(--ink-soft); font-size:15px; line-height:var(--lh-body); max-width:44ch; margin-top:16px; font-variant-numeric:oldstyle-figures; }

/* ============================== OUR JOURNEY ==============================
   An architectural editorial composition: no horizontal timeline bar, no
   cards, no circles. Milestones sit on their own vertical "datum" lines
   at varying heights — an architectural section-drawing rhythm — inside
   one fixed-height stage, unified by ONE continuous trajectory line
   running through every milestone's own terminal point in sequence.

   Cohesion pass: the trajectory used to be built from near-full-width
   "shelves" under each milestone plus diagonal "steps" in the gaps —
   correct for text clearance, but it read as six separate platforms
   loosely wired together. The shelves are now narrow landings (just
   wide enough to register as a pause on the line — wider for the Hero,
   "a stronger horizontal datum" at the journey's pivot); the diagonal
   travel between them now does most of the visual work, so the whole
   thing reads as one path, not six fragments. The stage itself is
   pulled up toward the header (negative margin, not a change to the
   milestone tiers or the headline) to close the empty gap the two used
   to sit across — tuned twice since, currently -105px. Datum lines are
   shortened via the mark's own
   height (68% of its box, bottom-anchored) rather than by shrinking the
   box the text sits in — visual length changed, text safety margin
   didn't. Roles are Origin (first) / Hero (first Primary) / Destination
   (last) / Normal — all positional, never by year; Normal milestones
   split into a before-Hero and an after-Hero tier pool so the rhythm
   reads as rise-toward-2016/continue-after-2016, not a blind repeating
   cycle. Descriptions stay hidden at rest except for Origin/Destination
   (the journey's narrative bookends). Same milestone data/admin
   throughout — nothing here touches WordPress. ========================== */
.journey{
  background:var(--paper); position:relative; overflow:hidden;
  display:flex; flex-direction:column; min-height:73vh; max-height:780px;
}
.journey-frame{
  /* Same content-grid formula as .container (system.css) — max-width:var(--max)
     + padding-inline:var(--edge) + margin-inline:auto — so Journey's content
     shares the exact same left/right edges as Trust/Studio/Contact/Practice
     instead of its own wider one-off box. Kept as a separate rule (not the
     literal .container class) only because this element is also the
     section's vertical flex column (flex-direction, flex:1, padding-block)
     which .container doesn't provide. */
  /* width:100% is required here specifically because .journey-frame is a
     flex item (of .journey's own column flex) with auto inline margins —
     without an explicit width, a flex item with auto cross-axis margins
     shrinks to fit its content instead of stretching to the container,
     which silently defeated the max-width/margin-inline:auto centering
     below. .container (system.css) never needs this because it's always
     a plain block child, never a flex item. */
  position:relative; z-index:1; width:100%; max-width:var(--max); padding-inline:var(--edge); margin-inline:auto;
  display:flex; flex-direction:column; flex:1; padding-block:clamp(28px,4vw,48px);
}

/* ---- Header: editorial statement top-left, date range top-right — the
   same .eyebrow/serif-headline language used sitewide, split across two
   columns. The headline itself splits on the first ". " into a roman
   line then an italic line (matching the approved reference); a headline
   with no sentence break just renders as one italic line. ---- */
.journey-head{
  flex:none; display:flex; justify-content:space-between; align-items:flex-start; flex-wrap:wrap; gap:28px;
}
.journey-head-main{ max-width:640px; }
.journey-headline{
  font-family:var(--f-display); font-weight:500;
  font-size:clamp(34px,3vw,58px); line-height:1.02; letter-spacing:var(--ls-head);
  color:var(--ink); margin-top:10px; font-variant-numeric:oldstyle-figures;
}
.journey-headline-roman{ display:block; font-style:normal; }
.journey-headline-italic{ display:block; font-style:italic; }
.journey-head-meta{ text-align:end; flex-shrink:0; }
.journey-daterange{
  display:block; font-family:var(--f-display); font-weight:500; letter-spacing:.08em;
  font-size:clamp(17px,1.6vw,22px); color:var(--ink); font-variant-numeric:oldstyle-figures;
  direction:ltr; unicode-bidi:isolate; /* "2009 — 2026" is numerals only in either language */
}
.journey-caption{
  display:block; font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide);
  text-transform:uppercase; color:var(--ink-soft); margin-top:6px;
}

/* ---- Stage: the architectural composition itself. Fills whatever space
   is left between the header and the closing statement (flex:1), so the
   section as a whole stays within its min/max-height budget at any
   viewport height without any element needing a hardcoded pixel height.
   A -105px margin pulls it up toward the headline — the milestone tiers
   all sit in the lower half of the stage's own box, so without this the
   header and the timeline read as two separate zones instead of one
   composition; negative margin here, not a headline change. ---- */
.journey-stage{ position:relative; flex:1; min-height:260px; z-index:1; margin-top:-105px; }

/* Progression line — the ONLY background geometry in the section (the
   earlier large decorative "X" construction lines were removed outright
   — too busy). One faint angular path through every milestone's own
   terminal point, drawn as individual segments (not one polyline) so a
   single segment can brighten on hover/focus of either milestone it
   connects — journey.js toggles `.is-active` per segment, reference-
   counted the same way the old Broken-X's segment-highlight was. Built
   from the exact same top/bottom values each milestone already
   positions itself with (template-parts/home/journey.php), so it can
   never drift out of sync with them, and it is routed at each
   milestone's terminal (bottom) point specifically so it never crosses
   through year/title/metric/description text above it. A drafting
   reference, not a timeline connector — no arrowheads, no dots. */
.journey-progression{
  position:absolute; inset:0; width:100%; height:100%; pointer-events:none; z-index:0;
}
.journey-progression-shelf,
.journey-progression-step{
  stroke:var(--ink); stroke-width:1; opacity:.16; vector-effect:non-scaling-stroke;
  transition:opacity .35s cubic-bezier(.22,.61,.36,1);
}
.journey-progression-shelf.is-active,
.journey-progression-step.is-active{ opacity:.34; }
/* Destination's own landing reads as the trajectory's clean, deliberate
   termination — a touch stronger at rest, nothing decorative added. One
   lever only (the landing itself), not also the step leading into it. */
.journey-progression-shelf--end{ opacity:.28; }
.journey-progression-shelf--end.is-active{ opacity:.47; }
[dir="rtl"] .journey-progression{ transform:scaleX(-1); }

/* ---- Milestones: one flex row: each <li> is a plain horizontal slot,
   height:100% of the stage; the actual content+datum line inside it is
   absolutely positioned at --top/--bottom (both %, set inline from PHP
   per the milestone's tier) so every milestone can sit at its own
   height while the row itself stays one simple, responsive flex line.
   The Hero gets a little extra column width for 100+ to breathe. ---- */
.journey-milestones{
  position:absolute; inset:0; z-index:1; display:flex; align-items:stretch;
  list-style:none; margin:0; padding:0; gap:clamp(6px,1.4vw,22px);
}
.journey-milestone{ flex:1 1 0; min-width:0; height:100%; position:relative; }
.journey-milestone--hero{ flex-grow:1.4; }

.journey-milestone-inner{
  all:unset; box-sizing:border-box; cursor:pointer; position:absolute; inset-inline:0;
  top:var(--top); bottom:calc(100% - var(--bottom));
  display:flex; flex-direction:column; padding-inline-start:16px;
  opacity:1; transition:opacity .38s cubic-bezier(.22,.61,.36,1);
}

/* The datum line itself + its two terminal marks — a tiny perpendicular
   tick at top, a tiny filled square at bottom. Never a circle. Both
   marks inherit the line's own opacity (they're its ::before/::after),
   so "line becomes slightly darker" on hover/focus/active is one rule.
   Anchored to the BOTTOM of the milestone's own box (height, not top+
   bottom:0) and drawn shorter than that box — a secondary supporting
   mark now, not the section's dominant graphic — while the box itself
   keeps its full height so the milestone's text keeps the exact same
   clearance from the trajectory as before; only the visible line got
   shorter, not the safety margin around it. */
.journey-milestone-datum{
  position:absolute; inset-inline-start:0; bottom:0; height:68%; width:1px; background:var(--ink);
  opacity:.18; transition:opacity .38s cubic-bezier(.22,.61,.36,1);
}
.journey-milestone-datum::before{
  content:""; position:absolute; top:0; inset-inline-start:-4px; width:8px; height:1px; background:var(--ink);
}
.journey-milestone-datum::after{
  content:""; position:absolute; bottom:0; inset-inline-start:-1.5px; width:4px; height:4px; background:var(--ink);
}
.journey-milestone-inner:hover .journey-milestone-datum,
.journey-milestone-inner:focus-visible .journey-milestone-datum,
.journey-milestone.is-active .journey-milestone-datum{ opacity:.48; }

.journey-milestone-code{
  font-family:var(--f-mono); font-size:10px; letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--ink-soft);
}
.journey-milestone-year{
  font-family:var(--f-display); font-weight:500; font-size:clamp(19px,1.7vw,25px); color:var(--ink);
  margin-top:6px; font-variant-numeric:oldstyle-figures;
}
.journey-milestone-rule{
  display:block; width:20px; height:1px; background:var(--ink); opacity:.4; margin-top:10px;
  transition:opacity .38s cubic-bezier(.22,.61,.36,1), width .38s cubic-bezier(.22,.61,.36,1);
}
.journey-milestone-metric{
  font-family:var(--f-display); font-weight:600; font-size:clamp(50px,4.6vw,80px); color:var(--ink);
  line-height:1; margin-top:6px;
}
.journey-milestone-title{
  font-family:var(--f-display); font-size:11.5px; letter-spacing:var(--ls-head); text-transform:uppercase;
  color:var(--ink); margin-top:10px;
}

/* ---- Bidi handling — Arabic milestones can mix scripts within one
   field ("17 عاماً", "…NEMA X…"): unicode-bidi:plaintext lets the
   browser's own bidi algorithm resolve each run by its first strong
   character rather than inheriting a fixed direction, which is what
   keeps a short embedded Latin/digit run from disturbing the
   surrounding Arabic. The code/year/metric spans are always pure
   brand-code or numeral content regardless of language ("NX / 01",
   "2016", "100+"), so those get a firmer direction:ltr + isolate
   instead — harmless in English, and it stops a "/" or "+" from ever
   reading in the wrong order inside an RTL flow. Journey-scoped only. */
.journey-milestone-code,
.journey-milestone-year,
.journey-milestone-metric{ direction:ltr; unicode-bidi:isolate; }
.journey-milestone-title,
.journey-milestone-desc{ unicode-bidi:plaintext; }
/* ---- Description: space is ALWAYS reserved (min-height, always in
   flow) so revealing it can never shift the stage or section height.
   Hidden at rest for Normal/Hero milestones — "do not permanently
   display descriptions" — and revealed only on hover/focus/tap; Origin
   and Destination override this below to stay legible at rest, since
   they're the journey's narrative bookends. ---- */
.journey-milestone-desc{
  display:block; font-size:11.5px; line-height:var(--lh-body); color:var(--ink-soft);
  min-height:2.6em; margin-top:8px; max-width:21ch; font-variant-numeric:oldstyle-figures;
  opacity:0; transform:translateY(5px);
  transition:opacity .38s cubic-bezier(.22,.61,.36,1), transform .38s cubic-bezier(.22,.61,.36,1);
}
.journey-milestone--origin .journey-milestone-desc,
.journey-milestone--destination .journey-milestone-desc{ opacity:.6; transform:none; }

/* ---- Hover / focus / tap: the addressed milestone strengthens; every
   other milestone quiets (never hidden). 380ms, no scale, no movement,
   no layout shift. ---- */
.journey-milestone-inner:hover .journey-milestone-desc,
.journey-milestone-inner:focus-visible .journey-milestone-desc,
.journey-milestone.is-active .journey-milestone-desc{ opacity:1; transform:translateY(0); }
.journey-milestone-inner:hover .journey-milestone-rule,
.journey-milestone-inner:focus-visible .journey-milestone-rule,
.journey-milestone.is-active .journey-milestone-rule{ opacity:.75; width:26px; }
.journey-milestone-inner:focus-visible{ outline:2px solid var(--stamp); outline-offset:4px; border-radius:2px; }

.journey-milestones:hover .journey-milestone:not(:hover):not(.is-active) .journey-milestone-inner,
.journey-milestones:focus-within .journey-milestone:not(:focus-within):not(.is-active) .journey-milestone-inner{
  opacity:.6;
}

/* ---- Hero (2016) — the Importance=Primary milestone: no card, no
   border, no badge, no circle — typography and whitespace only. 100+
   stays the largest number in the section, but only ~1.6–2× the visual
   weight of a normal milestone now (down from the previous pass), not
   the whole page's focal point. ---- */
.journey-milestone--hero{ z-index:1; }
.journey-milestone--hero .journey-milestone-code{ opacity:.8; }
.journey-milestone--hero .journey-milestone-year{ font-size:clamp(23px,2vw,30px); }
.journey-milestone--hero .journey-milestone-rule{ width:28px; opacity:.5; }
.journey-milestone--hero .journey-milestone-title{ font-size:13.5px; letter-spacing:var(--ls-wide); margin-top:12px; }
.journey-milestone--hero .journey-milestone-desc{ font-size:12.5px; max-width:26ch; }

/* ---- Origin (first milestone) — "slightly stronger than ordinary",
   never oversized. Description stays visible at rest (see above). ---- */
.journey-milestone--origin .journey-milestone-year{ font-size:clamp(20.5px,1.8vw,26.5px); }
.journey-milestone--origin .journey-milestone-title{ font-size:12.5px; letter-spacing:var(--ls-wide); }

/* ---- Destination (last milestone) — the second-strongest milestone in
   the section (Level 2, alongside the Hero) without ever outweighing
   100+ — it has no large metric number to compete with, so its weight
   comes from title/year size and its own always-on description. ---- */
.journey-milestone--destination .journey-milestone-year{ font-size:clamp(21px,1.9vw,27.5px); }
.journey-milestone--destination .journey-milestone-title{ font-size:13.5px; letter-spacing:var(--ls-wide); }
.journey-milestone--destination .journey-milestone-rule{ width:24px; opacity:.48; }

/* ---- Before-Hero (the Normal milestone right before 2016 — 2012 in the
   current data) — nudged a few px right so it doesn't read as visually
   paired/equal-column with Origin. Its trajectory landing is nudged the
   same amount in journey.php so the datum line and the point the
   trajectory actually touches stay together. Set as a custom property,
   not a direct `transform`, because the entrance rule below already owns
   `transform` (scaleY) on this same element — a second, separate
   `transform` declaration would replace that one instead of combining
   with it. */
.journey-milestone-inner{ --nudge-x:0px; }
.journey-milestone--before-hero .journey-milestone-inner{ --nudge-x:10px; }
[dir="rtl"] .journey-milestone--before-hero .journey-milestone-inner{ --nudge-x:-10px; }

/* ---- Entrance: header settles first (default .reveal--fade timing),
   then the trajectory itself starts fading in at 150ms — so it reads as
   the thing establishing the journey, not a decoration added after the
   fact — and each datum "rises" from its own base (scaleY from its own
   bottom edge) in chronological/DOM order — Origin at 150ms, then every
   150ms after: 2012→Hero→next→next→Destination — landing at ~1.65s
   total. Reduced motion shows the finished state immediately (system.
   css's generic .reveal fallback already covers opacity/transform; only
   this custom scaleY needs its own override). */
.journey-milestone-inner.reveal{
  opacity:0; transform:translateX(var(--nudge-x)) scaleY(.82); transform-origin:bottom;
  transition:opacity .6s cubic-bezier(.22,.61,.36,1), transform .6s cubic-bezier(.22,.61,.36,1);
  transition-delay:calc(var(--i,0) * 180ms + 150ms);
}
.journey-milestone-inner.reveal.is-visible{ opacity:1; transform:translateX(var(--nudge-x)) scaleY(1); }
.journey-progression.reveal--fade.reveal{ transition-delay:150ms; }

@media (prefers-reduced-motion:reduce){
  .journey-milestone-inner.reveal{ transform:translateX(var(--nudge-x)); }
}

/* ---- Closing statement: a small editorial line below the stage, a
   hairline above it separating it from the composition — never another
   full section, never a gallery. The arrows are a purely decorative
   editorial detail from the approved reference, not carousel controls
   (aria-hidden, no handlers, nothing to click). ---- */
.journey-closing{
  flex:none; display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px;
  margin-top:clamp(0px,0.4vw,6px); padding-top:clamp(10px,1.4vw,14px); border-top:1px solid var(--line);
}
.journey-closing-text{
  max-width:640px; padding-inline-start:16px; border-inline-start:1px solid var(--line);
}
.journey-closing-title{
  font-family:var(--f-display); font-size:12px; letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--ink); line-height:1.6; font-weight:500;
}
.journey-closing-body{
  font-size:12px; line-height:var(--lh-body); color:var(--ink-soft); margin-top:8px; max-width:54ch;
  font-variant-numeric:oldstyle-figures;
}
.journey-closing-mark{
  display:flex; align-items:center; gap:14px; font-size:14px; color:var(--ink-soft); opacity:.5; flex-shrink:0;
}
.journey-closing-mark i{ display:block; width:1px; height:14px; background:var(--line); font-style:normal; }

/* ---- Tablet & Mobile — the desktop row is NOT squeezed narrower; below
   1024px this becomes one vertical column with a single shared datum
   line instead of six independent per-item ones (the brief's own
   "another responsive editorial composition if that fits better"). ---- */
@media (max-width:1023px){
  .journey{ min-height:0; max-height:none; }
  .journey-stage{ min-height:0; margin-top:0; } /* the desktop pull-up only applies to the absolute-positioned skyline */
  .journey-milestones{
    position:static; height:auto; flex-direction:column; gap:0;
    border-inline-start:1px solid var(--line); padding-inline-start:22px;
  }
  .journey-milestone{ flex:none; height:auto; }
  .journey-milestone + .journey-milestone{ border-top:1px solid var(--line); }
  .journey-milestone-inner{
    position:static; padding-inline-start:0; padding-block:18px; opacity:1; transform:none;
  }
  .journey-milestone-datum{ display:none; }
  .journey-milestone-desc{ opacity:.55; }
  .journey-milestone--origin .journey-milestone-desc,
  .journey-milestone--destination .journey-milestone-desc{ opacity:.65; }
  .journey-milestones:hover .journey-milestone:not(:hover):not(.is-active) .journey-milestone-inner,
  .journey-milestones:focus-within .journey-milestone:not(:focus-within):not(.is-active) .journey-milestone-inner{
    opacity:1; /* the shared single-line layout has no "siblings to dim" once stacked */
  }
  .journey-progression{ display:none; } /* the segments' points are stage-relative percentages that no longer mean anything once milestones stack vertically */
  .journey-milestone--hero .journey-milestone-metric{ font-size:clamp(36px,5.5vw,56px); }
}

@media (max-width:639px){
  .journey{ padding-block:0; }
  .journey-frame{ padding-block:var(--pad-beat); }
  .journey-head{ flex-direction:column; gap:20px; }
  .journey-head-meta{ text-align:start; }
  .journey-milestone--hero .journey-milestone-metric{ font-size:clamp(30px,10vw,42px); }
  .journey-closing{ flex-direction:column; align-items:flex-start; }
}
