
/* ============================================================
   §NA · NO BARE COLOURED ACCENT LINES  (permanent rule)
   ------------------------------------------------------------
   Abhay, 2026-08-18: a bare vertical or horizontal coloured bar
   on a box, card, tile, callout or media frame instantly reads
   as an AI-generated layout. It is banned SITEWIDE.

   Structure now comes from spacing, type hierarchy, background
   and real content — never a decorative colour bar.

   ⚠ THIS SUPERSEDES the DESIGN LANGUAGE SPEC v1 "engineered
   framing" signature (a 1-2px brand rule on ONE edge of every
   photograph). That device is retired by this rule. The frame,
   its radius, its tile gradient and its tracked mono fact
   caption all REMAIN — only the coloured edge bar is gone.

   DELIBERATELY KEPT, because none of these is an accent bar:
     · 6x6 brand squares used as LIST BULLETS (a marker)
     · the dimension-annotation / technical-drawing linework
       (.mech-dim, the caliper) — that is draughting, not decor
     · the explorer hot-dot (a dot)
   ============================================================ */
/* ==========================================================================
   HOME — Shaheen Arabia — enterprise direction (v5)
   Light canvas dominant. Brand-green accent, sparing. Two charcoal emphasis moments
   (Why-Shaheen + Close). Rules, not boxes. Restrained reveal, no scroll-jacking.
   Front-page-only: the .eyebrow rule device here does not affect inner pages.
   Tokens + base type come from app.css.
   ========================================================================== */

/* eyebrow: clean tracked caps in brand green — NO leading rule/dash (retired 2026-07-28).
   The base .eyebrow treatment in app.css is the single sitewide definition. */

/* ==========================================================================
   VERTICAL RHYTHM — homepage only (2026-07-31)
   The client reported large blank gaps between sections. Two sections at
   padding-block:var(--sp) put ~190px of air between them at 1352px, which reads
   as sections floating apart rather than flowing.

   The scale is TIGHTENED, not replaced, and it is scoped to body.home so the 26
   inner pages stay geometrically unchanged — --sp was deliberately tuned to the
   value they already computed, and that must not move. Still exactly three
   steps; no fourth was invented.

     --sp      104 -> 78px at cap   (section gap 208 -> 156)
     --sp-sm    72 -> 54px
     --sp-lg   144 -> 108px
   ========================================================================== */
/* ⚠ 2026-08-17 — this override now carries the SAME values as :root in app.css.
   It is kept rather than deleted because home.css is enqueued AFTER app.css, so
   if it were left at the old numbers it would silently beat the retuned tokens
   on the homepage and nowhere else — the exact cascade failure recorded five
   times in CLAUDE.md. Keeping it in sync is the safe form; if it is ever
   removed, remove it here and re-measure the homepage, do not assume. */
body.home{
  --sp-sm:clamp(17px,1.55vw,23px);
  --sp:clamp(21px,1.95vw,28px);
  --sp-lg:clamp(28px,2.6vw,38px);
}
.hero-f-vid{opacity:0;transition:opacity .8s ease}
.hero-f-vid.playing{opacity:1}
/* The scrim has to EARN the contrast, so it was measured against the lightest
   composited pixel behind each text block (the molten pour is bright). At the
   first pass the amber <em> came out at 2.89:1 — under the 3:1 large-text
   minimum — so the horizontal wash was deepened until it cleared. Final
   measured values are recorded in the deploy notes; re-measure if the clip is
   ever swapped, because the numbers belong to THIS footage. */
.hero-f-scrim{position:absolute;inset:0;
  background:
    linear-gradient(180deg,rgba(8,9,10,.76) 0%,rgba(8,9,10,.50) 34%,rgba(8,9,10,.90) 100%),
    linear-gradient(90deg,rgba(8,9,10,.92) 0%,rgba(8,9,10,.70) 48%,rgba(8,9,10,.34) 100%)}
/* A · the button row. This lost its display:flex in an earlier cleanup, so the
   gap was inert and the two CTAs sat 5px apart on raw whitespace. */
.hero-e-cta{display:flex;flex-wrap:wrap;align-items:center;gap:18px 32px}

/* proof strip, inside the dark scene */
.trust-f{position:relative;z-index:2;border-top:1px solid rgba(255,255,255,.14);
  background:rgba(8,9,10,.55);backdrop-filter:saturate(140%) blur(6px)}

/* ==========================================================================
   THE DIMENSION ANNOTATION — "the caliper reading"
   RESTORED 2026-07-29. This whole block was destroyed when the hero was
   replaced: the replacement spanned from the old hero marker to "trust strip",
   and the annotation CSS sat between them. The markup survived, so the section
   rendered as a stretched 1114px-tall image with an unpositioned overlay.
   It now lives with the proof figures, where it belongs.
   ========================================================================== */
.dimshot{position:relative;display:block;width:100%}
/* the <img> carries width/height attributes to reserve layout (CLS). Without a
   CSS height the height attribute won and stretched the photo to 436x1114. */
.dimshot img{display:block;width:100%;height:auto}

/* 2026-08-05 — the callout is NO LONGER AN OVERLAY at any width.
   It was absolutely positioned at the bottom-right of .dimshot, which put it
   squarely over the housing being measured: it hid the dimension line, the
   readout and the right anvil, and the anvil travels FURTHER right as the
   tolerance slider opens, so the one thing the widget exists to show was the
   one thing covered. The ≤720px rule already dropped it below the photograph;
   that is now the behaviour everywhere, and the photograph is never occluded.
   Content is unchanged — only where it sits. */
.hero-e-tag{position:relative;z-index:4;background:var(--white);
  border:1px solid var(--hair-l);border-left:0;padding:10px 14px;margin-top:12px}
.hero-e-tag::before{display:none}
.hero-e-tag b{display:block;font-family:var(--f-dsp);font-weight:600;font-size:.92rem;letter-spacing:-.01em;color:var(--tx-l)}
.hero-e-tag span{display:block;font-size:.76rem;color:var(--tx-l-dim);line-height:1.45;margin-top:2px}
.js .hero-e-tag b,.js .hero-e-tag span{opacity:0;transform:translateY(6px)}
.hero-e-tag.on::before{transform:scaleY(1);transition:transform .55s var(--ease)}
.hero-e-tag.on b,.hero-e-tag.on span{opacity:1;transform:none;
  transition:opacity .5s ease,transform .5s var(--ease)}
.hero-e-tag.on b{transition-delay:.18s}
.hero-e-tag.on span{transition-delay:.26s}

.dim{position:absolute;left:0;top:0;width:100%;height:100%;z-index:3;pointer-events:none;
  overflow:visible;opacity:.92;transition:opacity .3s ease}
.dimshot.hot .dim{opacity:1}
.dim line{stroke:var(--brand-graphic);stroke-width:2.5;vector-effect:non-scaling-stroke}
.dim path{fill:var(--brand-graphic)}
.dim text{fill:var(--brand-ink);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:26px;letter-spacing:2.6px;text-anchor:middle;paint-order:stroke;
  stroke:rgba(255,255,255,.85);stroke-width:5px}
.dim g{transform-box:fill-box}
.js .dim .dim-ext{transform:scaleY(0);transform-origin:center}
.js .dim .dim-line{transform:scaleX(0);transform-origin:left center}
.js .dim .dim-arrow,.js .dim .dim-lbl{opacity:0}
.js .dim .dim-lbl{transform:translateY(6px)}
.dim.on .dim-ext{transform:scaleY(1);transition:transform .5s var(--ease)}
.dim.on .dim-line{transform:scaleX(1);transition:transform .7s var(--ease) .16s}
.dim.on .dim-arrow{opacity:1;transition:opacity .3s ease .78s}
.dim.on .dim-lbl{opacity:1;transform:none;transition:opacity .45s ease .62s,transform .45s var(--ease) .62s}
@media (max-width:640px){ .dim text{font-size:30px} }
@media (max-width:720px){ .hero-e-tag{margin-top:14px} }
@media (prefers-reduced-motion:reduce){
  .hero-e-tag::before,.dim .dim-ext,.dim .dim-line{transform:none!important}
  .hero-e-tag b,.hero-e-tag span,.dim .dim-arrow,.dim .dim-lbl{opacity:1!important;transform:none!important}
  .hero-e-tag::before,.hero-e-tag b,.hero-e-tag span,.dim g{transition:none!important}
}
.intro-e-head h2{margin-top:16px;max-width:16ch}
.intro-e-body{display:flex;flex-direction:column;gap:18px}
.intro-e-body p{color:var(--tx-l-dim);max-width:62ch}

/* ---------------- 3 · CAPABILITIES — scroll-progress sequence (sticky media + scrolling text) ---------------- */
.caps-seq{padding-block:var(--sp)}
.caps-seq-head{margin-bottom:clamp(32px,4vw,52px)}
.caps-seq-head h2{margin-top:16px;max-width:20ch}
.caps-seq-head .link-ar{margin-top:18px}
.caps-seq-grid{display:grid;grid-template-columns:1.18fr .82fr;gap:clamp(40px,6vw,96px);align-items:start}

/* sticky media: shows the active capability's photograph, crossfading */
.caps-seq-media{position:sticky;top:calc(var(--hdr-h) + 20px)}
.capseq-frame{position:relative;width:100%;aspect-ratio:4/5;max-height:84vh;border-radius:var(--r-lg);
  overflow:hidden;background:radial-gradient(70% 70% at 50% 45%,#20242b 0%,#0f1114 78%)}
.capseq-frame::after{display:none}
/* cross-dissolve with a slight scale-settle: the incoming photograph comes to
   rest rather than cutting in. Transform/opacity only. */
.cslide{position:absolute;inset:0;opacity:0;transform:scale(1.035);
  transition:opacity .55s ease,transform .8s var(--ease)}
.cslide.on{opacity:1;transform:scale(1)}
.cslide img{width:100%;height:100%;display:block}
.cslide.fit-cover img{object-fit:cover}
.cslide.fit-contain img{object-fit:contain;padding:clamp(22px,3.5vw,52px)}
.capseq-cap{position:absolute;left:16px;right:16px;bottom:14px;z-index:4;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.68rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--tx-d-dim);display:flex;align-items:center;gap:9px;
  opacity:0;transition:opacity .4s ease}
.capseq-cap.on{opacity:1}
.capseq-cap::before{content:"";width:6px;height:6px;background:var(--brand-lift);flex:none}

/* scrolling text column with a progress rail */
.caps-seq-text{position:relative;padding-left:40px}
.caps-seq-rail{position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--hair-l)}
/* the fill is driven by real scroll position (app.js), not a timed animation */
.caps-seq-rail i{position:absolute;left:0;top:0;width:2px;height:0;
  background:linear-gradient(180deg,var(--brand-soft) 0%,var(--brand-graphic) 55%,var(--brand-ink) 100%)}
.capseq{min-height:62vh;display:flex;flex-direction:column;justify-content:center;padding:24px 0}
.js .capseq{opacity:.38;transition:opacity .45s ease}
.js .capseq.active{opacity:1}
.capseq-k{display:inline-block;font-family:var(--f-body);font-weight:600;
  font-size:.72rem;letter-spacing:.15em;text-transform:uppercase;color:var(--brand-ink)}
.capseq h3{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.02em;color:var(--tx-l);
  font-size:clamp(1.65rem,2.6vw,2.3rem);margin-top:14px;max-width:16ch}
.capseq-prop{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.015em;color:var(--brand-ink);
  font-size:clamp(1.35rem,2.3vw,2.05rem);line-height:1.1;margin-top:14px;max-width:18ch}
/* the proposition resolves as its capability becomes the active one */
.js .caps-seq-media ~ .caps-seq-text .capseq .capseq-prop{opacity:0;transform:translateY(8px)}
.js .caps-seq-media ~ .caps-seq-text .capseq.active .capseq-prop{opacity:1;transform:none;
  transition:opacity .55s ease .1s,transform .55s var(--ease) .1s}
.capseq-desc{margin-top:14px;color:var(--tx-l-dim);font-size:1rem;line-height:1.6;max-width:44ch}
.capseq-lnk{margin-top:18px;display:inline-flex;align-items:center;gap:9px;font-size:.9rem;font-weight:600;color:var(--tx-l)}
.capseq-lnk svg{width:14px;height:14px;transition:transform .25s ease}
.capseq-lnk:hover{color:var(--brand-ink)}
.capseq-lnk:hover svg{transform:translateX(4px)}
/* per-block inline media for mobile (sticky media hidden < 960px) */
.capseq-inline{position:relative;aspect-ratio:16/11;border-radius:var(--r);overflow:hidden;
  background:radial-gradient(70% 70% at 50% 45%,#20242b 0%,#0f1114 78%)}
.capseq-inline::after{display:none}
.capseq-inline img{width:100%;height:100%}
.capseq-inline.fit-cover img{object-fit:cover}
.capseq-inline.fit-contain img{object-fit:contain;padding:22px}

/* ---------------- 4 · WHY SHAHEEN (charcoal) + bearing signature ---------------- */
.why-e{background:var(--ink);color:var(--tx-d)}
.bearing-poster,.bearing canvas{grid-area:1/1;width:100%;height:100%;object-fit:contain}
.bearing canvas{opacity:0;transition:opacity .4s ease}
.bearing.ready canvas{opacity:1}
.bearing.ready .bearing-poster{opacity:0}
.bearing-cap{position:absolute;left:16px;bottom:16px;display:flex;align-items:center;gap:9px;
  font-family:var(--f-body);font-size:.76rem;letter-spacing:.02em;color:var(--tx-d-dim)}
.bearing-cap .dot{width:7px;height:7px;border-radius:50%;background:var(--brand-graphic);flex:none;box-shadow:0 0 0 4px rgba(4,105,56,.18)}
.why-e-body h2{margin-top:18px;max-width:18ch}
.why-e-body .lead{margin-top:20px;color:var(--tx-d-dim);max-width:52ch}
.on-dark-ghost{color:var(--tx-d)}

/* ---------------- 5b · CREDENTIALS WALL ----------------
   Was a 303px band holding 130px of content (57% dead). Now fitted: the small
   step of the rhythm scale, and the head sits inline with the marquee. */
.creds-e{padding-block:var(--sp-sm)}
.creds-e-head{text-align:center;max-width:64ch;margin:0 auto clamp(20px,2.2vw,28px)}
.creds-e-head p{margin-top:10px;color:var(--tx-l-dim);margin-inline:auto}

/* ---------------- 6 · INDUSTRIES (image-led tiles, title only) ---------------- */
.inds-e{padding-block:var(--sp)}
.inds-e-head{max-width:62ch}
.inds-e-head h2{margin-top:16px;max-width:20ch}
.inds-e-head .lead{margin-top:16px}
/* Asymmetric: one lead tile carries the section, the rest support it — rather
   than eight identical 3:4 rectangles reading as a contact sheet. */
.inds-tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  grid-auto-rows:1fr;gap:clamp(12px,1.4vw,18px);margin-top:clamp(32px,4vw,52px)}
.inds-tile:first-child{grid-column:span 2;grid-row:span 2}
.inds-tile:first-child h3{font-size:clamp(1.3rem,1.9vw,1.8rem);left:24px;right:24px;bottom:22px}
/* 4:3 matches the supplied sector artwork exactly, so nothing is cropped away.
   The lead tile spans 2x2 and stays 4:3, just twice the size. */
.inds-tile{position:relative;aspect-ratio:4/3;border-radius:var(--r);overflow:hidden;background:#0f1114;display:block;color:#fff}
.inds-tile:first-child{aspect-ratio:auto}
.inds-tile img{width:100%;height:100%;object-fit:cover;opacity:.88;transition:transform .5s ease,opacity .3s ease}
/* .inds-tile's bespoke 180deg scrim is RETIRED — the shared --tile-grad device
   in app.css now provides the only overlay on every photographic tile. */
.inds-tile:hover img{transform:scale(1.05);opacity:1}
.inds-tile h3{position:absolute;left:18px;right:18px;bottom:16px;z-index:2;font-family:var(--f-dsp);font-weight:600;
  letter-spacing:-.01em;font-size:clamp(1.05rem,1.3vw,1.28rem);line-height:1.15;color:#fff}

/* 9 · CLOSE — the homepage now uses the shared .cta-panel (pages.css), so the
   closing moment is identical on all 15 pages. The old bespoke .close-e
   centred-44ch band is retired. */

/* ---------------- responsive ---------------- */
@media (max-width:960px){
  /* capability sequence stacks: media un-sticks and leads each block instead */
  .caps-seq-grid{grid-template-columns:1fr;gap:0}
  .caps-seq-media{display:none}          /* per-block inline media shown instead (see PHP .capseq-inline) */
  .caps-seq-text{padding-left:32px}
  .capseq{min-height:0;padding:34px 0}
  .js .capseq{opacity:1}                  /* no dim when stacked */
  /* stacked: every block is read in full, so every proposition is shown */
  .js .caps-seq-media ~ .caps-seq-text .capseq .capseq-prop{opacity:1;transform:none}
  .capseq-inline{display:block;margin-bottom:22px}
  .inds-tiles{grid-template-columns:repeat(2,minmax(0,1fr))}
  .inds-tile:first-child{grid-column:span 2;grid-row:span 1;aspect-ratio:16/10}
}
@media (min-width:961px){ .capseq-inline{display:none} }
@media (max-width:720px){
  .trust-f-in span:last-child{border-bottom:0}
}
@media (max-width:560px){
  .inds-tiles{grid-template-columns:1fr 1fr;gap:10px}
}

/* ---------------- bold pass: responsive ---------------- */
@media (max-width:1080px){
  .trust-f-in span:nth-child(4)::before,.trust-f-in span:nth-child(4){border-top:1px solid rgba(255,255,255,.12)}
  .trust-f-in span:nth-child(5){border-top:1px solid rgba(255,255,255,.12)}
  .trust-f-in span:nth-child(4)::before{display:none}
}
@media (max-width:960px){
  /* the loop is stripped to its poster below this width (app.js) */
  .hero-f-vid{display:none}
}
@media (max-width:560px){
  .ph-tag{right:8px;bottom:8px;font-size:10.5px;letter-spacing:.1em}
}
@media (prefers-reduced-motion:reduce){
  .hero-f-vid{display:none}          /* static poster hero, always */
  .hero-f-poster{opacity:1!important}
}

/* ==========================================================================
   3 · CAPABILITIES — INTERACTIVE COMPONENT EXPLORER (2026-07-30)
   Replaces the sticky scroll sequence. Bearings is the hero and the default,
   because the approved positioning is "Saudi Arabia's only white metal bearing
   manufacturer" — that dominance is expressed in weight, not just order.

   PROGRESSIVE ENHANCEMENT: everything below is fully visible and readable with
   NO JavaScript. The `.js` gate is what collapses the panels and callouts into
   the interactive explorer; the head failsafe strips `.js` if app.js never runs.
   ========================================================================== */
.exp{padding-block:var(--sp)}
.exp-head{max-width:60ch;margin-bottom:clamp(26px,3vw,44px)}
.exp-head h2{margin-top:10px}
.exp-head .lead{margin-top:14px}

/* ---- capability selector ---- */
.exp-nav{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-bottom:clamp(22px,2.4vw,34px)}
@media (min-width:900px){.exp-nav{grid-template-columns:1.6fr 1fr 1fr 1fr}}
.exp-cap{display:flex;flex-direction:column;gap:6px;text-align:start;padding:16px 18px;
  border:1px solid var(--hair-l);border-radius:var(--r);background:var(--white);
  transition:border-color .3s var(--ease),background .3s var(--ease),transform .3s var(--ease)}
.exp-cap:hover{border-color:var(--brand-graphic);transform:translateY(-2px)}
.exp-cap .exp-cap-t{font-family:var(--f-dsp);font-weight:600;font-size:.98rem;line-height:1.2;color:var(--tx-l)}
.exp-cap .exp-cap-p{font-size:.82rem;color:var(--tx-l-dim);line-height:1.35}
/* bearings: the hero. Larger type, brand edge, tinted plate. */
.exp-cap.is-hero{padding:20px 22px;background:var(--tint);border-color:transparent;position:relative;overflow:hidden}
.exp-cap.is-hero::before{display:none}
.exp-cap.is-hero .exp-cap-t{font-size:1.12rem}
.exp-cap.on{border-color:var(--brand-graphic);background:var(--white);box-shadow:var(--shadow)}
.exp-cap.is-hero.on{background:var(--tint-2)}

/* ---- stage ---- */
.exp-panel{display:grid;gap:clamp(22px,3vw,42px)}
@media (min-width:1000px){.exp-panel[data-panel="bearings"]{grid-template-columns:1.45fr 1fr;align-items:start}}

/* the diagram. aspect-ratio reserves the box, so switching panels never shifts layout. */
.exp-figure{min-width:0}
.exp-svgwrap{position:relative;aspect-ratio:1000/460;width:100%;
  background:var(--tint);border-radius:var(--r);overflow:hidden}
.rotor{position:absolute;inset:0;width:100%;height:100%;color:var(--tx-l)}
.rotor .rs-ink,.rotor .rs-cmp .rs-ink{fill:none;stroke:currentColor;stroke-width:1.6;opacity:.72}
.rotor .rs-thin{stroke-width:1;opacity:.55}
.rotor .rs-shaft{opacity:.85}
.rotor .rs-blades path{opacity:.6}
.rotor .rs-cl{stroke:currentColor;stroke-width:1;opacity:.35;stroke-dasharray:14 5 3 5}
.rotor .rs-dim{fill:none;stroke:currentColor;stroke-width:1;opacity:.3}
.rotor .rs-grid{color:var(--tx-l)}
/* the highlight: invisible until its component is active */
.rotor .rs-hl{fill:none;stroke:var(--brand-graphic);stroke-width:3;opacity:0;
  stroke-linejoin:round;stroke-dasharray:1000;stroke-dashoffset:1000;
  transition:opacity .3s var(--ease),stroke-dashoffset .55s var(--ease)}
.rotor .rs-cmp.on .rs-hl{opacity:1;stroke-dashoffset:0}
.rotor .rs-cmp.on .rs-ink{opacity:.95}

/* ---- hotspots: real buttons over the diagram ---- */
.exp-hot{position:absolute;transform:translate(-50%,-50%);width:44px;height:44px;
  display:grid;place-items:center;border-radius:50%;background:none;z-index:3}
.exp-hot-dot{width:13px;height:13px;border-radius:50%;background:var(--brand-graphic);
  box-shadow:0 0 0 4px rgba(4,105,56,.16);transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
.exp-hot-dot::after{content:"";position:absolute;left:50%;top:50%;width:13px;height:13px;margin:-6.5px 0 0 -6.5px;
  border-radius:50%;border:1.5px solid var(--brand-graphic);opacity:.7;
  animation:expPulse 2.8s var(--ease) infinite}
.exp-hot:hover .exp-hot-dot,.exp-hot.on .exp-hot-dot{transform:scale(1.25);box-shadow:0 0 0 7px rgba(4,105,56,.2)}
.exp-hot:focus-visible{outline:2px solid var(--brand-graphic);outline-offset:2px;border-radius:50%}
@keyframes expPulse{0%{transform:scale(1);opacity:.7}70%{transform:scale(2.6);opacity:0}100%{transform:scale(2.6);opacity:0}}

/* ---- callouts ---- */
.exp-callouts{min-width:0;position:relative;padding-inline-start:22px}
.exp-callouts::before{content:"";position:absolute;left:0;top:4px;bottom:4px;width:2px;background:var(--hair-l)}
.exp-cal h3{font-size:1.15rem;margin-bottom:8px}
.exp-cal p{color:var(--tx-l-dim);font-size:.95rem}
.exp-callouts .link-ar{margin-top:18px;display:inline-flex}
/* with JS: one callout at a time, in a reserved box so nothing jumps */
.js .exp-callouts{min-height:11.5rem}
.js .exp-cal{display:none}
.js .exp-cal.on{display:block;animation:expIn .34s var(--ease) both}
@keyframes expIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* without JS every callout is simply a stacked list */
.exp-cal + .exp-cal{margin-top:18px}

/* ---- the three non-bearing capabilities ---- */
.exp-alt{display:grid;gap:clamp(18px,2.4vw,32px)}
@media (min-width:900px){.exp-alt{grid-template-columns:1.1fr 1fr;align-items:start}}
.exp-prop{font-family:var(--f-dsp);font-weight:600;font-size:clamp(1.3rem,2vw,1.75rem);
  line-height:1.15;letter-spacing:-.02em;color:var(--tx-l);margin-bottom:14px}
.exp-alt-body p{color:var(--tx-l-dim)}
.exp-alt .link-ar{margin-top:18px;display:inline-flex}
.exp-items{display:grid;gap:0}
.exp-items li{padding:11px 0;border-bottom:1px solid var(--hair-l);font-size:.92rem;color:var(--tx-l)}
.exp-items li:last-child{border-bottom:0}

/* with JS only the selected panel shows; without JS all four stack and are readable */
.js .exp-panel{display:none}
.js .exp-panel.on{display:grid}
.exp-panel + .exp-panel{margin-top:clamp(30px,4vw,56px)}
.js .exp-panel + .exp-panel{margin-top:0}

/* ---- mobile: MEASURED fallback, not a guess ----
   At 390px the diagram box is 350x161. Six 44px tap targets on that width
   overlap in 4 pairs — measured, not estimated — so the hotspot layer is
   genuinely unusable. Per the spec's own instruction (legibility beats
   cleverness) the markers are dropped below 640px and the schematic becomes a
   static labelled illustration, with the components presented as a readable,
   tappable list underneath. Nothing is lost: the list carries every component
   name, its line of copy and the link. */
@media (max-width:640px){
  .exp-svgwrap{aspect-ratio:1000/460}
  .exp-hot{display:none}                 /* display:none also removes them from the tab order */
  .exp-callouts{padding-inline-start:0;margin-top:16px}
  .exp-callouts::before{display:none}
  .js .exp-callouts{min-height:0}
  .js .exp-cal{display:block}            /* every component listed, as in the no-JS state */
  .js .exp-cal.on{animation:none}
  .exp-cal{padding:14px 16px;background:var(--tint);border-radius:var(--r)}
  .exp-cal + .exp-cal{margin-top:10px}
  .exp-cal h3{font-size:1.02rem}
  .exp-cal p{font-size:.9rem}
  .exp-cap .exp-cap-t{font-size:.92rem}
  .exp-cap.is-hero .exp-cap-t{font-size:1rem}
}

@media (prefers-reduced-motion:reduce){
  .exp-hot-dot::after{animation:none;opacity:0}
  .rotor .rs-hl{transition:none;stroke-dasharray:none;stroke-dashoffset:0}
  .js .exp-cal.on{animation:none}
  .exp-cap{transition:none}
  .exp-cap:hover{transform:none}
}

/* ==========================================================================
   6 · INDUSTRIES — horizontal carousel (batch §2, 2026-07-30)
   Three states, in increasing order of capability:
     1. no-JS / reduced-motion / narrow  -> the existing GRID, untouched.
     2. touch / <1000px                  -> NATIVE swipeable horizontal scroll.
                                            No pin, no JS, cannot trap the page.
     3. desktop + motion allowed         -> .is-hcar, pinned scrub (JS-added).
   The grid is the base state, so anything that fails leaves a working section.
   ========================================================================== */

/* --- native horizontal scroll at EVERY width (pin removed 2026-07-30) ---
   Abhay's call: the gear mechanism is the homepage's one pinned moment. Two pins
   back to back (4.75 viewports) read as scroll-grabbing; one reads as intentional.
   This is the fallback that was already built and verified, promoted to the only
   state. It is user-driven, so it needs no reduced-motion special case. --- */
@media (min-width:0px){
  .inds-tiles{display:flex;grid-template-columns:none;overflow-x:auto;overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
    gap:12px;padding-bottom:10px;scrollbar-width:thin}
  .inds-tile,.inds-tile:first-child{flex:0 0 clamp(260px,30vw,360px);scroll-snap-align:start;aspect-ratio:4/3;
    grid-column:auto;grid-row:auto}
  .inds-tile:first-child h3{font-size:clamp(1.05rem,1.3vw,1.28rem);left:18px;right:18px;bottom:16px}
}
@media (max-width:999px){ .inds-tile,.inds-tile:first-child{flex:0 0 78%} }
@media (max-width:520px){ .inds-tile,.inds-tile:first-child{flex:0 0 86%} }

/* state 3 (desktop pinned scrub) REMOVED 2026-07-30 — see note above. */

/* (reduced-motion pin guard removed with the pin itself) */

/* =========================================================================
   3 · CAPABILITIES — THE MECHANISM (2026-07-30)
   A precision gear bleeds off the right edge; scroll turns it, and each detent
   swaps the capability on the left. Replaces the component explorer.

   Base state (no JS / reduced motion / mobile) is FOUR STACKED BLOCKS. The `.js`
   gate is the only thing that collapses them into the pinned mechanism, so a dead
   app.js leaves a complete, readable section.
   ========================================================================== */
.mech{position:relative;overflow:hidden;padding-block:var(--sp)}
.mech-in{position:relative;z-index:1;display:grid;gap:clamp(24px,3vw,40px)}
/* Technical-drawing underlay filling the restored stage floor. Hairline, brand
   green, aria-hidden, pointer-events:none — decoration, never content. Masked so
   it fades out before it reaches the gear, which is the section's actual figure
   and must not compete with a background. Hidden below 1000px, where the stage
   stacks and there is no empty floor to fill. */
.mech-stage{position:relative}
/* Both decorations are desktop-only: below 1000px the stage stacks, the corridor
   does not exist and there is no floor to fill. Hidden by default and switched on
   inside the desktop query, so neither can ever pick up stray styling on mobile. */
.mech-field,.mech-dim{display:none}
@media (min-width:1000px){
  .js .mech-field{display:block;position:absolute;inset:0;z-index:0;pointer-events:none;
    overflow:hidden;color:var(--brand-graphic);opacity:.18;
    -webkit-mask-image:linear-gradient(100deg,#000 0%,#000 34%,transparent 62%);
    mask-image:linear-gradient(100deg,#000 0%,#000 34%,transparent 62%)}
  .js .mech-field .la-field{position:absolute;left:-16%;top:-30%;width:104%;height:160%}
  .js .mech-field .la-field circle,
  .js .mech-field .la-field path{fill:none;stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke}
  .js .mech-field .la-field .la-fine{stroke-width:.6;opacity:.7}

  /* CORRIDOR RULE — dimensions the card the way the gear dimensions itself, so
     the 157px gutter between them becomes part of one drawing instead of white
     space. Extension ticks at the card's top and bottom edges, a dashed rule
     running between them, terminator dots at each end. Geometry only.
     Anchored to the deck box, so it tracks the card at any width without a
     hard-coded height. Absolutely positioned => takes no grid area, so the
     stacked cards and their cross-fade are untouched. */
  .js .mech-deck{position:relative}
  .js .mech-dim{display:block;position:absolute;left:100%;top:0;bottom:0;width:78px;
    margin-left:38px;pointer-events:none;z-index:0}
  /* the dashed vertical rule, centred in the corridor */
  .js .mech-dim::before{content:"";position:absolute;left:50%;top:0;bottom:0;width:1px;
    transform:translateX(-.5px);
    background:repeating-linear-gradient(to bottom,
      var(--brand-graphic) 0 7px, transparent 7px 13px);
    opacity:.42}
  /* the two extension ticks, drawn as one element with a top and bottom edge */
  .js .mech-dim::after{content:"";position:absolute;left:0;right:0;top:0;bottom:0;
    border-top:1px solid var(--brand-graphic);
    border-bottom:1px solid var(--brand-graphic);
    opacity:.34}
}
@media (prefers-reduced-motion:reduce){.js .mech-field{opacity:.12}}
.mech-head h2{margin-top:10px}
.mech-copy{min-width:0}

/* --- the gear --- */
.mech-media{position:relative;pointer-events:none;display:none}
.mech-gear{width:100%;height:auto;display:block;color:var(--tx-l);overflow:visible}
.mech-gear-body{transform-box:fill-box;transform-origin:50% 50%}
/* line weights follow the mandate: hairline, uniform, no fill, no shading */
.mech-gear .g-rim{fill:none;stroke:currentColor;stroke-width:2.2;opacity:.55;stroke-linejoin:round}
.mech-gear .g-ring{fill:none;stroke:currentColor;stroke-width:1.6;opacity:.42}
.mech-gear .g-thin{fill:none;stroke:currentColor;stroke-width:1;opacity:.26}
.mech-gear .g-spokes{fill:none;stroke:currentColor;stroke-width:.9;opacity:.20}
.mech-gear .g-hatch{fill:none;stroke:currentColor;stroke-width:.8;opacity:.16}
.mech-gear .g-mark{fill:none;stroke:var(--brand-graphic);stroke-width:1.4;opacity:.85}
.mech-gear .g-dim{fill:none;stroke:var(--brand-graphic);stroke-width:1;opacity:.5;stroke-dasharray:9 4 2 4}
.mech-gear .g-dim-dot{fill:var(--brand-graphic);opacity:.9}
/* the fixed dial pointer that marks the active detent */
.mech-notch{position:absolute;z-index:2;left:0;top:50%;width:54px;height:2px;
  background:var(--brand-graphic);transform:translateY(-50%)}
.mech-notch::after{content:"";position:absolute;right:-1px;top:-5px;
  border:6px solid transparent;border-right-color:var(--brand-graphic)}
@media (min-width:1000px){
  /* The copy column must win: at 1fr/0.92fr the text measured 233px, which is
     unreadable. The gear still bleeds because it is 150% of its own column. */
  .js .mech-in{grid-template-columns:minmax(0,1.22fr) minmax(0,0.78fr);align-items:center}
  .js .mech-media{display:block;margin-inline-end:calc(var(--gutter) * -1);
    /* bleed ~45% off the right edge: only the left half of the machine is seen */
    width:150%;max-width:none;transform:translateX(18%)}
}

/* ====== MOBILE / narrow: never pin. Gear is a section motif; blocks stack. ====== */
@media (max-width:999px){
  .mech-media{display:block;position:absolute;right:-38%;top:-6%;width:86%;opacity:.5;z-index:0}
  .mech-notch{display:none}
  .mech-copy{position:relative;z-index:1}
  .js .mech-dial{display:none}          /* stepping is by scroll, not by control */
}

@media (prefers-reduced-motion:reduce){
  .mech-gear-body{transform:none!important}
}
/* the em already carries the brand colour; the rule underneath is what resolves */
.hero-f-in h1 em{position:relative}

/* ---- 3 · CAPABILITIES — the left content side ---------------------------
   The image was a 250px stamp; it is now a large portrait frame that anchors the
   column. Sized by AVAILABLE HEIGHT, because this section pins: a width-driven
   image overflows a pinned viewport at short window heights. */

.mech-rail{position:absolute;left:0;right:0;top:50%;height:1px;background:var(--hair-l);transform:translateY(-50%)}

/* ---- 4 · BACKED BY FALCON GROUP ----------------------------------------- */
.why-e{padding-block:var(--sp)}
.why-e-top{display:grid;grid-template-columns:minmax(0,1.12fr) minmax(0,.88fr);
  gap:clamp(32px,5vw,76px);align-items:center}
.why-e-body h2{margin-top:16px;max-width:18ch}
.why-e-body .lead{margin-top:18px;color:var(--tx-d-dim);max-width:50ch}
.why-e-body .cta{margin-top:26px}
/* six points as an icon band, not a six-row bullet list */
.why-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(22px,2.6vw,38px) clamp(24px,3vw,48px);margin-top:clamp(38px,4.6vw,64px)}
.why-grid li{padding-top:20px;border-top:1px solid var(--hair-d)}
.why-ic{display:block;width:40px;height:40px;margin-bottom:14px}
.why-ic .wi-fine{stroke-width:.9;opacity:.55}
.why-ic .wi-mark{stroke:var(--brand-lift);stroke-width:1.5}
.why-grid b{display:block;font-family:var(--f-dsp);font-weight:600;font-size:1.02rem;
  letter-spacing:-.012em;color:var(--tx-d);margin-bottom:6px}
.why-grid .why-t{display:block;color:var(--tx-d-dim);font-size:.92rem;line-height:1.55}

/* ---- 5 · THE CLAIM — "ONLY" -------------------------------------------- */
.only-e{padding-block:var(--sp)}
.only-panel > *:not(.la-plate){position:relative;z-index:1}

/* ---- 5b · ONE ROOF — statement + the six figures, one band --------------- */
.roof-f{position:relative;overflow:hidden;isolation:isolate;background:var(--ink)}
.roof-f-media{position:absolute;inset:0;z-index:0}
.roof-f-media img{width:100%;height:100%;object-fit:cover}
/* deepened at the foot because the figures now sit there — measured, see report */
.roof-f-scrim{position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(8,9,10,.66) 0%,rgba(8,9,10,.52) 34%,rgba(8,9,10,.90) 100%),
             linear-gradient(90deg,rgba(8,9,10,.72) 0%,rgba(8,9,10,.40) 62%,rgba(8,9,10,.24) 100%)}
/* ONE grid, one treatment, hairline rules, tabular numerals — the fix for
   "scattered". Row 1 is the proof tier and stays larger; row 2 is context. */
.roof-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:0 clamp(20px,2.8vw,46px);margin-top:clamp(38px,4.4vw,62px)}
.rstat .num .accent{color:var(--brand-lift);font-size:.52em;margin-left:3px}

/* ---- 6 · INDUSTRIES ------------------------------------------------------ */
/* heading and lead share the row, so the intro no longer strands half a row */
.inds-e-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(20px,3vw,56px);align-items:end;max-width:none}
.inds-e-k h2{margin-top:14px;max-width:18ch}
.inds-e-v{display:flex;flex-direction:column;gap:20px;align-items:flex-start}
.inds-e-v .lead{margin-top:0;max-width:52ch}
/* carousel affordance: real controls, plus a rail that shows how far along you are */
.inds-nav{display:flex;gap:10px}
.inds-nav button{width:46px;height:46px;display:grid;place-items:center;border-radius:50%;
  border:1px solid var(--hair-l);background:var(--white);color:var(--tx-l);
  transition:border-color .3s var(--ease),background .3s var(--ease),opacity .3s ease}
.inds-nav button:hover:not(:disabled){border-color:var(--brand);background:var(--tint)}
.inds-nav button:disabled{opacity:.3;cursor:default}
.inds-nav svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.inds-prog{position:relative;height:2px;background:var(--hair-l);margin-top:20px;overflow:hidden}
.inds-prog i{position:absolute;left:0;top:0;height:2px;width:22%;background:var(--brand);
  transform-origin:left center}
/* bigger, better-proportioned tiles with a considered hover */
.inds-tiles{scrollbar-width:none}
.inds-tiles::-webkit-scrollbar{display:none}
.inds-tile h3{left:22px;right:22px;bottom:20px;font-size:clamp(1.1rem,1.4vw,1.35rem);
  transform:translateY(0);transition:transform .45s var(--ease)}
.inds-tile img{transition:transform .7s var(--ease),opacity .4s ease}
.inds-tile:hover img{transform:scale(1.06)}
.inds-tile:hover h3{transform:translateY(-4px)}
.inds-tile:focus-visible{outline:2px solid var(--brand-graphic);outline-offset:3px}
.clients-panel > *:not(.la-plate):not(.clients-metal){position:relative;z-index:1}
/* brushed-metal sheen: a hairline repeating gradient, no asset, no request */
.clients-metal{position:absolute;inset:0;z-index:0;pointer-events:none;opacity:.5;
  background:
    repeating-linear-gradient(90deg,rgba(12,13,15,.035) 0 1px,transparent 1px 3px),
    linear-gradient(104deg,rgba(255,255,255,.85) 0%,rgba(12,13,15,.05) 42%,rgba(255,255,255,.75) 100%)}
.clients-head h2{margin-top:10px}

/* ---- responsive for everything above ------------------------------------ */
@media (max-width:1100px){
  .why-e-top{grid-template-columns:1fr;gap:34px}
}
@media (max-width:960px){
  .why-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .inds-e-head{grid-template-columns:1fr;gap:18px;align-items:start}
  .inds-nav{display:none}
}
@media (max-width:640px){
  .why-grid{grid-template-columns:1fr;gap:0}
  .why-grid li{padding:18px 0 0}
}

/* Reduced motion: every mechanism added above lands on its final state.
   Kept in one place, as the system requires. */
@media (prefers-reduced-motion:reduce){
  .mech-carriage,.mech-carriage i,.mech-rail-fill{transition:none!important}
  .inds-tile img,.inds-tile h3{transition:none!important}
  .inds-tile:hover img{transform:none}
}

/* The bearing sequence is 16:9 inside a 1:1 plate, so a third of the plate was
   empty. 5/4 fits the subject without cropping it. */
.bearing{position:relative;width:100%;aspect-ratio:5/4;max-height:620px;margin:0;
  display:grid;place-items:center;overflow:hidden;border-radius:var(--r-lg);
  background:radial-gradient(60% 60% at 50% 46%,#1a1d22 0%,var(--ink) 72%)}

/* the two lightest icons were losing their read at 40px */
.why-ic .wi{width:40px;height:40px;fill:none;stroke:currentColor;stroke-width:1.3;
  stroke-linecap:round;stroke-linejoin:round;color:var(--tx-d);opacity:.92;overflow:visible}

/* stacked: the media leads the section, as it did before the restructure */
@media (max-width:1100px){
  .bearing{max-height:520px;order:-1}
}
@media (max-width:640px){
  .bearing{max-height:400px;aspect-ratio:4/3}
}

/* ==========================================================================
   THE TOLERANCE STACK — draggable micrometer on the ONLY panel (2026-07-31)
   Reports geometry and the inspection standard. NEVER a measured number.
   The control is `hidden` in the markup and revealed by app.js, so a dead
   script leaves the approved static annotation and no orphan control.
   ========================================================================== */
.tol{margin-top:16px}
.tol-bar{display:flex;align-items:center;gap:12px}
.tol-k{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--tx-l-dim);flex:none}
/* the barrel: a knurled machined track, not a generic slider */
.tol-range{-webkit-appearance:none;appearance:none;flex:1;min-width:0;height:44px;
  background:none;cursor:ew-resize}
.tol-range::-webkit-slider-runnable-track{height:10px;border-radius:5px;
  background:repeating-linear-gradient(90deg,rgba(12,13,15,.16) 0 1px,transparent 1px 4px),
             linear-gradient(180deg,rgba(12,13,15,.10),rgba(12,13,15,.04));
  box-shadow:inset 0 0 0 1px rgba(12,13,15,.10)}
.tol-range::-moz-range-track{height:10px;border-radius:5px;
  background:repeating-linear-gradient(90deg,rgba(12,13,15,.16) 0 1px,transparent 1px 4px),
             linear-gradient(180deg,rgba(12,13,15,.10),rgba(12,13,15,.04));
  box-shadow:inset 0 0 0 1px rgba(12,13,15,.10)}
.tol-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:22px;height:26px;margin-top:-8px;border-radius:3px;background:var(--brand);
  box-shadow:0 2px 8px rgba(4,105,56,.3), inset 0 0 0 1px rgba(255,255,255,.35)}
.tol-range::-moz-range-thumb{width:22px;height:26px;border:0;border-radius:3px;
  background:var(--brand);box-shadow:0 2px 8px rgba(4,105,56,.3)}
.tol-range:focus-visible{outline:2px solid var(--brand-graphic);outline-offset:4px;border-radius:6px}
/* the readout: two mono lines, reserved height so nothing shifts as it changes */
.tol-read{margin-top:6px;min-height:2.6em;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.66rem;
  letter-spacing:.1em;text-transform:uppercase;line-height:1.35}
.tol-feat{display:block;color:var(--tx-l-dim)}
.tol-read.is-set .tol-feat{color:var(--brand-ink)}
/* while the anvils are off the contacts the annotation reads as provisional */
.dimshot.tol-open .dim line,.dimshot.tol-open .dim path{opacity:.42}
.dimshot.tol-open .dim text{opacity:.22}
.dimshot .dim line,.dimshot .dim path,.dimshot .dim text{transition:opacity .25s ease}
@media (max-width:820px){
  .tol-range{height:48px}          /* comfortable touch target when stacked */
  .tol-read{font-size:.7rem}
}
@media (prefers-reduced-motion:reduce){
  .dimshot .dim line,.dimshot .dim path,.dimshot .dim text{transition:none!important}
}

/* the anvil faces: solid, so the jaws read as jaws */
.dim .dim-anvil rect{fill:var(--brand-graphic);stroke:none}
.dimshot.tol-open .dim .dim-anvil rect{opacity:.55}
.dim .dim-anvil rect{transition:opacity .25s ease}
@media (prefers-reduced-motion:reduce){.dim .dim-anvil rect{transition:none!important}}

/* .tol-std carried opacity:.55 in the open state to read as secondary. Measured
   2.35:1 — an AA failure, and on the one line that actually tells the user what
   to do. De-emphasis by opacity is what broke it, so the hint now keeps a real
   colour in both states and the SET state is distinguished by the feature line
   turning brand green instead. Re-measured at 5.99:1. */
.tol-std{display:block;color:var(--tx-l-dim)}

/* The anvils were outside the entrance sequence, so they painted before the
   dimension line had drawn. They now resolve with it — and, like every other
   part of the annotation, they are listed in the reduced-motion block so the
   final state is reached with no transition rather than being left at zero. */
.js .dim .dim-anvil{opacity:0}
.dim.on .dim-anvil{opacity:1;transition:opacity .4s ease .34s}
@media (prefers-reduced-motion:reduce){
  .js .dim .dim-anvil,.dim .dim-anvil{opacity:1!important;transition:none!important}
}

/* ==========================================================================
   CAPABILITIES — THE CARD STACK (2026-07-31)
   Four cards in ONE grid cell, so the deck's height is simply the tallest card:
   nothing is absolutely positioned against a guessed height, and there is no
   fixed track for an image to outgrow. Cards move by transform only.

   Base state (no JS / reduced motion / narrow) is four ordinary stacked blocks.
   ========================================================================== */
.mech-deck{display:block}
.mcard + .mcard{margin-top:clamp(20px,2.4vw,32px)}
.mcard-shot::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:var(--tile-grad);border-radius:inherit}
.mcard-shot img{width:100%;height:100%;display:block}
.mcard-shot.fit-cover img{object-fit:cover}
.mcard-shot.fit-contain img{object-fit:contain;padding:clamp(14px,1.8vw,26px)}
.mcard-text{min-width:0}

/* ====== ENHANCED: the deck ====== */
@media (min-width:1000px){
  .js .mech-deck{display:grid;grid-template-areas:"deck";isolation:isolate}
  .js .mcard{grid-area:deck;margin-top:0;
    box-shadow:var(--shadow-lg);
    will-change:transform;backface-visibility:hidden;
    transform:translate3d(0,110%,0)}
  /* The card that is currently on top. Nothing here changes layout — only paint —
     so advancing the deck can never shift the page. */
  .js .mcard.on{transform:none}
}

/* ---- INDUSTRIES: expand-on-hover accordion (2026-07-31) -------------------
   Fine-pointer desktop only. Touch and narrow keep the native swipe strip, which
   is the fallback that was already built and verified — expand-on-hover has no
   meaning without a hover state, and tap-to-expand would fight the scroll.
   The row has a FIXED height and its own overflow, so nothing outside it can be
   moved by the accordion; measured for CLS rather than assumed. */
@media (hover:hover) and (pointer:fine) and (min-width:1000px){
  .inds-tiles{display:flex;overflow:hidden;scroll-snap-type:none;
    gap:10px;padding-bottom:0;height:clamp(340px,38vw,460px)}
  .inds-tile,.inds-tile:first-child{flex:1 1 0%;min-width:0;aspect-ratio:auto;height:100%;
    transition:flex-grow .55s var(--ease)}
  .inds-tiles:hover .inds-tile{flex-grow:.78}
  .inds-tiles .inds-tile:hover,
  .inds-tiles .inds-tile:focus-visible{flex-grow:2.6}
  /* the label stays legible while a tile is narrow: it rotates out of the way
     and returns to horizontal as the tile opens */
  .inds-tile h3{left:18px;right:18px;bottom:18px;font-size:clamp(1rem,1.15vw,1.2rem)}
  .inds-tile img{transition:transform .7s var(--ease),opacity .4s ease}
  .inds-tile:hover img{transform:scale(1.03)}
  /* the arrows are redundant once the row is an accordion */
  .inds-nav{display:none}
  .inds-prog{display:none}
}

/* the tool removed this block when its only two rules were replaced above —
   restoring the ONLY panel's stacked state */
@media (max-width:820px){
}

/* ==========================================================================
   CUSTOM CURSOR (2026-07-31) — an instrument, not a blob
   HARD RULES, all enforced in CSS as well as JS so a JS failure cannot strand
   the page without a pointer:
     · fine pointers only — never on touch or coarse
     · never hides the native cursor unless .cursor-on is present, which app.js
       adds only after the cursor is actually built and running
     · text inputs keep their native caret
     · reduced motion never activates it at all
   ========================================================================== */
.cur{position:fixed;left:0;top:0;z-index:9999;pointer-events:none;opacity:0;
  will-change:transform;contain:layout style size}
.cur-dot{position:absolute;left:-3px;top:-3px;width:6px;height:6px;border-radius:50%;
  background:var(--brand-lift);box-shadow:0 0 0 1px rgba(255,255,255,.35)}
/* the ring is the instrument: a hairline, not a filled circle */
.cur-ring{position:absolute;left:-17px;top:-17px;width:34px;height:34px;border-radius:50%;
  border:1px solid rgba(6,167,89,.55);
  transition:width .3s var(--ease),height .3s var(--ease),left .3s var(--ease),
             top .3s var(--ease),border-color .3s var(--ease),opacity .3s ease}
/* the glow — soft, brand green, and low enough to read as light not paint */
.cur-glow{position:absolute;left:-45px;top:-45px;width:90px;height:90px;border-radius:50%;
  background:radial-gradient(circle,rgba(6,167,89,.16) 0%,rgba(6,167,89,.06) 42%,transparent 70%);
  transition:opacity .35s ease,transform .35s var(--ease)}
html.cursor-on .cur{opacity:1}
/* over something interactive the ring closes on the target like a gauge */
html.cursor-on .cur.is-link .cur-ring{width:46px;height:46px;left:-23px;top:-23px;
  border-color:rgba(6,167,89,.9)}
html.cursor-on .cur.is-link .cur-glow{transform:scale(1.25)}
/* over text-entry the instrument gets out of the way entirely */
html.cursor-on .cur.is-text .cur-ring,
html.cursor-on .cur.is-text .cur-glow{opacity:0}
html.cursor-on .cur.is-down .cur-ring{width:26px;height:26px;left:-13px;top:-13px}

/* Hide the native cursor ONLY when the custom one is confirmed running.
   Inputs, textareas and selects always keep theirs — a missing caret is a
   genuine usability failure, not a style choice. */
@media (hover:hover) and (pointer:fine){
  html.cursor-on, html.cursor-on a, html.cursor-on button,
  html.cursor-on [role="button"], html.cursor-on label, html.cursor-on summary{cursor:none}
  html.cursor-on input, html.cursor-on textarea, html.cursor-on select,
  html.cursor-on [contenteditable]{cursor:auto}
  html.cursor-on input[type="text"], html.cursor-on input[type="email"],
  html.cursor-on input[type="tel"], html.cursor-on textarea{cursor:text}
  html.cursor-on input[type="range"]{cursor:ew-resize}
}
/* coarse pointers and reduced motion never see it at all */
@media (hover:none),(pointer:coarse){.cur{display:none!important}
  html.cursor-on,html.cursor-on *{cursor:auto}}
@media (prefers-reduced-motion:reduce){.cur{display:none!important}
  html.cursor-on,html.cursor-on *{cursor:auto}}

/* ==========================================================================
   HERO — ENTRANCE, DEPTH, PROOF STRIP (2026-07-31)

   ONE declared beat scale. Every element carries --b (its beat number) and the
   delay is computed from it, so the sequence is legible in the markup and cannot
   drift out of order when something is added: beat 1 eyebrow, 2 headline,
   3 subhead, 4 CTAs, 5 proof strip.

   --beat is the interval; --b0 the offset before anything moves.
   ========================================================================== */
.hero-f{--beat:.16s;--b0:.06s}
.hero-b{--d:calc(var(--b0) + (var(--b) - 1) * var(--beat))}
.js .hero-b{opacity:0;transform:translateY(16px)}
.hero-b.in{opacity:1;transform:none;
  transition:opacity .72s ease var(--d),transform .82s var(--ease) var(--d)}

/* beat 1 — the eyebrow, and its datum rule drawing out beside it */
.hero-f-k{display:flex;align-items:center;gap:16px}

/* beat 2 — the headline. Line masks with a per-word stagger inside each line:
   SplitType gives .line and .word, the LINE is the mask and the WORDS travel, so
   the type assembles left-to-right rather than arriving as five slabs.
   --wi is the word's index, set once by app.js. */
.hero-f-in h1 .line{overflow:hidden;padding-bottom:.06em;margin-bottom:-.06em}
.js .hero-f-in h1 .word{display:inline-block;transform:translateY(112%)}
.hero-f-in h1.in .word{transform:none;
  transition:transform .86s var(--ease) calc(.22s + var(--wi,0) * .038s)}

/* beat 5 — proof strip. The vertical dividers are gone: they made five facts read
   as one run-on line. Each fact now sits under its own short brand tick. */
.trust-f-in{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  gap:0 clamp(14px,1.8vw,30px);padding:0}
.js .trust-f-i{opacity:0;transform:translateY(10px)}
.js .trust-f-i i{transform:scaleX(0)}
.trust-f-in.in .trust-f-i{opacity:1;transform:none;
  transition:opacity .6s ease var(--d),transform .6s var(--ease) var(--d)}
.trust-f-in.in .trust-f-i i{transform:scaleX(1);
  transition:transform .5s var(--ease) calc(var(--d) + .06s)}

@media (max-width:720px){
  .trust-f-in{grid-template-columns:1fr;gap:0}
  .trust-f-i:last-child{border-bottom:0}
}
@media (max-width:1080px){
  .trust-f-in{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* Reduced motion: every hero beat lands at its final state with no transition.
   Listed explicitly — this is the section a visitor sees first, so a stranded
   element here is the worst possible failure. */
@media (prefers-reduced-motion:reduce){
  .js .hero-b,.hero-b{opacity:1!important;transform:none!important;transition:none!important}
  .js .hero-f-in h1 .word,.hero-f-in h1 .word{transform:none!important;transition:none!important}
  .js .trust-f-i,.trust-f-i{opacity:1!important;transform:none!important;transition:none!important}
  .js .trust-f-i i,.trust-f-i i{transform:none!important;transition:none!important}
}
/* pull the composition up so the deck is not sitting low with dead space under it */
@media (min-width:1000px){
  /* Round 3 set this to 0 to "remove the dead space under the card". That took
     the room away instead of using it. A floor is back — generous enough that the
     deck and the gear both sit in a composed field rather than a tight band, but
     NOT the full viewport round 3 rejected. .mech-in is align-items:center, so the
     extra height is shared above and below rather than dumped underneath. */
  .js .mech-stage{min-height:clamp(640px,88svh,900px);display:flex;align-items:center}
  .js .mech-stage > .mech-in{width:100%}
  .js .mech-in{align-items:center}
  .mech{padding-block:var(--sp-sm)}
}
.only-e{padding-block:var(--sp-sm)}

/* --- §7 one-roof band: shorter headline, tighter padding, compact stat grid --- */
.roof-f-in{position:relative;z-index:2;padding-block:clamp(28px,2.6vw,38px);
  color:var(--tx-d);--brand-text:var(--brand-lift);--brand-graphic:var(--brand-lift)}
.roof-f-line{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.026em;line-height:1.08;
  font-size:clamp(1.5rem,2.85vw,2.35rem);color:#fff;margin-top:12px;max-width:26ch;
  text-shadow:0 2px 26px rgba(0,0,0,.45)}
.rstat .num .accent{color:var(--brand-lift);font-size:.5em;margin-left:3px}

/* --- §8 CTA proof points: one three-column row of condensed labels --- */
/* ⚠ 2026-08-18 · A1 — THE PROOF ROW IS NOW THREE DISCRETE TILES.
   It was `repeat(3,minmax(0,1fr))` inside the CTA panel's ~419px proof
   column, i.e. 122px a column at 1440. "Saudi Arabia's only" sets as a
   166px UNBROKEN line, so it spilled 44px past its own box and crossed
   18px into "8,000 hrs". Measured on glyph rects — the element boxes did
   NOT overlap, which is why a box-level check called it clean.

   Root cause, and it is the standing trap this file already documents:
   the sitewide fix dropped `white-space:nowrap` from `.cta-facts b` in
   app.css, but the line below carried its OWN copy of nowrap, and
   home.css is enqueued AFTER app.css — so the fix never reached the
   homepage. A mobile override set `white-space:normal`, which is why the
   defect was desktop-only and every 390 sweep passed.

   Fixed STRUCTURALLY rather than by tuning the gap: each proof item is
   its own tile at the full column width, so no column arithmetic exists
   to collide. Stacking also lets the green claim and its supporting line
   read as heading-and-body instead of one run-on. */
.cta-facts-inline{display:grid;grid-template-columns:1fr;
  gap:10px;margin-top:clamp(20px,2.2vw,28px);
  border-top:0;padding-top:0}
.p-close.scene-dark .cta-facts-inline{border-top-color:var(--hair-d)}
/* ⚠ 2026-08-18 · PART D — the first tile's title sat 1px off its top edge.
   Measured: tile 0 computed `padding:0 16px 14px` against 14px on tiles 1-2,
   and stood 67px tall against 81px. The cause is app.css's
   `.cta-facts li:first-child{border-top:0;padding-top:0}` — correct for the
   ORIGINAL stacked list, where the first row had to lose its top rule and the
   space that went with it, and simply wrong now that each fact is a tile.
   Restated here (home.css is enqueued after app.css) so all three tiles carry
   identical inner spacing. */
.cta-facts-inline li{padding:16px;border-top:0;
  display:grid;grid-template-columns:auto 1fr;grid-template-areas:'icon title' 'icon body';
  column-gap:13px;align-items:start;
  background:var(--tint);border:1px solid var(--hair-l);border-radius:var(--r)}
.cta-facts-inline li:first-child{padding-top:16px}
.cta-facts-inline .cfi{grid-area:icon;width:26px;height:26px;margin-top:1px;
  color:var(--brand-ink);opacity:.9}
.cta-facts-inline b{grid-area:title}
.cta-facts-inline span{grid-area:body}
.p-close.scene-dark .cta-facts-inline .cfi{color:var(--brand-lift)}
.p-close.scene-dark .cta-facts-inline li{background:rgba(255,255,255,.055);
  border-color:rgba(255,255,255,.12)}
/* the label WRAPS. This is the declaration whose nowrap caused A1. */
.cta-facts-inline b{display:block;margin-bottom:5px;white-space:normal;
  text-wrap:balance;font-size:clamp(1rem,1.3vw,1.18rem);line-height:1.2}
.cta-facts-inline span{font-size:.82rem;line-height:1.45}
.intro-e-head{position:relative}
.intro-e-head h2{margin-top:14px;max-width:15ch}
.intro-e-body{display:flex;flex-direction:column;gap:16px}
.intro-e-body p{color:var(--tx-l-dim);max-width:60ch}
@media (max-width:860px){
  .cta-facts-inline{grid-template-columns:1fr;gap:14px}
  .cta-facts-inline b{white-space:normal}
}

/* --- §3 proof strip, refined -------------------------------------------------
   Icons were considered here. The mandated line-art set has an honest glyph for
   bearings, the facility, certification and the group, but NONE for "8,000
   working hours of warranty" — and a forced icon reads as decoration, so the
   refinement is typographic instead: more room, a clearer hierarchy, and the tick
   reduced to a mark rather than a bar. */
.trust-f-i{position:relative;display:block;font-size:.84rem;font-weight:500;color:#E4E7EA;
  line-height:1.45;padding:24px 0 26px;text-wrap:balance;letter-spacing:-.004em;
  --d:calc(.06s + 4 * .16s + var(--ti) * .05s)}
.trust-f-i i{display:block;width:12px;height:2px;background:var(--brand-lift);
  margin-bottom:13px;transform-origin:left center;opacity:.9}
.trust-f-i:first-child{font-weight:600;color:#fff}

/* ==========================================================================
   VISUAL AUDIT FIXES (2026-07-31) — found by LOOKING at the rendered page
   ========================================================================== */

/* §4 Who We Are — the heading column now carries a real, sized image slot.
   The column and the body also share one top axis; before, the paragraph column
   started above the heading and the two never lined up. */
.intro-e-grid{align-items:start}
.intro-e-shot .ph-media{width:100%;aspect-ratio:4/3;border-radius:var(--r)}

/* §7 One-roof stats — the audit's clearest miss of my own making. Splitting six
   figures into three large and three small was defensible while they sat in TWO
   rows; in ONE row, side by side on a shared baseline, the size difference reads
   as an inconsistency rather than a hierarchy. All six now share one scale, and
   the labels get a wider measure so long ones stop wrapping to three lines while
   short ones waste their column. */
.rstat{padding:16px 0 0;border-top:1px solid rgba(255,255,255,.22)}
.rstat .num .accent{color:var(--brand-lift);font-size:.5em;margin-left:3px}
/* the mono caption sat ~30px under a display heading; it needs its own air */
.roof-f-cap{margin-top:clamp(20px,2.4vw,30px);color:#C9CDD2!important;max-width:60ch}

/* §9 Clients — 593px of near-invisible white-on-white plates reading as a
   loading skeleton. The reservation stays, but the cells are now VISIBLE and
   deliberate, on two rows rather than three, and the panel is tightened. */
.clients-e{padding-block:var(--sp-sm)}
.clients-panel{position:relative;isolation:isolate;overflow:hidden;border-radius:var(--r-lg);
  background:var(--off);padding:clamp(24px,2.8vw,40px) clamp(22px,2.8vw,42px)}
.clients-head{margin-bottom:clamp(18px,2.2vw,28px)}
.clients-head h2{margin-top:8px}

/* ---- MOBILE AUDIT FIXES (390px), 2026-07-31 --------------------------------
   Found by capturing at 390, not by measuring: at two columns the ten reserved
   client cells became FIVE ROWS of empty plates, ~390px of nothing — worse in
   proportion than the desktop problem this replaced. The cells are decorative
   reservations (aria-hidden), so showing fewer of them on a narrow screen loses
   nothing: six still reads as a wall, ten reads as a stall. */
/* (The .client-slot rule that lived here is REMOVED, 2026-08-10. It hid cells
   7+ at 390px back when the cells were empty aria-hidden reservations. The
   class has had no markup since the names shipped on 2026-07-31, so the rule
   was dead — and now it would have been actively wrong: every cell is a real
   client mark and none may be hidden. Verified: 0 occurrences of client-slot
   in the theme.) */
.only-claim em{font-style:normal;color:var(--brand-ink)}

/* --- §3 capability card: the NAME leads, the proposition supports ---------- */
.mcard-text h3{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.02em;
  font-size:clamp(1.2rem,1.75vw,1.62rem);line-height:1.15;color:var(--tx-l);
  text-transform:none;margin-bottom:10px;max-width:22ch}
.mcard-prop{font-family:var(--f-body);font-weight:600;letter-spacing:-.005em;
  font-size:clamp(.95rem,1.1vw,1.05rem);line-height:1.35;color:var(--brand-ink);
  margin:0 0 10px;max-width:34ch}
.mcard.is-hero .mcard-prop{font-size:clamp(.95rem,1.1vw,1.05rem)}
.mcard-d{color:var(--tx-l-dim);font-size:.9rem;line-height:1.55;max-width:46ch}
/* a compact affordance, not a repeated sentence */
.mcard-btn{margin-top:16px;padding:11px 20px;font-size:.85rem}
/* §3 the green edge bar on each of the four card images is removed — four
   decorative markers, and the standing note is no lines that do no work */

/* --- §4 stats band: contained AND uncramped ------------------------------- */
.roof-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(22px,2.6vw,38px) clamp(24px,3vw,52px);margin-top:clamp(26px,3vw,40px)}
.rstat{padding:18px 0 0;border-top:1px solid rgba(255,255,255,.20)}
.rstat .num{display:block;color:#fff;font-size:clamp(1.3rem,1.75vw,1.7rem);
  font-variant-numeric:tabular-nums}
.rstat .num .accent{color:var(--brand-lift);font-size:.48em;margin-left:4px}
.rstat p{margin-top:10px;font-size:.82rem;line-height:1.5;color:#CDD1D5;max-width:30ch}
.rstat-lg .num{font-size:clamp(1.3rem,1.75vw,1.7rem)}
.rstat-lg p{color:#CDD1D5}
.intro-e-shot .ph-media{width:100%;aspect-ratio:4/3;border-radius:var(--r);
  min-height:0}

@media (max-width:960px){
  .roof-stats{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:640px){
  .roof-stats{grid-template-columns:1fr;gap:18px}
}

/* ==========================================================================
   HERO — framed split (round 4 §1)
   Copy on solid ground; the video held in a drawing sheet. Contrast is now a
   property of the design, not of whichever video frame happens to be playing.
   ========================================================================== */
.hero-f{position:relative;overflow:hidden;isolation:isolate;background:var(--ink);
  padding-top:var(--hdr-h);--beat:.16s;--b0:.06s}
@media (prefers-reduced-motion:reduce){.hero-f-vid{display:none}.hero-f-poster{opacity:1!important}}
.only-claim em{font-style:normal;color:var(--brand-ink)}

/* --- clients: the approved marks, ONE uniform treatment (2026-08-10) -------
   Replaces the typeset-name plates. The plate chrome is gone deliberately: a
   box around every logo fought the marks and made the wall read as a table.
   The marks now sit directly on the panel with generous air.

   FLEX, NOT GRID, and that is the whole point. 14 marks divide evenly only by
   7 and 2, so any other column count leaves a ragged final row — the same
   orphan defect already fixed once in the footer. Flex + justify-content
   CENTRES a partial last row at every width, so no breakpoint can orphan.

   Sizing is per-logo (set in front-page.php by the optical pass) — the cell is
   a fixed-height CONTAINER, and the image inside keeps its own computed size.
   Never size these to a common width/height: that is pixel-matching, and it
   makes a compact monogram look tiny beside a long wordmark. */
.clients-wall{--cscale:1;display:flex;flex-wrap:wrap;justify-content:center;
  gap:clamp(18px,2.6vw,34px) clamp(20px,2.4vw,30px);margin:0;padding:0;list-style:none}
.client-mark{flex:0 0 auto;width:calc((100% - 6*clamp(20px,2.4vw,30px))/7);
  height:calc(66px*var(--cscale));display:flex;align-items:center;justify-content:center}

/* ⚠ WIDTH IS SET EXPLICITLY FROM --cw, AND THAT IS LOAD-BEARING.
   These files are exported at 3x for retina, so their NATURAL size is 3x the
   intended one. With width:auto the browser sizes from those natural pixels
   and then whatever cap you wrote (max-height) does the real sizing — which
   silently height-matches every mark and THROWS AWAY the optical pass. That
   is what shipped in the first cut of this section and it was caught by
   measuring rendered boxes, not by looking (it still looked plausible).
   --cw is the per-logo width the optical pass computed; height follows from
   the width/height attributes' aspect ratio. Scale the SET with --cscale at a
   breakpoint — never re-cap an individual axis, or the balance is gone. */
.client-mark img{width:calc(var(--cw)*var(--cscale));height:auto;max-width:100%;
  opacity:.62;filter:saturate(0);transition:opacity .4s var(--ease),transform .4s var(--ease)}
.clients-wall:hover .client-mark img{opacity:.34}
.clients-wall .client-mark:hover img{opacity:1;transform:translateY(-2px)}
/* a mark with no clean official file: a sized slot, never a bad logo */
.client-pending{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.01em;
  font-size:.85rem;line-height:1.2;color:var(--tx-l-dim);opacity:.7;text-align:center;
  text-wrap:balance}
@media (max-width:1100px){
  .clients-wall{--cscale:.94}
  .client-mark{width:calc((100% - 4*clamp(20px,2.4vw,30px))/5)}
}
@media (max-width:760px){
  .clients-wall{--cscale:.88}
  .client-mark{width:calc((100% - 2*clamp(20px,2.4vw,30px))/3)}
}
@media (max-width:430px){
  .clients-wall{--cscale:.82}
  .client-mark{width:calc((100% - clamp(20px,2.4vw,30px))/2)}
}
/* Hover-dim is a pointer affordance; on touch it can stick. */
@media (hover:none){
  .clients-wall:hover .client-mark img{opacity:.62}
}
@media (prefers-reduced-motion:reduce){
  .client-mark img{transition:none}
  .clients-wall .client-mark:hover img{transform:none}
}

/* ==========================================================================
   HERO — "the drawing comes alive" (round 5)
   Layer 1 footage · layer 2 blueprint · layer 3 copy.
   ========================================================================== */
.hero-f{position:relative;overflow:hidden;isolation:isolate;background:var(--ink);
  min-height:clamp(560px,88svh,920px);display:flex;flex-direction:column;justify-content:flex-end;
  padding-top:var(--hdr-h);--beat:.16s;--b0:.06s}
.hero-f-media{position:absolute;inset:0;z-index:0}
.hero-f-media img,.hero-f-media video{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;filter:saturate(.82) contrast(1.04) brightness(.94)}
.hero-f-vid{opacity:0;transition:opacity .8s ease}
.hero-f-vid.playing{opacity:1}
/* The scrim EARNS the contrast — measured against the brightest composited
   region of the footage (the pour), not its average. Deepened from the round-3
   values because the drawing layer now also sits between the video and the text
   and must stay readable without lightening the ground under the headline. */
.hero-f-scrim{position:absolute;inset:0;
  background:
    linear-gradient(180deg,rgba(8,9,10,.80) 0%,rgba(8,9,10,.58) 32%,rgba(8,9,10,.93) 100%),
    linear-gradient(90deg,rgba(8,9,10,.94) 0%,rgba(8,9,10,.76) 46%,rgba(8,9,10,.42) 100%)}
.hero-f-media .ph-tag{bottom:64px}
@media (max-width:720px){.hero-f-media .ph-tag{bottom:auto;top:88px}}

/* --- layer 2: the drawing constructs --- */
.hero-bp-wrap{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden}
.hero-bp{position:absolute;inset:0;width:100%;height:100%}
/* --- THE FULL SHEET ----------------------------------------------------------
   1861 real recovered paths. Stroke properties live on the GROUP so they are
   inherited once rather than declared 1861 times, and — the point — so does
   stroke-dashoffset: pathLength="1" normalises every path, so animating the
   inherited dashoffset on the group draws the whole sheet while the browser
   transitions ONE element. Paint-only, no layout, no CLS.
   Density is carried by opacity, not by thinning the drawing: nothing is
   dropped to make it legible, it is simply drawn quieter. */
.hero-bp .bp-sheet{stroke:var(--brand-lift);stroke-width:.9;stroke-linecap:round;
  stroke-linejoin:round;fill:none;opacity:.30}
.js .hero-bp .bp-sheet{stroke-dasharray:1;stroke-dashoffset:1}
.hero-bp-wrap.in .bp-sheet{stroke-dashoffset:0;
  transition:stroke-dashoffset 1.4s var(--ease) .12s}   /* capped: dense, not sluggish */

/* THE LEFT-SIDE BLEND (2026-08-10) — the drawing is now full hero width, so it
   no longer needs to be ERASED from the copy column, it needs to DISSOLVE under
   it. The mask never reaches transparent on the left: it bottoms out at .17, so
   the sheet is still present behind the headline as texture while the headline
   keeps its contrast, and the drawing reads as one element spanning the hero
   rather than a box on the right. Composite-only: no layout, no CLS. */
.hero-bp-wrap.is-sheet{
  -webkit-mask-image:linear-gradient(90deg,rgba(0,0,0,.17) 0%,rgba(0,0,0,.26) 26%,rgba(0,0,0,.58) 52%,#000 82%);
          mask-image:linear-gradient(90deg,rgba(0,0,0,.17) 0%,rgba(0,0,0,.26) 26%,rgba(0,0,0,.58) 52%,#000 82%)}
@media (max-width:900px){
  /* Narrow: the copy is full width, so a column mask would dim the whole sheet
     evenly and lose the gradient's point. The copy stacks instead, so the fade
     runs on the OTHER axis — quietest across the eyebrow + headline band at the
     top, full strength below them, where the lead and CTA already clear AA. */
  .hero-bp-wrap.is-sheet{
    -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.55) 0%,rgba(0,0,0,.20) 9%,rgba(0,0,0,.20) 17%,#000 30%);
            mask-image:linear-gradient(180deg,rgba(0,0,0,.55) 0%,rgba(0,0,0,.20) 9%,rgba(0,0,0,.20) 17%,#000 30%)}
}

/* --- THE TRAVELLING LIGHT ----------------------------------------------------
   "Current running through the drawing." It runs ALONG the lines, because it IS
   the lines: 132 of the sheet's own longest paths, drawn again as a halo + core
   pair, carrying a dash pattern whose period (3) is three times pathLength (1).
   So each carrier is lit for about a third of its cycle and dark for the rest —
   the light arrives, travels the line, and leaves, rather than strobing.

   WHY IT IS CHEAP. Three bands, three animated elements. The dash offset is
   declared on the band and INHERITED by both <use>s and every path inside them,
   exactly as the draw-on does it, so the browser animates 3 elements and
   repaints ~264 strokes a frame — not 1861, and not one animation per path.
   No filter (a blur would re-rasterise every frame); the glow is two stacked
   strokes. No transform, no opacity keyframes, no layout. */
/* GATED ON .motion, NOT .js — deliberately, and verified on the live page. The
   head sets .js and arms a failsafe; app.js sets .motion as its first statement
   and never removes it, so .motion is the only class that means "the motion
   engine is actually alive". html was observed as "motion lenis" with no .js at
   all, so a .js gate would have hidden the light from every real visitor.
   No-JS keeps the static drawing and nothing else, which is the requirement. */
.hero-bp .bp-lit{display:none}
.motion .hero-bp .bp-lit{display:block}
.hero-bp .bp-l{stroke:var(--brand-lift);fill:none;
  stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0;opacity:0}
/* One PERIOD (2.2), three dash LENGTHS. They start at the same point on the path
   and end at different ones, so the three stack into a bright head trailing a
   soft body — a travelling light rather than a tick. A period LONGER than
   pathLength (1) is what makes it ambient rather than a strobe: the light
   crosses the line, leaves it, and comes back.
   The period is 2.2 and not 3 for a measured reason. Every path in a band shares
   one offset, so a band is lit or dark as a unit; at period 3 each band was lit
   47% of the time and frame-sampling found all five dark together in 61 of 840
   samples — the hero stood still for up to a second. At 2.2 that falls to ~4%. */
.hero-bp .bp-tail{stroke-dasharray:.230 1.970;stroke-width:2.4;opacity:.07}
.hero-bp .bp-mid {stroke-dasharray:.105 2.095;stroke-width:1.5;opacity:.17}
.hero-bp .bp-core{stroke-dasharray:.042 2.158;stroke-width:1.0;opacity:.50}
/* Lit only once the sheet has finished drawing itself: entrance first, then the
   loop. Negative delays start each band mid-cycle so they never march in step. */
.hero-bp-wrap.in .bp-l{opacity:1;transition:opacity 1.1s var(--ease) 1.5s;
  animation:bp-run var(--t) linear infinite var(--dl)}
.hero-bp .bp-l1{--t:11s;  --dl:0s}
.hero-bp .bp-l2{--t:13s;  --dl:-3.6s}
.hero-bp .bp-l3{--t:15.5s;--dl:-8.2s}
.hero-bp .bp-l4{--t:18s;  --dl:-12.9s}
.hero-bp .bp-l5{--t:21s;  --dl:-17.4s}
@keyframes bp-run{from{stroke-dashoffset:0}to{stroke-dashoffset:-2.2}}
@media (max-width:900px){
  /* A phone shows a narrow, heavily zoomed crop, so three bands of carriers put
     far more light per visible line than the desktop does — and it is the device
     least able to pay for it. One band, quieter, slower. */
  /* The light's horizontal fade is in VIEWBOX space, and a phone shows the
     MIDDLE of the viewBox — where that fade is only ~.12. Frame sampling found
     the travelling light all but invisible at 390 because of it. The mask is
     dropped here (CSS beats the presentation attribute); the copy is protected
     on this width by the wrap's vertical band mask instead, and re-measured. */
  .hero-bp .bp-lit{-webkit-mask:none;mask:none}
  .hero-bp .bp-l2,.hero-bp .bp-l4,.hero-bp .bp-l5{display:none}
  .hero-bp .bp-l1{--t:15s}
  .hero-bp .bp-l3{--t:19s}
  /* Dropping the fade means the light now falls where the copy is, so it is
     dimmed until it clears AA with margin. Measured across five settings at
     390: .05/.12/.40 put the green em at 2.30 and the lead at 4.38 — both
     failing; .025/.06/.20 lands the em on 3.10, a 3% margin that a brighter
     video frame would eat. These values measure 3.36 (needs 3) and 6.36
     (needs 4.5): the worst pair keeps 12% headroom. */
  .hero-bp .bp-tail{opacity:.020}
  .hero-bp .bp-mid {opacity:.045}
  .hero-bp .bp-core{opacity:.150}
}
/* --- layer 3: the copy --- */
.hero-f-in{position:relative;z-index:2;
  padding-block:clamp(30px,4.2vw,58px) clamp(22px,2.4vw,34px);
  color:var(--tx-d);--brand-text:var(--brand-lift);--brand-graphic:var(--brand-lift)}
.hero-f-in h1{margin-top:16px;max-width:16ch;color:#fff;text-wrap:balance;
  text-shadow:0 2px 26px rgba(0,0,0,.5)}
.hero-f-in h1 em{font-style:normal;color:var(--brand-lift)}
.hero-f-in .lead{margin-top:18px;max-width:52ch;color:#DDE1E4}
.hero-f .hero-e-cta{margin-top:28px}
.hero-f .cta.ghost{padding-left:2px}
/* the fact that locks last */
.hero-lock{display:flex;align-items:center;gap:11px;margin-top:26px;max-width:none;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.7rem;
  letter-spacing:.11em;text-transform:uppercase;color:#C6CBCF}
.hero-lock i{width:16px;height:2px;background:var(--brand-lift);flex:none;transform-origin:left center}
.js .hero-lock i{transform:scaleX(0)}
.hero-lock.in i{transform:scaleX(1);transition:transform .5s var(--ease) calc(var(--d) + .1s)}
@media (max-width:640px){.hero-lock{font-size:.64rem;letter-spacing:.08em;align-items:flex-start}
  .hero-lock i{margin-top:7px}}
@media (max-width:560px){.brk{display:none}}

@media (prefers-reduced-motion:reduce){
  .hero-f-vid{display:none}.hero-f-poster{opacity:1!important}
  /* the drawing lands COMPLETE, not unstarted — a static composed hero */
  .js .hero-bp .bp-sheet,.hero-bp .bp-sheet{stroke-dasharray:none!important;stroke-dashoffset:0!important;
    transition:none!important}
  /* NO travelling light. Not slowed, not faded — removed, so nothing moves and
     nothing is left mid-dash. The full sheet is still there, at final state. */
  .hero-bp .bp-lit,.js .hero-bp .bp-lit{display:none!important}
  .js .hero-lock i,.hero-lock i{transform:none!important;transition:none!important}
}

/* ==========================================================================
   §2 ONLY — the canvas gets a designed ground
   Brushed steel + a green-tinted wash + the drawing motif at real strength,
   layered so it reads as a surface rather than flat off-white.
   ========================================================================== */
.only-panel{position:relative;isolation:isolate;overflow:hidden;border-radius:var(--r-lg);
  background:var(--tint);
  padding:clamp(26px,3vw,44px) clamp(24px,3vw,46px);
  display:grid;grid-template-columns:minmax(0,1.02fr) minmax(0,.8fr);
  gap:clamp(22px,2.8vw,40px);align-items:center}
/* brushed steel */
.only-panel::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:
    repeating-linear-gradient(92deg,rgba(12,13,15,.030) 0 1px,transparent 1px 4px),
    linear-gradient(112deg,rgba(255,255,255,.85) 0%,rgba(12,13,15,.05) 46%,rgba(255,255,255,.78) 100%);
  opacity:.85}
/* green wash tying the two halves together */
.only-panel::after{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(120% 90% at 8% 10%,rgba(4,105,56,.10) 0%,rgba(4,105,56,.03) 42%,transparent 72%)}
.la-plate{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0;
  color:var(--ink);opacity:.11}
.la-plate .la-field{width:128%;height:128%;left:-14%;top:-16%}
.only-panel > *:not(.la-plate):not(.clients-metal){position:relative;z-index:1}

/* §2 tighten: the figure was setting the panel height and leaving ~165px of void
   beside the claim. Smaller figure + a shorter measure on the claim pulls the two
   halves into one unit instead of two things sharing a box. */
.only-fig{margin:0;justify-self:end;width:100%;max-width:300px;position:relative;
  background:linear-gradient(180deg,rgba(255,255,255,.9),rgba(255,255,255,.5));
  border-radius:var(--r);padding:clamp(11px,1.2vw,16px);
  box-shadow:inset 0 0 0 1px rgba(12,13,15,.07)}
.only-claim{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.028em;
  font-size:clamp(1.4rem,2.45vw,2.1rem);line-height:1.16;color:var(--tx-l);
  max-width:24ch;margin-top:10px}
.only-claim em{font-style:normal;color:var(--brand-ink)}
/* ⚠ PRE-EXISTING, found 2026-08-05 while checking the relocated callout at 390.
   .only-panel declared TWO columns and nothing ever collapsed them, so at 390
   the claim ran in a ~200px column and the figure in a ~150px one — the callout
   read one word per line. The 820px rule below already set justify-self:start,
   which only means anything in a single column, so the stack it assumed was
   simply never written. It is written now. */
@media (max-width:820px){
  .only-panel{grid-template-columns:minmax(0,1fr)}
  .only-fig{justify-self:start;max-width:300px}
}

/* ⚠ AA FIX. The layered ground (steel hatch + green wash + line-art at 11%)
   darkened the panel enough to push every small dim-grey element below 4.5:1 —
   measured 3.72-3.89:1 against a worst-case composite of all three layers at
   their strongest stops. The texture is the point of the section, so the fix is
   split: the layers come down to where they still read as a designed surface,
   and the small text on this panel takes a darker ink than the sitewide dim. */
.only-panel::before{opacity:.55}
.only-panel::after{background:radial-gradient(120% 90% at 8% 10%,rgba(4,105,56,.055) 0%,rgba(4,105,56,.02) 42%,transparent 72%)}
.la-plate{opacity:.07}
.only-panel .eyebrow{color:var(--brand-deep)}
.only-panel .tol-k,.only-panel .tol-feat,.only-panel .tol-std{color:#3E4247}
.only-panel .tol-read.is-set .tol-feat{color:var(--brand-deep)}

/* --- the card image: larger, better composed, and the slot reads intentional --- */
.mcard{background:var(--white);border-radius:var(--r-lg);
  padding:clamp(16px,1.8vw,24px);
  display:grid;grid-template-columns:minmax(0,46%) minmax(0,1fr);
  gap:clamp(18px,2.2vw,30px);align-items:center}
.mcard-shot{position:relative;width:100%;aspect-ratio:5/4;min-width:0;
  border-radius:var(--r);overflow:hidden;
  /* the plate carries a brushed ground, so a contain-fit part sits ON something
     rather than floating on flat black — and the 5 Aug photographs land in a
     frame that already looks deliberate */
  background:
    repeating-linear-gradient(97deg,rgba(255,255,255,.022) 0 1px,transparent 1px 5px),
    radial-gradient(76% 76% at 50% 42%,#22262d 0%,#111417 62%,#0d1012 100%);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.05)}
.mcard-shot img{width:100%;height:100%;display:block;
  transition:transform .7s var(--ease)}
.mcard:hover .mcard-shot img{transform:scale(1.025)}
.mcard-shot.fit-cover img{object-fit:cover}
.mcard-shot.fit-contain img{object-fit:contain;padding:clamp(10px,1.4vw,20px)}
@media (prefers-reduced-motion:reduce){
  .mcard-shot img{transition:none!important}
  .mcard:hover .mcard-shot img{transform:none}
}
@media (max-width:640px){.mcard{grid-template-columns:1fr}}

/* --- capability position counter (arrows removed 2026-08-01) ---------------
   A readout, not a control. The keyboard path moved to the deck region first —
   see front-page.php. The deck carries a visible focus ring so a keyboard user
   can see where they are before pressing an arrow. */
.mech-nav{display:flex;align-items:center;margin:clamp(14px,1.6vw,20px) 0 clamp(18px,2vw,26px)}
.mech-count{display:inline-flex;align-items:baseline;gap:5px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.8rem;letter-spacing:.1em;color:#5A5E64}
.mech-count b{font-size:1.05rem;font-weight:600;color:var(--brand-ink);
  font-variant-numeric:tabular-nums}
.mech-count i{font-style:normal;opacity:.45}
.js .mech-deck:focus-visible{outline:2px solid var(--brand-graphic);outline-offset:6px;
  border-radius:var(--r-lg)}
.mech-deck:focus:not(:focus-visible){outline:none}

/* --- Who We Are: balance the two columns (2026-08-01) ----------------------
   Measured before changing anything: the heading+slot column ended 236px BELOW
   the paragraph column, which is what read as an empty lower-right. The section
   gap itself was already 0 after the sitewide rhythm fix, so the void was
   column imbalance, not spacing.

   The slot is capped so the two columns finish close together, and the grid
   ratio shifts slightly toward the text so the paragraphs carry more of the
   height. The slot stays 4:3 and comfortably sized for the 5 Aug photograph. */
.intro-e-grid{display:grid;grid-template-columns:minmax(0,.72fr) minmax(0,1.28fr);
  gap:clamp(24px,3.6vw,52px);align-items:start}
/* The previous attempt capped this slot at 300px inside the .72fr column and
   called the job done. It was not: at 4:3 that is still 225px tall, and the
   heading column then finished 128px BELOW the body text, which is the residual
   that kept reading as a gap. The slot now spans both columns and closes the
   section on one flush edge, so neither column can overhang the other. */
.intro-e-shot{grid-column:1/-1;width:100%;margin-top:clamp(20px,2.6vw,34px)}
.intro-e-shot .ph-media{width:100%;aspect-ratio:16/5;border-radius:var(--r);min-height:0}
.intro-e{padding-block:var(--sp) clamp(34px,4vw,58px)}
@media (max-width:960px){
  .intro-e-grid{grid-template-columns:1fr;gap:20px}
  /* A 16:5 band is 122px tall at 390 — a slot, not a photograph. Stacked, the
     figure gets the whole width, so it takes a normal frame aspect again. */
  .intro-e-shot .ph-media{aspect-ratio:16/10}
}

/* ==========================================================================
   §M · THE MOBILE PASS (2026-08-12)
   Everything below is width-gated and APPENDED, never spliced into the rules
   above — the two worst regressions on this project both came from removing a
   CSS range by marker span, so this block only ever adds.
   Ordering matters: it is last in the file, so at equal specificity it wins.
   ========================================================================== */

/* --------------------------------------------------------------------------
   §M1 · THE HERO, COMPOSED FOR A PHONE
   Measured before: at 390 the hero ran 1117px — 1.4 screens — and the order was
   eyebrow(2 lines) -> h1(4) -> lead(9 lines / 260px) -> CTAs(131px) -> lock ->
   proof(268px). On a real 390x844 phone with browser chrome that puts BOTH
   CTAs below the fold: the first screen was a headline and a wall of body copy
   with nothing to act on. That is the "not well structured" the client saw.
   Nothing here changes a word of copy — only measure, rhythm and the proof
   strip's axis.
   -------------------------------------------------------------------------- */
@media (max-width:720px){
  /* THE EYEBROW IS NOT SHRUNK ON MOBILE AT ALL (Abhay, 2026-08-12).
     This first reduced it to .7rem/.085em to hold one line at 390/414, then to
     .66rem below 400. The ruling is that ~11px is too small to read on a phone
     and a two-line kicker is the better trade — so the sitewide 12.16px/.16em
     stands at every width and simply wraps. Measured: two lines at 360, 390 and
     414, never three. Deliberately no override here; the cost is ~20px of hero
     height and it is the right one to pay. */

  .hero-f-in{padding-block:clamp(20px,5vw,34px) clamp(18px,3vw,26px)}
  .hero-f-in h1{margin-top:12px;max-width:none}
  /* The lead is verbatim client copy and stays whole; it is the LINE that was
     wrong, not the length. 16px/1.5 in a 350px measure gives ~44 characters a
     line, which is a comfortable mobile measure and returns ~90px. */
  .hero-f-in .lead{margin-top:14px;font-size:1rem;line-height:1.52}
  .hero-f .hero-e-cta{margin-top:20px;gap:12px 18px}
  /* The primary action takes the full measure so it is unmistakably the
     action; the secondary sits under it as a text link, not a second slab. */
  .hero-f .hero-e-cta .cta:not(.ghost){flex:1 0 100%;justify-content:center;min-height:48px}
  .hero-f .hero-e-cta .cta.ghost{min-height:44px;display:inline-flex;align-items:center}
  .hero-lock{margin-top:16px}

  /* The INDICATIVE VISUAL flag is pinned at top:88px, which used to sit clear
     because the copy started ~135px down. Tightening the hero above brought the
     eyebrow up to ~96px and the two collided — caught by looking, not by any
     measurement, because neither element overflows anything. The flag moves up
     under the header and the copy keeps enough offset to clear it outright. */
  .hero-f-media .ph-tag{top:calc(var(--hdr-h) + 6px)}
  .hero-f-in{padding-block:50px clamp(18px,3vw,26px)}
}
/* THE <400px EYEBROW SHRINK IS DELIBERATELY GONE (Abhay, 2026-08-12).
   It dropped the eyebrow to .66rem — 10.6px — to hold one line at 360. The call
   is that 11px is too small to read on a phone and a two-line kicker is the
   better trade, so below 400 it simply WRAPS at the same 11.2px everything else
   in this band uses. Recorded rather than silently reverted: one line was the
   earlier judgement, legibility is the decision. */

/* THE PROOF STRIP — five stacked facts (268px) become one swipeable rail.
   Five full-width rows is a list, and a list of five one-line claims reads as
   filler. On its own axis it is a rail the thumb can push through, which is
   both shorter and the first thing on the page that responds to touch. Native
   overflow + scroll-snap: no JS, no library, and it cannot trap the page. */
@media (max-width:720px){
  .trust-f-in{display:flex;grid-template-columns:none;gap:0;
    overflow-x:auto;overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
    scrollbar-width:none;
    /* the rail bleeds to both edges so it reads as swipeable, while the first
       card still lines up on the page's text axis */
    margin-inline:calc(var(--gutter) * -1);
    padding-inline:var(--gutter);padding-block:16px 18px;
    /* SCROLL-PADDING IS LOAD-BEARING, not a nicety. A snap container's snap
       positions are measured from the SCROLLPORT edge, which ignores padding —
       so with `x mandatory` the browser snapped the first card's start edge to
       scrollLeft:0 of the scrollport, i.e. 20px INSIDE the gutter, and the first
       word of the first fact was cut off. scrollLeft was stuck at 20 and could
       not be set back to 0, which is the tell. Caught by looking at the rail,
       then confirmed by reading scrollLeft. */
    scroll-padding-inline:var(--gutter)}
  .trust-f-in::-webkit-scrollbar{display:none}
  .trust-f-i{flex:0 0 min(70%,240px);scroll-snap-align:start;
    padding:0 18px 0 0;margin-right:16px;font-size:.8rem;line-height:1.4;
    border-right:1px solid rgba(255,255,255,.12)}
  .trust-f-i:last-child{border-right:0;margin-right:0;
    /* trailing air, so the last card can reach the snap position */
    padding-right:var(--gutter)}
  .trust-f-i i{margin-bottom:10px}
}

/* --------------------------------------------------------------------------
   §M2 · THE TRAVELLING LIGHT, VISIBLE ON A PHONE
   The client reported "the lines are not animating on mobile". They ARE — the
   animation was verified running by driving it — but at tail .020 / mid .045 /
   core .150 over a scrimmed video it is below the threshold you can see on a
   handset, and three of the five bands were display:none. In effect the client
   is right: nothing appears to move.

   WHY IT WAS DIMMED, AND WHERE THE REAL FAULT WAS. The light was dimmed to
   protect ONE measured pair — the green <em> in the headline, which spread
   light put at 2.79 against the 3.0 it needs. But the <em> was only fragile
   because THE MASK PLATEAU ENDED IN THE MIDDLE OF THE HEADLINE: the wrap mask
   held .20 alpha to 17% of the hero and reached full strength by 30%, while the
   h1 spans 18%-37%. So the lower two thirds of the headline sat under the layer
   at FULL strength and the only lever left was to dim the whole layer — paying
   for one band of type with the entire effect everywhere else on the screen.

   Extend the plateau across the headline's real extent instead, and the
   constraint disappears: measured over the composited stack with every carrier
   forced fully lit, the <em> goes 3.57 -> 5.68 while the light runs at DESKTOP
   strength. Numbers and method in mobile-audit.md.
   -------------------------------------------------------------------------- */
@media (max-width:900px){
  /* A third gradient layer, and ONLY on narrow widths: a deeper pool under the
     eyebrow + headline + lead band, feathered out before the proof rail so the
     scene does not turn into a flat black panel. */
  .hero-f-scrim{background:
    linear-gradient(180deg,rgba(8,9,10,.88) 0%,rgba(8,9,10,.80) 46%,rgba(8,9,10,.62) 74%,rgba(8,9,10,.93) 100%),
    linear-gradient(90deg,rgba(8,9,10,.86) 0%,rgba(8,9,10,.72) 52%,rgba(8,9,10,.58) 100%)}

  /* Three bands, not one. Five was the desktop count; one band meant that for
     roughly half of its 15s cycle NOTHING on the phone was lit at all — the
     hero genuinely stood still, which is exactly the report. Three staggered
     bands close that window without paying for five. */
  .hero-bp .bp-l2{display:block}
  .hero-bp .bp-l4,.hero-bp .bp-l5{display:none}
  .hero-bp .bp-l1{--t:13s}
  .hero-bp .bp-l2{--t:16s}
  .hero-bp .bp-l3{--t:19s}

  /* OPACITY AT DESKTOP PARITY. With the headline band properly protected there
     is no longer anything to buy by dimming: at these values the worst pair on
     the whole hero is the lead at 6.62 against the 4.5 it needs. The phone now
     shows the same light the desktop does — it just shows three bands of it.
     DASH LENGTHS ARE OPENED UP, and that is a separate, free lever: a longer
     lit segment reads as a light travelling a line where a very short one reads
     as a speck, and it costs NOTHING in contrast, because the measurement forces
     every carrier fully lit and is therefore dash-length independent. */
  .hero-bp .bp-tail{stroke-dasharray:.330 1.870;stroke-width:2.4;opacity:.070}
  .hero-bp .bp-mid {stroke-dasharray:.165 2.035;stroke-width:1.5;opacity:.170}
  .hero-bp .bp-core{stroke-dasharray:.070 2.130;stroke-width:1.0;opacity:.500}

  /* THE PLATEAU NOW COVERS THE HEADLINE, which is the whole fix. The h1 spans
     roughly 18%-37% of the hero at every phone width, so the quiet band runs to
     38% and only then ramps to full by 52% — below the headline, across the
     lead, which has 6.6:1 of headroom and can carry it. Everything the previous
     tuning was fighting came from this gradient reaching full strength at 30%,
     with a third of the headline still to go.
     The layer opacity comes UP at the same time: masked to .20 across the
     headline, .42 is .084 effective there — still quieter than the old .30 was
     at full strength — while below the plateau the drawing finally reads as a
     drawing rather than as a few stray diagonals. */
  .hero-bp-wrap.is-sheet{
    -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.62) 0%,rgba(0,0,0,.20) 8%,rgba(0,0,0,.20) 38%,#000 52%);
            mask-image:linear-gradient(180deg,rgba(0,0,0,.62) 0%,rgba(0,0,0,.20) 8%,rgba(0,0,0,.20) 38%,#000 52%)}
  .hero-bp .bp-sheet{opacity:.42}
}
/* Reduced motion already removes .bp-lit entirely and lands the sheet complete
   (the rule sits above with the rest of the hero's reduced-motion block); none
   of the above re-enables it, because every selector here is inside .bp-lit's
   subtree or is a plain opacity on the static sheet. */

/* --------------------------------------------------------------------------
   §M3 · THE GEAR — a composed detail, not a clipped fragment
   The client called this "the compass going haywire", and it was two faults at
   once.

   PLACEMENT. The mobile rule positioned the gear `top:-6%; right:-38%` inside
   .mech-in. Percentage `top` on an absolutely positioned box resolves against
   the containing block's HEIGHT — and .mech-in is the whole stacked capability
   column, 2557px tall at 390. So -6% is -153px: the gear was pushed above the
   section and clipped by .mech's own overflow:hidden at the same time as it
   was clipped by the right edge, i.e. cut on two sides simultaneously, sitting
   at .5 opacity directly over the section heading. Its vertical position also
   moved with the length of the capability copy, which is not a position at all.

   MOTION. See app.js — the rotation was snapped by an IntersectionObserver.

   Anchored to the head instead, sized to sit deliberately, and given a real
   easing so the continuous scroll drive reads as a machine turning.
   -------------------------------------------------------------------------- */
@media (max-width:999px){
  /* .mech-head is the anchor: a fixed-height box at the top of the section, so
     the gear's position no longer depends on how much copy follows it. */
  .mech-head{position:relative}
  .mech-media{position:absolute;inset:auto auto auto auto;
    right:calc(var(--gutter) * -1 - 62px);top:-30px;
    width:210px;opacity:.34;z-index:0}
  /* One edge only. A quarter-disc bleeding off the right reads as a detail
     drawing continuing past the page; a shape cut on the top AND the right
     reads as a broken image, which is what shipped. */
  .mech-head > *{position:relative;z-index:1}
  .mech-copy{position:relative;z-index:1}
  .js .mech-dial{display:none}
  /* The gear is scroll-driven now, so it moves every frame and needs no
     transition; the short one is a settle for the discrete keyboard jumps. */
  .mech-gear-body{transition:transform .22s var(--ease)}
}
@media (max-width:520px){
  .mech-media{width:172px;right:calc(var(--gutter) * -1 - 50px);top:-22px}
}
@media (prefers-reduced-motion:reduce){
  /* belt and braces: the transform is already forced to none above */
  .mech-gear-body{transition:none!important}
}

/* §M4 (touch ergonomics) is SITEWIDE and therefore lives in app.css.
   It was written here first and moved before it shipped: .ph-tag, .ph-label
   and .eframe-cap are rendered by sitewide helpers on 19 pages, and home.css
   only loads on the front page — which is precisely the page-scoped-stylesheet
   bug already recorded three times in CLAUDE.md. Pointer left deliberately.

   ONE line has to stay here, and it is the mirror image of that same trap.
   home.css carries its own `@media (max-width:560px){.ph-tag{font-size:10.5px}}`,
   and home.css is enqueued AFTER app.css — so on the homepage, and ONLY on the
   homepage, that 9px beat the sitewide fix at equal specificity. Re-stated
   below, later in this same file, so the cascade lands the right way round. */
@media (max-width:560px){
  .ph-tag{font-size:10.5px;letter-spacing:.09em}
}
/* Two more homepage-only items the sitewide sweep surfaced at <11.5px. Both
   are mono, tracked and uppercase, which reads smaller still at a given size. */
@media (max-width:720px){
  /* the fact that locks last under the hero CTAs — 10.2px */
  .hero-lock{font-size:11.6px;letter-spacing:.06em}
  /* the tolerance-stack labels on the ONLY panel — 9.9px and 11.2px. These are
     the widget's READOUT: what is being measured and to what standard. */
  .tol-k{font-size:11px}
  .tol-feat,.tol-std{font-size:11px}
  /* the unit suffix beside a proof numeral ("hrs / 1 year", "%", "+") at 10px.
     It is what makes the numeral mean anything, so it has to be readable.
     Written first as `.num .accent` and beaten by the FOUR existing
     `.rstat .num .accent` rules, which are three classes deep — the fifth
     cascade miss this pass, and again found by re-measuring, not by reading. */
  .rstat .num .accent{font-size:11.6px}
}

/* --------------------------------------------------------------------------
   §M5 · THE PROOF STRIP GOES BACK ON THE PAGE'S TEXT AXIS (2026-08-12)
   Found while chasing the WhatsApp-float overlap, and it is the actual cause of
   it. `.trust-f-in` carries the `.wrap` class — so it should take the page
   gutter — but its own base rule ends in `padding:0`, which beats `.wrap`'s
   `padding-inline` at equal specificity because it is declared later. Measured
   at 1200: the strip's first fact begins at **x=0** and the last ends at
   x=1200, flush to both viewport edges, while the hero copy directly above it
   starts at x=60. So the proof strip was never aligned with the hero it belongs
   to, and it ran straight under the fixed float parked in the gutter.
   Restoring the gutter fixes the alignment AND removes most of the collision;
   the float is nudged separately in app.css to clear the rest.
   The ≤720px rail deliberately keeps its own negative-margin bleed — that is a
   different, intentional treatment and is declared later, so it still wins. */
.trust-f-in{padding-inline:var(--gutter)}

/* THE FIXED FLOAT STILL CLIPS THE FIRST FACT BETWEEN 961 AND 1279 (2026-08-12).
   Restoring the gutter above fixed the bulk of this — the strip's text moved
   from x=0 to the page axis — but the float is a fixed 46px circle at left:18,
   so its right edge is ALWAYS at 64px while the gutter in this band is only
   48-64px (5vw). Measured clearance after the gutter fix: -16px at 961, -9px at
   1100, -4px at 1200, 0 at 1279, and +36px at 1352 where the container caps and
   the strip is centred. So the collision is confined to exactly this band.

   The strip is nudged rather than the float. Lifting the float would move it on
   EVERY desktop page and at every scroll position to avoid one section at one
   scroll position; widening the gutter would relayout the page. This band-scoped
   inset costs a ~22px misalignment between the strip's first fact and the hero
   copy above it, in this band only — and the strip is a full-bleed band with its
   own background, so the inset reads as the band's own padding rather than as a
   broken axis. Below 961 the float is display:none and the rail takes over. */
@media (min-width:961px) and (max-width:1279px){
  .trust-f-in{padding-left:86px}
}

/* ==========================================================================
   §X1h · STATS BANDS — homepage (2026-08-17). Same removal as pages.css §X1.
   MUST be here as well as there: home.css is enqueued AFTER app.css, so the
   .cta-facts-inline rules and .rstat live in this file's cascade and a fix
   written only in app.css would not reach the homepage. Fifth time on file.
   ========================================================================== */
.rstat{border-top:0;padding-top:0}
.cta-facts-inline{border-top:0;padding-top:0}
.cta-facts-inline li{border-top:0}

/* §F1 · industries tile titles break evenly.
   The end-to-end sweep flagged two as heading orphans at 1440
   ("Power Generation & Utilities" ran 3 lines with a 76px last line).
   These are tile titles in a fixed box, so the fix is balancing the
   wrap, not resizing: no copy and no type scale changes. */
.inds-tile h3{text-wrap:balance}

/* §T1 · the filled Facility & team band.
   The slot was a `.ph-media` plate; it now holds a real photograph in an
   `.eframe`. The frame keeps the band's 16:5 and the image fills it
   edge-to-edge — the source was cropped to 16:5 at build time, so cover
   removes nothing. No ground shows, so nothing reads as a padded box. */
.intro-e-shot{margin:0}
.intro-e-shot .eframe{width:100%;aspect-ratio:16/5;border-radius:var(--r)}
.intro-e-shot .eframe img{width:100%;height:100%;object-fit:cover;display:block}
.intro-e-shot .eframe-cap{margin-top:12px}
