/* Hero (client feedback 2026-10-01): the approach of the retired Why
   UrbanKriti hero, rebuilt here rather than linked to that page's CSS. One
   full-bleed client photograph behind the section, a soft paper wash from
   the left that the large light heading sits on, no buttons and no image
   column of its own. Mobile: copy on a solid paper band at the top, the
   photograph clear below it. */
.ovh {
  position: relative;
  isolation: isolate;
  min-height: min(100svh, 60rem);
  display: grid;
  align-items: start;
  overflow: hidden;
  padding: calc(var(--hdr-safe) + clamp(4.75rem, 11vh, 5.5rem)) 0 2rem;
  color: var(--svc-walnut);
  background: var(--svc-paper);
}
.ovh::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(251,247,239,.97) 0%, rgba(251,247,239,.95) 46%, rgba(251,247,239,.6) 56%, rgba(251,247,239,0) 68%);
}
.ovh__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.ovh__image {
  display: block;
  width: 100%;
  max-width: none;
  height: 100%;
  object-fit: cover;
  object-position: 70% 100%;
  filter: none;
  animation: ovh-field-in 1.2s var(--ease-out) both;
}
@keyframes ovh-field-in { from { opacity: 0; transform: scale(1.018); } to { opacity: 1; transform: none; } }
.ovh__grid { position: relative; z-index: 2; width: var(--svc-frame); margin-inline: auto; }
.ovh__copy { max-width: 23rem; }
.service-experience .ovh h1 {
  max-width: 6ch;
  margin: 0 0 clamp(1rem, 2.3vh, 1.45rem);
  color: var(--svc-walnut);
  font-size: calc(clamp(3.3rem, 14vw, 4.1rem) * var(--k-display));
  font-weight: 300;
  line-height: 1.04;
  letter-spacing: -.03em;
  text-wrap: balance;
}
.ovh__lead {
  max-width: 34ch;
  color: color-mix(in srgb, var(--svc-walnut) 88%, var(--svc-paper));
  font-size: clamp(.95rem, 1.15vw, 1.1rem);
  line-height: 1.65;
}
@media (min-width: 768px) {
  .ovh { align-items: center; padding-bottom: clamp(2.5rem, 7vh, 5rem); }
  .ovh::before {
    background:
      linear-gradient(180deg, rgba(251,247,239,.9) 0, rgba(251,247,239,.5) calc(var(--hdr-safe) * 1.2), rgba(251,247,239,0) calc(var(--hdr-safe) * 2)),
      linear-gradient(90deg, rgba(251,247,239,.97) 0%, rgba(251,247,239,.93) 42%, rgba(251,247,239,.5) 58%, rgba(251,247,239,0) 72%);
  }
  .ovh__image { object-position: 50% 50%; }
  .ovh__copy { max-width: min(54vw, 34rem); }
  .service-experience .ovh h1 { font-size: calc(clamp(4.1rem, 9vw, 5.3rem) * var(--k-display)); }
}
@media (min-width: 1100px) {
  .ovh::before {
    background:
      linear-gradient(180deg, rgba(251,247,239,.9) 0, rgba(251,247,239,.5) calc(var(--hdr-safe) * 1.2), rgba(251,247,239,0) calc(var(--hdr-safe) * 2)),
      linear-gradient(90deg, rgba(251,247,239,.97) 0%, rgba(251,247,239,.94) 30%, rgba(251,247,239,.68) 41%, rgba(251,247,239,0) 54%);
  }
  .ovh__copy { max-width: min(38vw, 36rem); }
  .service-experience .ovh h1 { font-size: calc(clamp(4.8rem, 6.4vw, 7.2rem) * var(--k-display)); }
}

/* The assembly sequence is its own section now, directly under the hero. */
.overview-assembly { padding-bottom: 2rem; }

/* The drawing used to be pinned via position:absolute at a guessed vh
   offset (38vh, then 46vh) — fragile by construction: at any viewport
   SHORTER than whatever height the guess assumed, the headline's real
   rendered bottom edge could sit lower than the guess, and the image cut
   straight through it (reproduced at 1366×768 and 1024×768, where
   "Coordinated" and "Project Needs." respectively were cut). Normal
   document flow instead: the image begins wherever the headline actually
   ends, at any viewport size or font-size, by construction rather than by
   tuning a number that only happened to fit one screen height. This is
   not a z-index trick — the two are no longer stacked at all, so there is
   real layout space between them, not just paint order.
   display:flow-root only exists to stop the sticky figure's own
   margin-top from collapsing through this box now that it's in normal
   flow.
   Height bumped 124vh → 190vh: overview.js's scroll-progress used to read
   the OUTER [data-assembly-section] (the whole hero, headline included),
   so most of the 0–1 range burned off while scrolling past the headline —
   reported as the crossfade already being finished by the time this stage
   was even on screen. Fixed by reading .assembly-stage's own position
   instead, but that only left ~184px of actual pin distance for five
   states to cross-fade in (measured) — twitchy on a couple of wheel ticks.
   190vh gives the sticky figure enough runway to pin for a comfortable
   scroll distance once it's reading progress off itself. */
.assembly-stage { display: flow-root; position: relative; width: var(--svc-frame); height: 190vh; margin: clamp(2rem, 6vh, 4.5rem) auto 0; }

/* Removed from this page entirely — the other four Services pages keep
   their numbered rail (services.shared.css); overview drops it for a
   simpler hero. */
body.service-page--overview .service-rail { display: none; }

/* Grid softened further here specifically — the drawing below already
   carries its own linework, so a strong grid competes with it. The other
   four Services pages keep the shared strength (services.shared.css). */
body.service-page--overview .architectural-grid::after { opacity: .84; }

/* The hero and the coordination workspace either side of this section stay
   cream — this one breaks that rhythm on purpose, the same dark walnut
   .project-conclusion already uses further down this page (and every other
   Services page's own dark section, services.shared.css). Everything
   inside inherits var(--text-on-dark) from this; only the eyebrow needs its
   own on-dark variant (added in the markup) since .eyebrow's default color
   is a specific value, not `inherit`. */
body.service-page--overview .service-intro { color: var(--text-on-dark); background: var(--svc-walnut); }
/* h2 needs its own override: h1/h2/h3/h4 carry a global explicit
   color:var(--text-primary) (global.css), which is dark umber and beats
   plain inheritance from .service-intro above — without this the headline
   rendered nearly invisible, dark text on the dark background. The
   paragraph text elsewhere in this section has no such rule and inherited
   correctly on its own. */
body.service-page--overview .service-intro h2 { color: var(--text-on-dark); }
.assembly-stage figure { position: sticky; top: calc(var(--hdr-safe) + 2vh); height: min(74vh, 820px); margin-top: 8vh; overflow: hidden; background: var(--svc-sand); }
.assembly-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Five real project states crossfade across five scroll bands: 0–.18 drawing,
   .18–.38 structure, .38–.58 services, .58–.78 finishing, .78–1 complete. */
.assembly-drawing { opacity: calc(1 - min(var(--assembly-progress, 0) * 2.7, 1)); }
.assembly-structure { opacity: clamp(0, calc((var(--assembly-progress, 0) - .18) * 5), 1); clip-path: inset(0 calc((1 - clamp(0, calc((var(--assembly-progress, 0) - .18) * 5), 1)) * 100%) 0 0); }
.assembly-services { opacity: clamp(0, calc((var(--assembly-progress, 0) - .38) * 5), 1); clip-path: inset(0 calc((1 - clamp(0, calc((var(--assembly-progress, 0) - .38) * 5), 1)) * 100%) 0 0); }
.assembly-finishing { opacity: clamp(0, calc((var(--assembly-progress, 0) - .58) * 5), 1); clip-path: inset(0 calc((1 - clamp(0, calc((var(--assembly-progress, 0) - .58) * 5), 1)) * 100%) 0 0); }
.assembly-complete { opacity: clamp(0, calc((var(--assembly-progress, 0) - .78) * 4.6), 1); clip-path: inset(calc((1 - clamp(0, calc((var(--assembly-progress, 0) - .78) * 4.6), 1)) * 50%) 0 0); }
.inspection-window { position: absolute; width: var(--lens-w, 220px); height: var(--lens-h, 160px); left: calc(var(--inspect-x, .66) * 100% - var(--lens-w, 220px) / 2); top: calc(var(--inspect-y, .52) * 100% - var(--lens-h, 160px) / 2); border: 1px solid var(--svc-copper); overflow: hidden; opacity: 0; transition: opacity .2s; pointer-events: none; }
.inspection-window img { position: absolute; width: var(--inspect-stage-w, 1000px); height: var(--inspect-stage-h, 700px); max-width: none; object-fit: cover; left: calc(var(--inspect-x, .66) * -1 * var(--inspect-stage-w, 1000px) + var(--lens-w, 220px) / 2); top: calc(var(--inspect-y, .52) * -1 * var(--inspect-stage-h, 700px) + var(--lens-h, 160px) / 2); opacity: 0; }
/* The inspection window always previews the *next* stage, set via data-reveal by JS. */
figure[data-reveal="0"] .inspection-window .insp-drawing,
figure[data-reveal="1"] .inspection-window .insp-structure,
figure[data-reveal="2"] .inspection-window .insp-services,
figure[data-reveal="3"] .inspection-window .insp-finishing,
figure[data-reveal="4"] .inspection-window .insp-complete { opacity: 1; }
.assembly-stage figure:hover .inspection-window, .assembly-stage figure:focus-within .inspection-window { opacity: 1; }
.inspection-tag { position: absolute; left: calc(var(--inspect-x, .66) * 100% + 120px); top: calc(var(--inspect-y, .52) * 100% - 80px); padding: .35rem .5rem; color: var(--svc-paper); background: var(--svc-walnut); font-size: .78rem; letter-spacing: 0; opacity: 0; transition: opacity .2s; }
.assembly-stage figure:hover .inspection-tag { opacity: 1; }
.assembly-sequence { position: sticky; top: calc(var(--hdr-safe) + min(74vh, 820px) + 3vh); display: flex; justify-content: space-between; gap: .5rem; padding-top: .75rem; border-top: 1px solid var(--svc-line); }
.assembly-sequence button { color: inherit; font-size: 0.76rem; letter-spacing: 0; opacity: .48; transition: opacity .2s; }
.assembly-sequence button.is-active { opacity: 1; color: var(--svc-copper); }
.assembly-sequence button:hover, .assembly-sequence button:focus-visible { opacity: 1; }
.discipline-workspace { padding: clamp(6rem, 10vw, 9rem) 0; }
/* .workspace-grid / .workspace-zone / .zone-* below: not referenced by any
   current markup in src/pages/services/overview/index.html (confirmed via
   grep against tools/build_services_experience.mjs before this migration —
   likely a leftover four-card layout from an earlier design iteration,
   superseded by .coordination-workspace). Kept rather than deleted per this
   migration's "don't delete blindly" rule; safe to remove in a future pass
   once confirmed unused for good. */
.workspace-grid { display: grid; border: 1px solid var(--svc-line); background: var(--svc-paper); }
.workspace-zone { position: relative; min-height: 29rem; padding: 1.5rem; overflow: hidden; border-bottom: 1px solid var(--svc-line); transition: background .25s; }
.workspace-zone:last-child { border-bottom: 0; }
.workspace-zone > span { color: var(--svc-copper); font-size: .8rem; letter-spacing: 0; }
.workspace-zone h3 { margin-top: 2rem; max-width: 10ch; font-size: clamp(1.8rem, 3vw, 3rem); }
.workspace-zone p { margin-top: 1rem; max-width: 30rem; color: var(--text-secondary); font-size: .9rem; }
.workspace-zone > i { position: absolute; inset: auto 1.5rem 1.25rem; font-style: normal; font-size: 0.845rem; letter-spacing: 0; }
.workspace-zone svg { position: absolute; inset: auto 0 3.5rem; width: 100%; height: 46%; color: var(--svc-brown); }
.workspace-zone svg path { fill: none; stroke: currentColor; stroke-width: 1; vector-effect: non-scaling-stroke; }
.workspace-zone .zone-detail { stroke-dasharray: 520; stroke-dashoffset: 520; transition: stroke-dashoffset .7s var(--ease-out); }
.workspace-zone:hover, .workspace-zone:focus-visible { background: #efe4d3; }
.workspace-zone:hover .zone-detail, .workspace-zone:focus-visible .zone-detail { stroke-dashoffset: 0; }
.zone-before-after { position: absolute; inset: auto 0 3.5rem; height: 46%; overflow: hidden; }
.zone-before-after img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: sepia(.18) saturate(.7); }
.zone-before-after img:last-of-type { clip-path: inset(0 0 0 50%); }
.zone-before-after b { position: absolute; inset: 0 auto 0 50%; width: 1px; background: var(--svc-copper); transition: transform .25s; }
.zone-renovation:hover .zone-before-after b { transform: translateX(18px); }
.coordination-workspace { display: grid; min-height: min(72rem, 82vh); border: 1px solid var(--svc-line); background: var(--svc-paper); }
.coordination-index { position: relative; z-index: 3; border-bottom: 1px solid var(--svc-line); }
.coordination-row { display: grid; grid-template-columns: 1fr 3.5rem; border-bottom: 1px solid var(--svc-line); }
.coordination-row:last-child { border-bottom: 0; }
.coordination-row > button { display: grid; grid-template-columns: minmax(0, 1fr); gap: .35rem 1rem; width: 100%; min-height: 7.6rem; padding: 1.1rem; color: inherit; text-align: left; opacity: .48; transition: opacity .25s, background .25s, padding .25s; }
/* round 6: the four services are not a sequence, so no "01-04" markers */
.coordination-row > button span { display: none; }
.coordination-row > button b { align-self: end; font-family: var(--font-display); font-size: calc(clamp(1.7rem, 2.5vw, 2.6rem) * var(--k-display)); font-weight: 300; line-height: 1.04; letter-spacing: -.02em; }
.coordination-row > button small { max-width: 26rem; color: var(--text-secondary); font-size: .85rem; line-height: 1.45; }
.coordination-row > button[aria-selected="true"] { padding-left: 1.5rem; opacity: 1; background: #efe4d3; }
.coordination-row > a { display: grid; place-items: center; border-left: 1px solid var(--svc-line); color: var(--svc-copper); font-size: 1.2rem; transition: color .2s, background .2s; }
.coordination-row > a:hover, .coordination-row > a:focus-visible { color: var(--svc-paper); background: var(--svc-walnut); }
.coordination-scene { position: relative; min-height: 34rem; overflow: hidden; background: var(--svc-sand); }
.coordination-image, .coordination-before { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity .65s, clip-path .75s, transform .75s; }
.coordination-image, .coordination-before img { width: 100%; height: 100%; object-fit: cover; }
.coordination-drawing { opacity: 1; }
.coordination-structure, .coordination-services, .coordination-finish, .coordination-renovation-after, .coordination-before, .coordination-plan { opacity: 0; }
.coordination-plan { transform: scale(1.04); transition: opacity .6s, transform .8s var(--ease-out); }
.coordination-workspace[data-coordination-state="construction"] .coordination-drawing { opacity: .18; }
.coordination-workspace[data-coordination-state="construction"] .coordination-structure { opacity: 1; clip-path: inset(0 0 0 0); }
.coordination-workspace[data-coordination-state="construction"] .coordination-services { opacity: .75; clip-path: inset(0 52% 0 0); }
.coordination-workspace[data-coordination-state="renovations"] .coordination-drawing { opacity: 0; }
.coordination-workspace[data-coordination-state="renovations"] .coordination-renovation-after { opacity: 1; }
.coordination-workspace[data-coordination-state="renovations"] .coordination-before { opacity: 1; clip-path: inset(0 50% 0 0); }
.coordination-workspace[data-coordination-state="planning"] .coordination-drawing { opacity: .08; transform: scale(.88); }
.coordination-workspace[data-coordination-state="planning"] .coordination-plan { opacity: 1; transform: scale(1); }
/* Closing band. Client feedback 2026-10-01 (second pass): with its button
   gone the section was a narrow six-line column in a ~930px dark band, and
   phones showed ~300px of near-empty vignetted photo under it. Now compact:
   moderate padding, the heading set as its two sentences (each one block,
   free to wrap) at a size that fills its column in about four lines, and
   the photo either side-by-side (1100px+) or a short wide crop below.
   The heading and photo are always in normal flow, so nothing can clip. */
.project-conclusion { padding: clamp(4.5rem, 9vw, 8.5rem) 0; }
.project-conclusion .service-frame { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(2rem, 5vw, 3rem); align-items: center; }
.project-conclusion-copy { min-width: 0; }
.project-conclusion h2 { max-width: 22ch; margin: 0; color: inherit; font-size: calc(clamp(2.5rem, 10vw, 3.4rem) * var(--k-display)); line-height: 1.02; text-wrap: balance; }
@media (min-width: 768px) {
  .project-conclusion h2 { max-width: 20ch; font-size: calc(clamp(3.4rem, 7.2vw, 4.4rem) * var(--k-display)); }
}
@media (min-width: 1100px) {
  .project-conclusion .service-frame { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: clamp(3rem, 5vw, 6rem); }
  .project-conclusion h2 { max-width: none; font-size: calc(clamp(3.4rem, 3.9vw, 4.7rem) * var(--k-display)); }
}
/* Plain opacity fade per sentence, not a masked slide-up: a slide through
   a mask, caught mid-way (reload, deep link, screenshot), looks like
   clipped letterforms; an opacity fade is always whole, just dimmer. */
.project-conclusion h2 .line-mask { display: block; }
.project-conclusion h2 .line { display: block; opacity: 0; animation: pc-line-in .5s ease-out forwards; }
.project-conclusion h2 .line-mask:nth-child(2) .line { animation-delay: 140ms; }
@keyframes pc-line-in { to { opacity: 1; } }

/* "The Room Appears From Darkness" — a real photograph (a lit recess
   opening off a dark corridor, wall/floor/ceiling/light all already in the
   frame) replaces the previous pass's stacked material planes, which read
   as flat sample boards rather than architecture. The reveal is choreographed
   on top of the photo, not built from scratch: a veil the same colour as
   this section's own background sits over the image so at rest it reads as
   part of the dark backdrop, a small glow appears first at the photo's own
   light source, then the veil clears to what was already a fully composed
   room. Softly vignetted (mask-image) rather than a hard rectangle, so
   nothing here reads as a card or a board. */
.room-reveal {
  position: relative;
  width: 100%;
  justify-self: end;
  /* Below 1100px a short wide crop centred on the lit doorway, so the photo
     reads as a picture rather than a tall dark patch under the heading. */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* Soft vignette so the photo melts into the section's own --svc-walnut
     behind it rather than reading as a hard rectangle. Gentler than before
     (solid to 36%, was 22%) so the room stays visible, but still fully
     transparent before every edge, so no rectangle shows. */
  mask-image: radial-gradient(ellipse 58% 56% at 50% 50%, #000 36%, transparent 88%);
  -webkit-mask-image: radial-gradient(ellipse 58% 56% at 50% 50%, #000 36%, transparent 88%);
  transition: transform .6s var(--ease-out);
}
.room-reveal img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 62% 50%; filter: saturate(.94) contrast(1.02); transition: filter .5s ease; }
/* The glow sits at roughly where the ceiling light line is brightest in the
   photo (measured against the actual asset) — it fades in first, alone in
   the dark, then the veil lifts to reveal that same light already built
   into the frame, so the two read as one continuous event rather than two
   unrelated effects.
   Both this and .room-veil below use fill-mode backwards, not forwards —
   the same mistake as the previous pass's joinery planes would recur in
   reverse here otherwise: backwards holds only the *pre-start* keyframe,
   and once the animation ends it falls back to the element's own plain
   (non-animation) property values — so those plain values have to already
   equal the animation's "to" state (opacity:.85 here, opacity:0 on the
   veil), not the "from" one, or the effect would hold correctly for a
   moment and then snap back to hidden/opaque right after finishing. */
.room-glow {
  position: absolute;
  left: 58%; top: 10%; width: 30%; height: 40%;
  background: radial-gradient(closest-side, rgba(255, 205, 140, .85), rgba(255, 205, 140, 0) 72%);
  filter: blur(14px);
  opacity: .85;
  transition: opacity .5s ease;
  animation: room-glow-in 1.1s var(--ease-out) 150ms backwards;
}
.room-veil {
  position: absolute; inset: 0;
  background: var(--svc-walnut);
  opacity: 0;
  animation: room-veil-out 1.3s var(--ease-out) 350ms backwards;
}
@keyframes room-glow-in { from { opacity: 0; transform: scale(.55); } }
@keyframes room-veil-out { from { opacity: 1; } }
/* Subtle only, as asked: a hair of extra depth and a touch more light, not
   a resize — a real hover state rather than a static image, without
   drawing attention to itself. */
.room-reveal:hover { transform: scale(1.012); }
.room-reveal:hover img { filter: saturate(.98) contrast(1.03) brightness(1.03); }
.room-reveal:hover .room-glow { opacity: 1; }
@media (max-width: 1099px) {
  .room-reveal:hover { transform: none; }
}
/* Phones: the vignetted photo read as an empty dark patch under the heading,
   so the band is just the statement there. Decorative (aria-hidden) and lazy,
   so hiding it also skips the download. */
/* Mobile pass 2026-10-01: phones show the room again (same content and
   effect as desktop): the desktop vignette on a 16:10 crop so it reads
   as the lit room, not a dark patch, and the reveal now plays when the band
   is reached (overview.js) rather than on load, unseen. */
@media (max-width: 767px) {
  .room-reveal {
    aspect-ratio: 16 / 10;
    mask-image: radial-gradient(ellipse 58% 56% at 54% 50%, #000 40%, transparent 86%);
    -webkit-mask-image: radial-gradient(ellipse 58% 56% at 54% 50%, #000 40%, transparent 86%);
  }
  .room-reveal img { object-position: 60% 45%; }
}
.project-conclusion[data-reveal="pending"] h2 .line,
.project-conclusion[data-reveal="pending"] .room-glow,
.project-conclusion[data-reveal="pending"] .room-veil { animation-play-state: paused; }
@media (min-width: 1100px) {
  .room-reveal { max-width: 760px; aspect-ratio: 1448 / 1086; }
  .room-reveal img { object-position: 50% 50%; }
}
@media (min-width: 768px) {
  .workspace-grid { grid-template-columns: repeat(2,1fr); }
  .coordination-workspace { grid-template-columns: minmax(19rem,.55fr) minmax(0,1.45fr); }
  .coordination-index { border-right: 1px solid var(--svc-line); border-bottom: 0; }
  .workspace-zone:nth-child(odd) { border-right: 1px solid var(--svc-line); }
  .workspace-zone:nth-last-child(-n+2) { border-bottom: 0; }
}
/* Phones (mobile pass 2026-10-01). The assembly keeps every desktop effect,
   fitted: a pinned runway long enough for five states to read one by one
   (it was ~290px, so states 2 and 3 flashed past in a single flick), the
   inspection lens shown on touch at a phone size (a tap moves it,
   overview.js) instead of hidden, and a stage strip whose labels are real
   tap targets that keep the active stage in view, with an edge fade as the
   cue that it scrolls. svh, not vh, so the pin does not jump when the URL
   bar slides. */
@media (max-width: 767px) {
  .assembly-stage { height: 230vh; height: calc(58svh + 2rem + 150svh); margin-top: 1.5rem; }
  .assembly-stage figure { top: calc(var(--hdr-safe) + 1rem); height: 58vh; height: 58svh; margin-top: 2rem; }
  .assembly-stage figure { --lens-w: min(9rem, 40vw); --lens-h: min(6.5rem, 29vw); }
  .inspection-tag { left: clamp(.5rem, calc(var(--inspect-x, .66) * 100% - var(--lens-w) / 2), calc(100% - 13.5rem)); top: min(calc(var(--inspect-y, .52) * 100% + var(--lens-h) / 2 + .35rem), calc(100% - 2.25rem)); font-size: .6rem; white-space: nowrap; }
  .assembly-sequence { top: calc(var(--hdr-safe) + 1rem + 58svh); gap: .25rem; padding-top: 0; overflow-x: auto; scrollbar-width: none; overscroll-behavior-inline: contain; --fade-l: 0px; --fade-r: 2.5rem; -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent 100%); mask-image: linear-gradient(90deg, transparent 0, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent 100%); }
  .assembly-sequence.is-scrolled { --fade-l: 2.5rem; }
  .assembly-sequence.is-end { --fade-r: 0px; }
  .assembly-sequence::-webkit-scrollbar { display: none; }
  .assembly-sequence button { flex: none; min-height: 2.75rem; padding: 0 .5rem; font-size: .8rem; }
  .assembly-sequence button:first-child { padding-left: 0; }
  .workspace-zone { min-height: 26rem; }
  .coordination-workspace { min-height: auto; }
  .coordination-row { grid-template-columns: 1fr 3rem; }
  .coordination-row > button { min-height: 6.5rem; }
  .coordination-row > button small { font-size: .8rem; line-height: 1.45; }
  /* The preview scene leads and stays pinned under the header while the
     four rows pass beneath it, so the image a tap (or the row crossing the
     reading line, overview.js) selects is always on screen. Desktop shows
     the two side by side for the same reason. */
  .coordination-scene { order: -1; position: sticky; top: calc(var(--hdr-safe) + .25rem); z-index: 4; min-height: 0; height: clamp(12.5rem, 36svh, 21rem); border-bottom: 1px solid var(--svc-line); }
}
@media (hover: none) and (max-width: 767px) {
  /* touch: the lens is the in-view state, not a hover */
  [data-assembly] figure.is-lens .inspection-window,
  [data-assembly] figure.is-lens .inspection-tag { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .ovh__image { animation: none; }
  .assembly-stage { position: relative !important; top: auto; left: auto; width: var(--svc-frame); height: auto; margin: 3rem auto 0; transform: none; }
  .assembly-stage figure { position: relative; top: auto; height: auto; aspect-ratio: 3/2; margin: 0; }
  .assembly-drawing, .assembly-structure, .assembly-services, .assembly-finishing { display: none; }
  .assembly-complete { opacity: 1; clip-path: none; }
  .assembly-sequence { display: none; }
  .project-conclusion h2 .line { animation: none; opacity: 1; }
  .room-veil { animation: none; opacity: 0; }
  .room-glow { animation: none; opacity: .85; transform: none; }
  .room-reveal, .room-reveal img { transition: none; }
  .room-reveal:hover { transform: none; }
}

/* Touch screens of any width: the stage labels are 44px tap targets. */
@media (hover: none) {
  .assembly-sequence button { min-height: 2.75rem; font-size: .8rem; }
}
/* Landscape phones (wide, short): the pinned figure, its stage strip and the
   coordination scene are fitted to the screen height, so the strip is never
   pinned below the fold and the scene never taller than the screen. */
@media (orientation: landscape) and (max-height: 560px) {
  .assembly-stage { height: 240vh; height: 240svh; margin-top: 1.5rem; }
  .assembly-stage figure { top: calc(var(--hdr-safe) + .5rem); height: calc(100vh - var(--hdr-safe) - 4.25rem); height: calc(100svh - var(--hdr-safe) - 4.25rem); margin-top: 1rem; }
  .assembly-sequence { top: calc(100vh - 3.5rem); top: calc(100svh - 3.5rem); padding-top: 0; }
  .coordination-workspace { min-height: 0; }
  .coordination-scene { position: sticky; top: calc(var(--hdr-safe) + .5rem); align-self: start; min-height: 0; height: calc(100vh - var(--hdr-safe) - 1.5rem); height: calc(100svh - var(--hdr-safe) - 1.5rem); }
  .coordination-row > button { min-height: 5.5rem; }
}
@media (orientation: landscape) and (max-height: 560px) and (prefers-reduced-motion: reduce) {
  .assembly-stage { height: auto; }
  .assembly-stage figure { position: relative; top: auto; height: auto; }
}
@media (prefers-reduced-motion: reduce) and (hover: none) {
  /* the static fallback shows the finished space alone */
  .inspection-window, .inspection-tag { display: none; }
}
