
/* ============================================================
   §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)
   ============================================================ */
/* ==========================================================================
   SACOM — THE FALCON'S EYE — cinematic design system (v4)
   Charcoal & white are SCENES in one film. One brand-green accent. No boxes,
   no paper, no borders-as-decoration, no stamps, no mono-annotation layer.
   ========================================================================== */

:root{
  /* PART C · the one ground every contained PRODUCT frame sits on */
  --shot-ground:#EDEEF0;
  --ink:#0C0D0F;          /* charcoal scene */
  --ink-2:#141619;
  --ink-3:#20242B;
  --white:#FFFFFF;        /* light scene (cool, never warm) */
  --off:#F4F5F6;          /* cool near-white */
  --tx-d:#F2F3F4;         /* text on charcoal */
  --tx-d-dim:#9DA1A7;
  --tx-l:#0C0D0F;         /* text on white */
  --tx-l-dim:#5A5E64;
  /* --- BRAND GREEN (2026-07-29) — replaces the amber system wholesale ---
     Source: Falcon Group Employee Brand Guidelines, section 06, which assigns
     Shaheen Arabia Company #046938 and explicitly covers "digital content".

     Green INVERTS the amber structure. #046938 measures 6.81:1 on white, so it
     serves as BOTH the graphic mark and the text colour on light — no darker
     step is needed. But it is only 2.85:1 on charcoal, failing even the 3:1
     graphic minimum, so dark scenes need a LIGHTER step. --brand-lift is the
     same colour lightened (hue 150.9deg and saturation 0.927 preserved exactly,
     lightness 0.214 -> 0.34), measuring 6.18:1 on ink.

     --brand-graphic and --brand-text flip per scene; anything sitting on a dark
     PLATE inside a light section must use --brand-lift explicitly. */
  --brand:#046938;        /* graphic marks + button fills, light scenes */
  --brand-ink:#046938;    /* text on light — the exact brand hex passes AA */
  --brand-lift:#06A759;   /* REQUIRED on charcoal / dark plates */
  --brand-deep:#03542D;   /* hover / pressed */
  --brand-soft:#0B8A4A;   /* decorative gradient stop only, never text/fill */
  --brand-graphic:var(--brand);
  --brand-text:var(--brand-lift);   /* :root default assumes the dark scene */
  --hair-d:rgba(255,255,255,.14);
  --hair-l:rgba(12,13,15,.10);
  /* one committed geometry: a considered soft radius, applied consistently.
     Replaces the retired 2–3px "cut sheet" sharpness. */
  --r-sm:8px;    /* buttons, inputs, chips */
  --r:12px;      /* cards, panels */
  --r-lg:18px;   /* feature surfaces, imagery */
  --tint:#F6F5F3;         /* warm card tint (borderless containment) */
  --tint-2:#EFEDEA;       /* deeper tint / hover */
  --shadow:0 1px 2px rgba(12,13,15,.04),0 8px 30px rgba(12,13,15,.06);
  --shadow-lg:0 2px 6px rgba(12,13,15,.05),0 18px 50px rgba(12,13,15,.10);
  --f-dsp:"Geist",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --f-body:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --container:1200px;   /* ATF standard 2026-08-17 (was 1280) */
  --gutter:clamp(20px,5vw,64px);
  /* --- vertical rhythm: ONE three-step scale (2026-07-28) ---
     Before this, the homepage used six different bespoke clamps across eight
     sections (86/96/110/115/84/130) and nothing used the system. --sp is tuned
     to the value every inner-page .p-block already computed to (101px @1440),
     so adopting the scale leaves all 14 inner pages geometrically unchanged.
     Differences below one step are not perceivable — never invent a fourth. */
  /* TIGHTENED SITEWIDE 2026-08-01 (approved). These were the homepage's values,
     scoped to body.home so inner pages stayed geometrically unchanged. That
     scoping is what left inner pages with ~300px empty bands between adjacent
     sections, so the scope is now removed and every page gets the tighter
     rhythm. Still exactly three steps — never add a fourth. */
  /* RETUNED 2026-08-17 to the ATF standard's tight rhythm. Measured before:
     .p-block bottom 73.0 + next .p-block top 52.7 = a 126px gap at 1352 (132 at
     1440) where the standard is 48-56. That is the "bloated / yelling" report,
     and it is a TOKEN problem, not a per-section one.
     Now: bottom --sp 28 + top --sp-sm 22.3 = ~50px at 1440, ~38px at 390.
     Homepage sections that pay --sp on BOTH sides land at 56, the top of the
     band. Still exactly three steps — never invent a fourth. */
  --sp-sm:clamp(17px,1.55vw,23px);
  --sp:clamp(21px,1.95vw,28px);
  --sp-lg:clamp(28px,2.6vw,38px);
  /* ONE easing token for every motion in the system (2026-07-29). If a new
     interaction needs a different curve, it probably needs a different idea. */
  --ease:cubic-bezier(.16,1,.3,1);
  --z-base:0; --z-raise:10; --z-chrome:20; --z-overlay:50;

  /* Header sizing is a TOKEN because three stylesheets key off it (the bar
     itself, the homepage hero pad, the inner-page hero pad) and they drifted
     apart before. The stacked lockup forced 76px -> 96px; see .brand .logo. */
  --hdr-h:96px;
  --logo-h:64px;

  /* --- SIGNATURE TILE GRADIENT (2026-07-30) -------------------------------
     ONE overlay for every photographic tile, so a mixed set of real photos and
     marked placeholders reads as a single art-directed library. Directional and
     brand-tinted in the mid-tones, deep at the foot where tile titles sit.
     It REPLACES the per-component scrims rather than stacking on them — the
     industries tiles previously carried their own 180deg scrim.
     Deliberately NOT applied to the full-bleed hero and statement scenes: those
     are a different device with their own scrims, already contrast-tuned. */
  --tile-grad:linear-gradient(197deg,
     rgba(4,105,56,0)   0%,
     rgba(4,105,56,.07) 34%,
     rgba(8,20,14,.34)  62%,
     rgba(6,15,11,.86) 100%);
}

/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0}
h1,h2,h3,h4,p,figure,blockquote,ul,ol{margin:0}
ul,ol{padding:0;list-style:none}
img,svg,video,canvas{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--brand-graphic);outline-offset:3px}

body{
  font-family:var(--f-body);font-size:clamp(15px,1.05vw,16px);line-height:1.65;
  color:var(--tx-d);background:var(--ink);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden;
}

/* ---------- type scale (one disciplined system) ---------- */
.dsp{font-family:var(--f-dsp);font-weight:600;line-height:1.0;letter-spacing:-.02em}
/* ---- TYPE SCALE, capped to the ATF standard 2026-08-17 ----
   Was H1 40-62 / H2 30-42 / H3 21-26 / body 16-17. The 62px H1 and 42px H2 are
   most of the "yelling", and an oversized H2 is also what forced the orphaned
   word in "The component, not the / overhaul." Going smaller than these caps is
   allowed; going bigger is not. */
h1,.h1{font-family:var(--f-dsp);font-weight:700;line-height:1.06;letter-spacing:-.02em;
  font-size:clamp(1.75rem,3.4vw,3rem);text-wrap:balance}      /* 28–48 */
h2,.h2{font-family:var(--f-dsp);font-weight:600;line-height:1.14;letter-spacing:-.02em;
  font-size:clamp(1.375rem,2.2vw,2rem);text-wrap:balance}     /* 22–32 */
h3,.h3{font-family:var(--f-dsp);font-weight:600;line-height:1.25;letter-spacing:-.02em;
  font-size:clamp(1.125rem,1.4vw,1.375rem);text-wrap:balance} /* 18–22 */
.lead{font-size:clamp(1rem,1.1vw,1.0625rem);line-height:1.62;max-width:46ch;letter-spacing:-.005em}   /* 16–17 */
p{max-width:66ch}
.accent{color:var(--brand-text)}
.eyebrow{display:inline-block;font-family:var(--f-body);font-weight:600;
  font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:var(--brand-text)}   /* 12px / .12em — ATF standard 2026-08-17, was .16em */
.num{font-family:var(--f-dsp);font-weight:600;line-height:.95;letter-spacing:-.028em;font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1,"ss01" 1}
/* ⚠ ONE DELIBERATE EXCEPTION to the H2 cap. .statement is the single full-bleed
   statement band, not a section heading — it is the page's one display moment.
   Capped at 38px: below the 48px H1 ceiling, above the 32px H2, and down from
   the 42px it was. If Abhay wants it inside the H2 band it is one number. */
.statement{font-family:var(--f-dsp);font-weight:600;line-height:1.14;letter-spacing:-.02em;font-size:clamp(1.5rem,2.6vw,2.375rem);max-width:24ch;text-wrap:balance}

/* ---------- layout / scenes ---------- */
.wrap{width:100%;max-width:var(--container);margin-inline:auto;padding-inline:var(--gutter)}
.scene{position:relative;z-index:var(--z-base)}
.scene-dark{background:var(--ink);color:var(--tx-d);--brand-text:var(--brand-lift);--brand-graphic:var(--brand-lift)}
.scene-light{background:var(--white);color:var(--tx-l);--brand-text:var(--brand-ink);--brand-graphic:var(--brand)}
.scene-light .lead,.scene-light p{color:var(--tx-l-dim)}
.bleed{width:100%}
.pad{padding-block:var(--sp-lg)}
.pad-sm{padding-block:var(--sp-sm)}

/* ---------- header (transparent, adapts to scene; solidifies on scroll) ---------- */
.hdr{position:fixed;top:0;left:0;right:0;z-index:var(--z-chrome);
  transition:transform .4s ease,color .3s ease,background .3s ease,box-shadow .3s ease,border-color .3s ease;
  border-bottom:1px solid transparent}
.hdr.hide{transform:translateY(-100%)}
.hdr.scrolled[data-scene="light"]{background:rgba(255,255,255,.9);backdrop-filter:saturate(180%) blur(12px);border-bottom-color:var(--hair-l)}
.hdr.scrolled[data-scene="dark"]{background:rgba(12,13,15,.82);backdrop-filter:saturate(180%) blur(12px);border-bottom-color:var(--hair-d)}
.hdr .in{display:flex;align-items:center;gap:clamp(16px,3vw,40px);height:var(--hdr-h)}
.brand{display:flex;align-items:center;flex:none}
/* The real lockup is a STACKED, near-square artwork (350x340). Its third line
   ("Falcon Group") is only 7.1% of the lockup height, so at the old 36px it was
   a 2.6px smear. 64px is the smallest height at which the wordmark is clean;
   see the measured table in PLACEHOLDER-SWAP-LIST.md. */
.brand .logo{height:var(--logo-h);width:auto;display:block}
.hdr[data-scene="light"]{color:var(--ink)}
.hdr[data-scene="dark"]{color:var(--tx-d)}

/* primary nav + mega-menu */
.nav{display:flex;align-items:center;gap:clamp(18px,2vw,30px);margin-inline-start:auto}
.nav > li{position:relative;list-style:none}
.nav > li > a,.nav > li > button{display:inline-flex;align-items:center;gap:7px;font-family:var(--f-body);
  font-size:.94rem;font-weight:500;color:currentColor;opacity:.78;transition:opacity .2s;padding:8px 0;white-space:nowrap}
.nav > li > a:hover,.nav > li > a.on,.nav > li.open > button,.nav > li > button:hover{opacity:1}
.nav .caret{width:8px;height:8px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;
  transform:translateY(-2px) rotate(45deg);opacity:.55;transition:transform .25s ease}
.nav > li.open .caret{transform:translateY(0) rotate(-135deg)}
.hdr-act{display:flex;align-items:center;gap:14px;flex:none}
.hdr .cta{padding:11px 18px;font-size:.8125rem}

/* mega panel */
.mega{position:absolute;top:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(6px);
  --brand-text:var(--brand-ink);--brand-graphic:var(--brand);   /* mega is always a light surface */
  min-width:600px;background:var(--white);color:var(--tx-l);border:1px solid var(--hair-l);
  box-shadow:0 24px 60px rgba(12,13,15,.14);border-radius:var(--r);padding:26px;
  display:grid;grid-template-columns:1.25fr .9fr;gap:28px;opacity:0;visibility:hidden;pointer-events:none;
  /* fades and rises rather than snapping open */
  transform:translateX(-50%) translateY(10px);
  transition:opacity .28s ease,transform .28s var(--ease),visibility 0s linear .28s;z-index:var(--z-overlay)}
.nav > li.open .mega{opacity:1;visibility:visible;pointer-events:auto;transform:translateX(-50%) translateY(0);
  transition:opacity .28s ease,transform .28s var(--ease),visibility 0s}
.mega::before{content:"";position:absolute;top:-14px;left:0;right:0;height:16px}   /* hover bridge */
.mega-lbl{font-family:var(--f-body);font-weight:600;font-size:.68rem;letter-spacing:.15em;text-transform:uppercase;
  color:var(--brand-ink);margin-bottom:14px;display:block}
.mega-cap{display:block;padding:11px 0;border-bottom:1px solid var(--hair-l)}
.mega-cap:last-child{border-bottom:0}
.mega-cap b{display:flex;align-items:baseline;gap:9px;font-family:var(--f-dsp);font-weight:600;font-size:1.02rem;
  letter-spacing:-.01em;color:var(--tx-l)}
.mega-cap .mn{font-size:.72rem;color:var(--brand-ink);font-variant-numeric:tabular-nums;flex:none}
.mega-cap span.d{display:block;font-size:.82rem;color:var(--tx-l-dim);line-height:1.4;margin-top:3px}
.mega-cap:hover b{color:var(--brand-ink)}
.mega-side{background:var(--off);margin:-26px -26px -26px 0;padding:26px;border-radius:0 var(--r) var(--r) 0;display:flex;flex-direction:column}
.mega-svc{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:6px}
.mega-svc span{font-size:.82rem;font-weight:500;color:var(--tx-l);background:var(--white);border:1px solid var(--hair-l);
  padding:6px 13px;border-radius:var(--r-sm)}
.mega-side p{font-size:.85rem;color:var(--tx-l-dim);line-height:1.5;margin-top:12px}
.mega-side .link-ar{margin-top:auto;padding-top:18px;font-size:.88rem}
/* Secondary items inside a mega panel's side column (IA restructure 2026-07-30).
   Tighter than a primary leaf so the panel keeps one clear hierarchy. */
.mega-cap.compact{padding:9px 0;border-bottom:1px solid var(--hair-l)}
.mega-cap.compact:last-of-type{border-bottom:0}
.mega-cap.compact b{font-size:.9rem}
.mega-cap.compact .d{font-size:.78rem}
.mega-side .mega-cap.compact{background:none}
/* A panel with no secondary column must not reserve one — the Resources panel
   was rendering a 2-track grid with an empty right half. */
.mega.one-col{grid-template-columns:minmax(0,1fr)!important;
  /* the base .mega sets min-width:600px, and min-width beats max-width — so the
     single-column panel has to lower the FLOOR, not just cap the ceiling. */
  min-width:420px;max-width:460px}

.hdr .navtoggle{display:none}
@media (max-width:960px){
  /* A fixed 96px bar costs too much of a phone viewport. 56px keeps "SHAHEEN"
     clean; the "Falcon Group" line degrades, which is the unavoidable cost of a
     stacked lockup with no horizontal variant. */
  :root{--hdr-h:76px;--logo-h:56px}
  .nav,.hdr-act .cta,.hdr-search{display:none}
  .hdr .navtoggle{display:flex;flex-direction:column;gap:5px;width:44px;height:44px;align-items:center;justify-content:center;margin-inline-start:auto}
  .hdr .navtoggle span{width:22px;height:2px;background:currentColor;transition:.3s}
  .hdr .navtoggle.open span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  .hdr .navtoggle.open span:nth-child(2){opacity:0}
  .hdr .navtoggle.open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
  .mnav{position:fixed;inset:0;background:var(--ink);color:var(--tx-d);z-index:var(--z-overlay);
    display:flex;flex-direction:column;justify-content:flex-start;gap:2px;padding:88px var(--gutter) 40px;overflow-y:auto;
    /* was justify-content:center. With the 16-item IA the drawer overflows, and
       centring a scrollable flex column makes the overflowing TOP unreachable. */
    transform:translateY(-100%);transition:transform .5s cubic-bezier(.6,0,.2,1)}
  .mnav.open{transform:none}
  .mnav .mgroup-lbl{font-family:var(--f-body);font-weight:600;font-size:.7rem;letter-spacing:.15em;text-transform:uppercase;
    color:var(--brand-graphic);margin:22px 0 8px}
  .mnav a{font-family:var(--f-dsp);font-weight:600;font-size:1.5rem;padding:8px 0;color:var(--tx-d)}
  /* .mnav is a column flex container, so its DIRECT children become block-level.
   Links nested inside .mnav-sub were not, so every grouped link rendered inline
   and the group read as one run-on paragraph. Pre-existing since the drawer was
   built; found at 390px during the IA restructure. */
.mnav .mnav-sub{display:flex;flex-direction:column;gap:2px}
.mnav .mnav-sub a{display:block;font-size:1.15rem;opacity:.85}
  .mnav .cta{margin-top:26px;align-self:flex-start;font-size:1rem}
  .mnav .cls{position:absolute;top:22px;right:var(--gutter);width:44px;height:44px}
}
@media (min-width:961px){.mnav{display:none}}

/* ---------- mobile sticky action bar (RFQ + WhatsApp) — replaces WA float on mobile ---------- */
.actionbar{position:fixed;left:0;right:0;bottom:0;z-index:var(--z-chrome);display:none;gap:1px;
  background:var(--hair-l);transform:translateY(100%);transition:transform .35s cubic-bezier(.2,.7,.3,1);
  padding-bottom:env(safe-area-inset-bottom)}
.actionbar.show{transform:none}
.actionbar a{flex:1;display:flex;align-items:center;justify-content:center;gap:9px;height:56px;
  font-family:var(--f-body);font-weight:600;font-size:.98rem}
.actionbar .ab-rfq{background:var(--brand-ink);color:#fff}
.actionbar .ab-wa{background:#1FA855;color:#fff;flex:0 0 40%}
.actionbar svg{width:19px;height:19px;flex:none}
@media (max-width:960px){.actionbar{display:flex}}
/* .wa mobile-hide lives after the .wa base rule (below) so it wins the cascade */

/* ==========================================================================
   SIGNATURE DEVICE — "engineered framing": every photograph sits in a container
   with a 1px brand rule on ONE edge, and a tracked mono caption below stating a
   verifiable FACT about what is shown. Unifies mixed imagery; reinforces
   "verified, not claimed". Alternate the edge (--l / --r) by position for rhythm.
   ========================================================================== */
.eshot{margin:0;position:relative}
.eframe{position:relative;border-radius:var(--r);overflow:hidden;background:#0f1114}
.eframe img{display:block;width:100%;height:100%;object-fit:cover}
/* ⚠ 2026-08-18 · PART C — PRODUCT GROUND.
   Was a dark radial gradient PLUS up to 44px of image padding, i.e. exactly
   the "product floating in a padded dark box" that keeps being rejected.
   Two treatments were built and photographed on the real /rotating-equipment/
   tiles before choosing:
     A · light neutral ground, zero padding
     B · blurred copy of the photo itself as the ground, zero padding
   B is tonally seamless and looks cinematic, but it is still a DARK ground —
   the thing being complained about. A won by eye: these parts are photographed
   on near-white, so a light ground is effectively invisible behind them and the
   machined metal reads brightest and cleanest against it.
   The image is CONTAIN and unpadded, so the product is never cropped and meets
   the frame edge on its long axis — no padded-box look. */
.eframe.contain{background:var(--shot-ground)}
.eframe.contain img{object-fit:contain;padding:0}
/* the eframe plate is #0f1114 even inside a light section, so its edge rule
   must take the LIFT step or it drops to 2.85:1 */
.eframe::after{content:none}
.eframe.edge-l::after{left:0;top:0;bottom:0;width:2px}
.eframe.edge-r::after{right:0;top:0;bottom:0;width:2px}
.eframe.edge-t::after{top:0;left:0;right:0;height:2px}
.eframe.edge-b::after{bottom:0;left:0;right:0;height:2px}
.eframe-cap{margin-top:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--tx-l-dim);
  display:flex;align-items:center;gap:9px;line-height:1.4}
.eframe-cap::before{content:"";width:6px;height:6px;background:var(--brand-graphic);flex:none}
.scene-dark .eframe-cap,.p-block.dark .eframe-cap,.why-e .eframe-cap{color:var(--tx-d-dim)}

/* ---------- placeholder honesty flag (reusable — every page) ----------
   Lived in home.css until 2026-07-29, which only loads on the front page, so
   the flag rendered as unstyled inherited ink on a near-black plate (1.03:1,
   invisible) on all 23 product / 6 gallery / 8 video placeholder tiles. It is a
   shared component, so it belongs here. Homepage POSITION overrides stay in
   home.css and still win (app.css is enqueued first). */
.ph-tag{position:absolute;right:12px;bottom:10px;z-index:5;pointer-events:none;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  /* AA FIX 2026-08-16. Was rgba(255,255,255,.62) on rgba(8,9,10,.42). That was
     written when this chip only ever sat over a DARK scrimmed hero video; the
     August drop puts it over 47 real photographs, several of them bright. Sampled
     against the brightest pixel under the chip on the actual composited stack it
     measured 2.03-2.49:1 against the 4.5 a text element needs — the one element
     on the site whose entire job is to be read. A 72% plate under 92% white
     measures 7.4:1 against a pure-white worst case. */
  color:rgba(255,255,255,.92);background:rgba(8,9,10,.72);
  padding:4px 8px;border-radius:3px;backdrop-filter:blur(2px)}
/* Inside a placeholder plate the tile is small and already dark, so the flag
   flows under the label instead of floating over it, and drops the chip. */
.ph-media .ph-tag{position:static;background:none;backdrop-filter:none;
  padding:0;margin-top:2px;color:rgba(255,255,255,.72);font-size:10.5px}

/* --- engineered framing reveals on scroll-in (2026-07-29) ---
   The brand edge draws along its own axis, then the fact caption resolves.
   Gated behind .js so a script failure can never leave a caption invisible;
   JS marks .eframe and .eframe-cap INDIVIDUALLY, so a caption outside .eshot
   still reveals. */
.js .eframe.edge-l::after,.js .eframe.edge-r::after{transform:scaleY(0);transform-origin:center top}
.js .eframe.edge-t::after,.js .eframe.edge-b::after{transform:scaleX(0);transform-origin:left center}
.eframe.rv::after{transform:none;transition:transform .6s var(--ease)}
/* ⚠ 2026-08-18 — the hide-before-reveal gate must NOT reach a caption whose
   reveal observer does not exist. `.eframe-cap.rv` is set by the engineered-
   framing observer, which only walks `.eframe` elements. The B4 gallery
   sliders reuse this caption class on `.gs-shot`, so every gallery caption
   sat at opacity 0 permanently — invisible, with the text present in the DOM.
   Found by measuring computed opacity, not by looking: a backgrounded tab
   shows mid-transition captions as blank anyway, so the eye could not tell
   this apart from the known decode/transition artifacts.
   Scoped to captions that are actually inside an .eframe. Same class of bug
   as the `.js` gate exposure already on file: never gate content on a class
   that some instances can never receive. */
.js .eframe-cap{opacity:0;transform:translateY(6px)}
.eframe-cap.rv{opacity:1;transform:none;transition:opacity .5s ease .12s,transform .5s var(--ease) .12s}
.creds-track{display:flex;align-items:center;gap:clamp(36px,5vw,80px);width:max-content;
  will-change:transform;animation:creds-scroll 42s linear infinite}
/* HOVER PAUSES IN PLACE (fixed 2026-07-31).
   This was `animation-duration:96s`. A CSS animation's position is
   currentTime / duration, and swapping the duration on a LIVE animation keeps
   currentTime — so progress is remapped and the track teleports. Measured on
   the real wall: 20s into the 42s cycle the track sits at -623px; the moment
   the hover rule applied it jumped to -273px, and back again on mouse-out.
   A 350px jump on a 2618px track is roughly a mark and a half, which is exactly
   the "suddenly shows a different name" the client reported.
   animation-play-state cannot do that: it freezes and resumes at the same
   currentTime, so there is nothing to remap. */
.creds:hover .creds-track,
.creds:focus-within .creds-track{animation-play-state:paused}
.creds-item{display:flex;align-items:center;gap:12px;white-space:nowrap;
  font-family:var(--f-dsp);font-weight:600;font-size:clamp(1rem,1.35vw,1.28rem);letter-spacing:-.01em;
  color:var(--tx-l);opacity:.72;transition:opacity .25s ease,transform .35s var(--ease)}
/* the wall recedes while you read one mark, and the mark you are on lifts */
.creds:hover .creds-item{opacity:.42}
.creds-item:hover{opacity:1;transform:translateY(-2px)}
.creds-item .cdot{transition:transform .35s var(--ease)}
.creds-item:hover .cdot{transform:scale(1.5)}
.creds-item .cdot{width:7px;height:7px;border-radius:50%;background:var(--brand-graphic);flex:none}
@keyframes creds-scroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .creds-track{animation:none;flex-wrap:wrap;justify-content:center;width:auto;row-gap:18px}
}

/* ---------- magnetic CTA ---------- */
.cta{position:relative;display:inline-flex;align-items:center;gap:12px;font-family:var(--f-body);font-weight:600;
  font-size:.875rem;letter-spacing:.02em;padding:14px 24px;border-radius:var(--r-sm);color:var(--white);background:var(--brand-ink);
  will-change:transform;transition:background .25s ease}
.cta:hover{background:var(--brand-deep)}
.cta svg{width:17px;height:17px;transition:transform .3s}
.cta:hover svg{transform:translateX(4px)}
.cta.ghost{background:transparent;color:currentColor;padding-left:0}
.cta.ghost::after{content:"";position:absolute;left:0;right:34px;bottom:8px;height:1px;background:currentColor;opacity:.4;transition:right .3s}
.cta.ghost:hover::after{right:0}
.cta.on-dark{color:var(--ink)}
.link-ar{position:relative;display:inline-flex;align-items:center;gap:9px;font-weight:600;color:var(--brand-text)}
.link-ar svg{width:15px;height:15px;transition:transform .3s}
.link-ar:hover svg{transform:translateX(4px)}
/* underline draws in from the left rather than switching on */
.link-ar::after{content:"";position:absolute;left:0;right:24px;bottom:-3px;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:left center;transition:transform .4s var(--ease)}
.link-ar:hover::after,.link-ar:focus-visible::after{transform:scaleX(1)}

/* native cursor is always visible; interactive elements get a pointer */
a,button,.cta,.link-ar,.wa,[role="button"],summary,label{cursor:pointer}
input,textarea,select{cursor:auto}
input[type="text"],input[type="email"],input[type="tel"],textarea{cursor:text}
/* Reveal is progressive enhancement: content is visible by default and only
   hidden once JS is confirmed (html.js). A JS error or blocked script can never
   leave the page blank. */
.js [data-fade]{opacity:0;transform:translateY(20px)}
[data-fade].in{opacity:1;transform:none;transition:opacity .7s ease,transform .7s cubic-bezier(.16,1,.3,1)}
[data-fade][data-d="1"].in{transition-delay:.1s}
[data-fade][data-d="2"].in{transition-delay:.2s}
[data-fade][data-d="3"].in{transition-delay:.3s}
@media (prefers-reduced-motion:reduce){
  /* the split headline: LINE masks and WORD travel both land at final state.
     This rule was collateral when the old wrapper-based .reveal-lines rules were
     removed — without it a reduced-motion visitor could be shown a headline
     translated 112% out of its own mask, i.e. an empty hero. */
  .reveal-lines .word,.reveal-lines .line > *{transform:none!important;transition:none!important}
  [data-fade]{opacity:1!important;transform:none!important;transition:none!important}
  .eframe::after{transform:none!important;transition:none!important}
  .eframe-cap{opacity:1!important;transform:none!important;transition:none!important}
  .link-ar::after{transition:none!important}
  .creds-item,.creds-item .cdot{transition:none!important;transform:none!important}
  .mega{transition:opacity .01s!important}
}

/* ---------- spotlight / torch (the Falcon's Eye) ---------- */
.torch{position:relative;--mx:50%;--my:50%}
.torch .veil{position:absolute;inset:0;z-index:var(--z-raise);pointer-events:none;
  background:radial-gradient(220px 220px at var(--mx) var(--my),transparent 0,rgba(12,13,15,.86) 62%);
  transition:opacity .4s}
.torch:hover .veil{background:radial-gradient(300px 300px at var(--mx) var(--my),transparent 0,rgba(12,13,15,.7) 66%)}

/* ---------- WhatsApp ---------- */
.wa{position:fixed;left:18px;bottom:18px;z-index:var(--z-chrome);width:46px;height:46px;border-radius:50%;
  background:#1FA855;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 24px rgba(0,0,0,.3);transition:transform .25s}
.wa:hover{transform:scale(1.08)}
.wa svg{width:24px;height:24px;fill:#fff}
@media (max-width:520px){.wa{width:44px;height:44px;left:14px;bottom:14px}}
/* on mobile the sticky action bar replaces the WhatsApp float (decision #1) */
@media (max-width:960px){.wa{display:none}}

/* ==========================================================================
   FOOTER (rebuilt 2026-07-28)
   Was: a 3-track grid holding FOUR children, so "Get in Touch" wrapped to an
   implicit second row and left ~598x210px empty. Now an explicit 12-column
   grid with declared spans (4 + 1 spacer + 2 + 2 + 3), so the track count and
   the child count can never disagree again.
   ========================================================================== */
.foot{background:#08090A;color:var(--tx-d);padding-block:var(--sp) 28px}

/* --- top zone: brand + positioning + primary action --- */
.foot-lede{display:grid;grid-template-columns:minmax(0,1.35fr) auto;gap:clamp(24px,4vw,56px);
  align-items:end;padding-bottom:clamp(32px,4vw,48px);border-bottom:1px solid var(--hair-d)}
/* .foot-logo-plate is retired — the supplied REVERSED lockup needs no plate.
   The plate carried the 20px bottom margin, so the image carries it now. */
.foot-logo{height:64px;width:auto;display:block;margin-bottom:20px}
.foot .big{font-family:var(--f-dsp);font-weight:600;font-size:clamp(1.45rem,2.8vw,2.1rem);
  line-height:1.08;letter-spacing:-.022em;max-width:24ch}
.foot .cta{display:inline-flex;width:auto;padding:15px 26px;font-size:.92rem;color:#fff;line-height:1;flex:none}

/* --- structured columns --- */
.foot-top{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
  gap:clamp(28px,3.5vw,44px) clamp(18px,2.2vw,30px);
  padding-block:clamp(36px,4.5vw,56px)}
.foot-brand{grid-column:span 3}
.foot-spacer{display:none}   /* four link columns now fill the row; no spacer needed */
.foot-col{grid-column:span 2}
.foot-col.contact{grid-column:span 3}
.foot-note{font-size:.86rem;line-height:1.55;color:var(--tx-d-dim);max-width:34ch}

.foot h4{font-family:var(--f-body);font-weight:600;font-size:.7rem;letter-spacing:.15em;
  text-transform:uppercase;color:var(--brand-text);margin-bottom:16px}
.foot-col ul{display:flex;flex-direction:column;gap:2px}
.foot a{color:var(--tx-d-dim);font-size:.875rem;line-height:1.4;transition:color .2s}
.foot-col a{display:block;padding:7px 0}
.foot a:hover{color:var(--tx-d)}
/* contact rows are content, not links, unless they are actually actionable */
.foot-col.contact li{padding:7px 0;font-size:.875rem;line-height:1.5;color:var(--tx-d-dim)}
.foot-col.contact li a{display:inline}
.foot-cr{margin-top:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.7rem;letter-spacing:.08em;color:var(--tx-d-dim)}   /* no opacity: it would drop 7.68:1 to ~4.6:1 */

/* --- legal baseline, below a hairline --- */
.foot-base{display:flex;justify-content:space-between;flex-wrap:wrap;gap:10px 28px;
  padding-top:22px;border-top:1px solid var(--hair-d);font-size:.76rem;color:var(--tx-d-dim)}
.foot-base a{font-size:inherit;color:inherit}
.foot-base a:hover{color:var(--brand-text)}
.foot-base .claim{color:var(--tx-d-dim)}
.foot-base .claim b{color:var(--tx-d);font-weight:600}

@media (max-width:1024px){
  .foot-brand{grid-column:span 12}
  .foot-col{grid-column:span 6}
  .foot-col.contact{grid-column:span 6}
}
@media (max-width:760px){
  .foot-lede{grid-template-columns:1fr;align-items:start;gap:26px}
  .foot-col,.foot-col.contact{grid-column:span 12}
}
/* clear the mobile action bar (56px + safe area) */
@media (max-width:960px){.foot{padding-bottom:96px}}

/* ---------- shared CTA row (used by sacom_ctas() on every page) ---------- */
.p-cta{display:flex;flex-wrap:wrap;gap:14px;margin-top:30px;align-items:center}

/* ==========================================================================
   CLOSING CTA (rebuilt 2026-07-28)
   Was: a centred 48ch column (485px) inside a 1152px content area — 42%
   horizontal fill, 37-44% vertical dead space, the heading wrapping to four
   lines and the two CTAs forced to stack. Now a CONTAINED PANEL: statement +
   actions one side, verifiable proof facts the other, height fitted to content.
   ========================================================================== */
/* The panel supplies its own interior padding, so the band around it takes the
   SMALL step — otherwise section padding and panel padding stack and the band
   grows taller than the one it replaced. */
.p-close{padding-block:var(--sp-sm)}   /* G: bg change already separates it */
.p-close.scene-dark{background:var(--ink);color:var(--tx-d);border-top-color:var(--hair-d)}
.p-close.scene-light{background:var(--off)}

.cta-panel{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  gap:clamp(28px,4vw,64px);align-items:center;position:relative;overflow:hidden;
  background:var(--white);border-radius:var(--r-lg);
  padding:clamp(30px,3.6vw,52px) clamp(26px,3.6vw,58px)}
.p-close.scene-dark .cta-panel{background:var(--ink-2)}
.cta-panel h2{margin-top:12px;max-width:17ch}
.cta-panel .lead{margin-top:14px;max-width:44ch;color:var(--tx-l-dim)}
.p-close.scene-dark .cta-panel .lead{color:var(--tx-d-dim)}
.cta-panel .p-cta{margin-top:26px}

/* proof column — verifiable facts, so the space carries substance, not air */
/* label above value: every row shares a left edge, so the column reads as a
   set rather than three independently-indented rows */
.cta-facts{display:flex;flex-direction:column}
.cta-facts li{padding:15px 0;border-top:1px solid var(--hair-l)}
.cta-facts li:first-child{border-top:0;padding-top:0}
.p-close.scene-dark .cta-facts li{border-top-color:var(--hair-d)}
.p-close.scene-dark .cta-facts b{color:var(--brand-graphic)}
.cta-facts span{font-size:.875rem;line-height:1.45;color:var(--tx-l-dim)}
.p-close.scene-dark .cta-facts span{color:var(--tx-d-dim)}

@media (max-width:860px){
  .cta-panel{grid-template-columns:1fr;gap:30px;align-items:start}
  .cta-panel h2{max-width:20ch}
}

/* Visually hidden, still announced. Used for the accessible name on the
   component-explorer hotspots, whose visible affordance is the marker itself.
   There was no such utility in the system — without it the names rendered as
   loose text over the schematic. */
.vh{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.skip{position:absolute;left:-9999px;top:0;background:var(--brand-ink);color:#fff;padding:10px 16px;z-index:var(--z-overlay)}
.skip:focus{left:0}

/* ================= shared inner-page components (cinematic, no boxes) ================= */
/* page hero — full-bleed dark cinematic opening */
.phero{position:relative;min-height:clamp(560px,84svh,880px);display:flex;align-items:flex-end;overflow:hidden;color:var(--tx-d)}
.phero-bg,.phero-bg-v{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:var(--z-base);transform:scale(1.03)}
.phero-grade{position:absolute;inset:0;z-index:1;background:linear-gradient(180deg,rgba(12,13,15,.5),transparent 30% 55%,rgba(12,13,15,.9)),linear-gradient(90deg,rgba(12,13,15,.75),transparent 60%)}
.phero .wrap{position:relative;z-index:var(--z-raise);padding-block:clamp(56px,8vw,120px)}
.phero .eyebrow{margin-bottom:18px}
.phero h1{max-width:15ch}
.phero .lead{margin-top:24px;color:var(--tx-d)}
.phero-cta{margin-top:34px;display:flex;gap:22px;align-items:center;flex-wrap:wrap}

/* prose scenes */
.prose{max-width:64ch}
.prose p + p{margin-top:1.1em}
.split-2{display:grid;grid-template-columns:.85fr 1.15fr;gap:clamp(28px,5vw,72px);align-items:start}
.split-2 .k h2{margin-top:12px}
@media (max-width:860px){.split-2{grid-template-columns:1fr;gap:24px}}

/* full-width typeset spec table (a table is typography, not a card) */
.spec{width:100%;border-collapse:collapse}
.spec caption{text-align:left;font-family:var(--f-body);font-weight:600;font-size:.82rem;letter-spacing:.12em;text-transform:uppercase;color:var(--brand-text);padding-bottom:22px}
.spec th,.spec td{text-align:left;padding:20px 8px;vertical-align:top;font-size:clamp(1rem,1.4vw,1.15rem);border-bottom:1px solid var(--hair-d)}
.scene-light .spec th,.scene-light .spec td{border-bottom-color:var(--hair-l)}
.spec thead th{font-family:var(--f-body);font-weight:600;font-size:.78rem;letter-spacing:.06em;text-transform:uppercase;color:var(--tx-d-dim);padding-bottom:14px}
.scene-light .spec thead th{color:var(--tx-l-dim)}
.spec tbody th{font-family:var(--f-dsp);font-weight:600;color:inherit;width:34%;font-size:clamp(1.05rem,1.6vw,1.35rem)}
.spec tbody td{color:var(--tx-d-dim);font-variant-numeric:tabular-nums}
.scene-light .spec tbody td{color:var(--tx-l-dim)}
.spec .hl{color:var(--brand-text)}
.spec-wrap{overflow-x:auto}

/* capability list — big numbered rows, no boxes */
.caps{margin-top:8px}
.cap{display:grid;grid-template-columns:auto 1fr;gap:clamp(18px,3vw,44px);align-items:baseline;padding:22px 0;border-bottom:1px solid var(--hair-d)}
.scene-light .cap{border-bottom-color:var(--hair-l)}
.cap .cn{font-family:var(--f-dsp);font-weight:600;color:var(--brand-text);font-size:1rem}
.cap .ct{font-family:var(--f-dsp);font-weight:600;font-size:clamp(1.3rem,2.6vw,2rem);letter-spacing:-.01em}
.cap .cd{color:var(--tx-d-dim);margin-top:6px;max-width:60ch}
.scene-light .cap .cd{color:var(--tx-l-dim)}

/* full-bleed figure + caption */
.fbleed{position:relative;width:100%;height:clamp(360px,60vw,720px);overflow:hidden}
.fbleed img,.fbleed video{width:100%;height:100%;object-fit:cover}
.fbleed .cap{position:absolute;left:0;bottom:0;padding:20px var(--gutter);z-index:var(--z-raise);color:#fff;font-weight:500;background:linear-gradient(0deg,rgba(12,13,15,.7),transparent)}
.fig-inline{width:100%;height:clamp(300px,44vw,560px);overflow:hidden}
.fig-inline img{width:100%;height:100%;object-fit:cover}

/* photo strip — full-bleed marquee of real parts (edge to edge, no card chrome) */
.strip{display:flex;gap:2px;width:100%}
.strip .s{flex:1;aspect-ratio:4/3;overflow:hidden;background:#101114;display:flex;align-items:center;justify-content:center}
.strip .s img{width:100%;height:100%;object-fit:contain;object-position:center;padding:14px;filter:grayscale(.1)}   /* contain — whole part visible, no side-clipping */
@media (max-width:900px){.strip{flex-wrap:wrap}.strip .s{flex:1 1 33.333%}}
@media (max-width:560px){.strip .s{flex:1 1 50%}}

/* (pull statement scale defined once, in the type system above) */

/* ---- contact: details column — label + value on a shared top baseline ---- */
.contact-detail{display:flex;flex-direction:column;font-size:1.02rem}
.cdrow{display:grid;grid-template-columns:88px 1fr;gap:22px;align-items:baseline;padding:20px 0;border-bottom:1px solid var(--hair-d)}
.cdrow:first-child{padding-top:0}
.cdlabel{font-family:var(--f-body);font-weight:600;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--brand-text);line-height:1.7}
.cdval{color:var(--tx-d);line-height:1.6}
.cdval a{color:inherit}
.contact-detail .cta{margin-top:26px;align-self:start}
@media (max-width:560px){.cdrow{grid-template-columns:1fr;gap:6px;align-items:start}.cdlabel{line-height:1.2}}

/* ---- contact: underline fields (no boxed inputs) ---- */
.rform{display:grid;gap:34px;max-width:640px}
.rrow{display:grid;grid-template-columns:1fr 1fr;gap:34px}
@media (max-width:560px){.rrow{grid-template-columns:1fr}}
.field{position:relative}
.field label{display:block;font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--tx-d-dim);margin-bottom:12px}
.scene-light .field label{color:var(--tx-l-dim)}
.field input,.field select,.field textarea{width:100%;background:transparent;border:0;border-bottom:1.5px solid var(--hair-d);color:inherit;font:inherit;font-size:1.05rem;padding:8px 0;transition:border-color .25s}
.scene-light .field input,.scene-light .field select,.scene-light .field textarea{border-bottom-color:var(--hair-l)}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-bottom-color:var(--brand-graphic)}
.field textarea{min-height:96px;resize:vertical}
.field select{cursor:pointer}
.scene-dark .field select option{color:#111}
.rhp{position:absolute;left:-9999px}
.form-status{min-height:1.3em;font-size:.95rem}
.form-status.ok{color:#3ecf8e}.form-status.err{color:#C0392B}   /* error state: deliberately NOT brand green */
.contact-detail a:hover{color:var(--brand-text)}
.map-bleed{width:100%;height:clamp(320px,40vw,480px)}
.map-bleed iframe{width:100%;height:100%;border:0;filter:grayscale(.4) invert(.9) contrast(.9)}

/* legal / privacy — clean typeset */
.legal{max-width:72ch}
.legal h2{font-family:var(--f-dsp);font-weight:600;font-size:clamp(1.3rem,2.4vw,1.7rem);margin:2em 0 .6em}
.legal p,.legal li{color:var(--tx-l-dim);margin-bottom:1em;line-height:1.7}
.scene-dark .legal p,.scene-dark .legal li{color:var(--tx-d-dim)}
.legal ul{list-style:disc;padding-left:22px}
.legal strong{color:inherit}
/* compact single-column mega (Resources) */
.mega.mega-sm{grid-template-columns:1fr;min-width:340px}

/* ---------- the signature tile treatment ----------
   ::before, because ::after is the engineered-framing brand edge rule on every
   one of these surfaces. z-index 1 keeps it above the image but below the edge
   rule, the tick and the title. */
.eframe:not(.contain)::before,
.inds-tile::before,
.gal-shot::before,
.prod-media::before,          /* both real AND placeholder, so the swap is seamless */
.vid-shot::before,
.bcard-media::before,
.sector-media::before,
.p-hero-shot::before{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:var(--tile-grad);border-radius:inherit}

/* ==========================================================================
   LINE-ART — the mandate's third brand element (2026-07-30)
   Brand Guide p5 names TECHNICAL DRAWINGS alongside the Centaur and the F Bow;
   p7 says the motif "becomes the central theme in all applications". Applied in
   exactly two places, per the approved plan:
     1. as a dark-scene background field (mirrors the mandate's own p7 treatment)
     2. as a hairline arc-fragment accent beside section openers
   Deliberately NOT used over light content sections: at any opacity visible on
   white it competes with body text and invalidates the measured AA ratios.
   ========================================================================== */
.la-field{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;
  fill:none;stroke:currentColor;stroke-width:1;z-index:0}
.la-field circle{opacity:.55}
.la-field .la-fine{stroke-width:.7;opacity:.34}
.la-field .la-ring{opacity:.5}
.la-field .la-mark{stroke:var(--brand-lift);stroke-width:1.2;opacity:.6}
.la-field .la-dim{stroke-dasharray:10 5 2 5;opacity:.4}

/* the host section decides the strength; the mandate shows light lines on dark */
.has-lineart{position:relative;isolation:isolate}
.has-lineart > .la-wrap{position:absolute;inset:0;overflow:hidden;pointer-events:none;
  color:#fff;opacity:.07;z-index:0}
.has-lineart > *:not(.la-wrap){position:relative;z-index:1}

/* --- hairline arc accent beside section openers --- */
.la-accent{display:block;width:44px;height:44px;margin-bottom:10px;color:var(--tx-l);
  fill:none;stroke:currentColor;overflow:visible}
.la-accent .la-ring{stroke-width:1.1;opacity:.36}
.la-accent .la-fine{stroke-width:.8;opacity:.24}
.la-accent .la-mark{stroke:var(--brand-graphic);stroke-width:1.2;opacity:.85}
.scene-dark .la-accent,.p-block.dark .la-accent{color:#fff}
.js .la-accent{opacity:0;transform:translateY(6px)}
.js .la-accent.rv{opacity:1;transform:none;transition:opacity .5s var(--ease),transform .5s var(--ease)}
@media (prefers-reduced-motion:reduce){.js .la-accent{opacity:1;transform:none}}

/* ==========================================================================
   C · TECHNICAL-DRAWING REVEAL on section openers (2026-07-30)
   The accent DRAWS along its own path, then the eyebrow and heading resolve.
   Sequenced off the block's existing .in class (added by the data-fade observer),
   so there is no second observer and no second chance to strand content.

   SAFETY: opacity stays owned by the block-level fade. Children are offset by
   TRANSFORM only, and the transform is applied under .js — so if app.js dies and
   the failsafe strips .js, every child is already at its final position.

   NOTE: this is the arc-and-crosshair drawing glyph, NOT the horizontal eyebrow
   dash the client rejected in §1.1. That rule stays retired.
   ========================================================================== */
.js .la-accent path{stroke-dasharray:120;stroke-dashoffset:120}
.p-head.in .la-accent path,.mech-head.in .la-accent path,
.la-accent.rv path{stroke-dashoffset:0;transition:stroke-dashoffset .62s var(--ease)}
.p-head.in .la-accent path:nth-child(2),.mech-head.in .la-accent path:nth-child(2){transition-delay:.07s}
.p-head.in .la-accent path:nth-child(3),.mech-head.in .la-accent path:nth-child(3){transition-delay:.13s}
.p-head.in .la-accent path:nth-child(4),.mech-head.in .la-accent path:nth-child(4){transition-delay:.19s}

/* the heading resolves after the line has been drawn */
.js .p-head > .eyebrow,.js .mech-head > .eyebrow{transform:translateY(7px)}
.js .p-head > h2,.js .mech-head > h2{transform:translateY(9px)}
.js .p-head > .lead{transform:translateY(9px)}
.p-head.in > .eyebrow,.mech-head.in > .eyebrow{transform:none;transition:transform .5s var(--ease) .26s}
.p-head.in > h2,.mech-head.in > h2{transform:none;transition:transform .55s var(--ease) .34s}
.p-head.in > .lead{transform:none;transition:transform .55s var(--ease) .42s}

@media (prefers-reduced-motion:reduce){
  .js .la-accent path{stroke-dasharray:none;stroke-dashoffset:0}
  .js .p-head > .eyebrow,.js .mech-head > .eyebrow,
  .js .p-head > h2,.js .mech-head > h2,.js .p-head > .lead{transform:none;transition:none}
}

/* ---- logo: scene knockout, not a white plate (2026-07-31) ----
   Dark scenes previously showed the black lockup on a white box, which over the
   video hero read as a sticker. Both variants are now in the DOM, stacked, and
   only one is painted per scene — no plate, no filter, no layout shift, and the
   swap is a pure opacity change so it cannot reflow. */
.brand{position:relative;display:inline-flex;align-items:center;flex:none}
.brand .logo{height:var(--logo-h);width:auto;display:block;transition:opacity .3s ease}
.brand .logo-kn{position:absolute;left:0;top:50%;transform:translateY(-50%)}
.hdr[data-scene="light"] .logo-dk{opacity:1}
.hdr[data-scene="light"] .logo-kn{opacity:0}
.hdr[data-scene="dark"]  .logo-dk{opacity:0}
.hdr[data-scene="dark"]  .logo-kn{opacity:1}
@media (prefers-reduced-motion:reduce){.brand .logo{transition:none}}

/* ---- CTA band, form variant (2026-07-31) ---------------------------------
   Left carries claim + action + proof; right carries the form. Fields use the
   same underline treatment as the contact and RFQ pages, so a visitor meets one
   form language across the site rather than three. */
.cta-panel:has(.cta-form){grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);align-items:start}
.cta-facts-inline{margin-top:clamp(24px,2.6vw,34px);border-top:1px solid var(--hair-l);padding-top:6px}
.p-close.scene-dark .cta-facts-inline{border-top-color:var(--hair-d)}
.cta-facts-inline li{padding:12px 0}
.cta-facts-inline b{font-size:clamp(.98rem,1.25vw,1.14rem)}
.cta-form{display:grid;gap:16px;min-width:0}
.cta-form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.cta-form .field label{display:block;font-size:.72rem;letter-spacing:.09em;text-transform:uppercase;
  color:var(--tx-l-dim);margin-bottom:7px}
.p-close.scene-dark .cta-form .field label{color:var(--tx-d-dim)}
.cta-form input,.cta-form select,.cta-form textarea{width:100%;background:transparent;border:0;
  border-bottom:1.5px solid var(--hair-l);color:inherit;font:inherit;font-size:1rem;padding:7px 0;
  transition:border-color .25s}
.p-close.scene-dark .cta-form input,.p-close.scene-dark .cta-form select,
.p-close.scene-dark .cta-form textarea{border-bottom-color:var(--hair-d)}
.cta-form input:focus,.cta-form select:focus,.cta-form textarea:focus{outline:none;
  border-bottom-color:var(--brand-graphic)}
.cta-form textarea{min-height:64px;resize:vertical}
.cta-form .p-consent{display:grid;grid-template-columns:auto 1fr;gap:10px;align-items:start;
  font-size:.82rem;line-height:1.45;color:var(--tx-l-dim)}
.cta-form .p-consent a{color:var(--brand-ink);text-decoration:underline}
.cta-form .p-consent input{width:16px;height:16px;margin-top:2px;accent-color:var(--brand)}
.cta-form .cta{justify-self:start;margin-top:2px}
.cta-form .form-status{min-height:1.3em;font-size:.9rem}
@media (max-width:860px){
  .cta-panel:has(.cta-form){grid-template-columns:1fr}
  .cta-form-row{grid-template-columns:1fr}
}


/* ==========================================================================
   PLACEHOLDER MEDIA — moved here from pages.css (2026-07-31)
   THIRD occurrence of the same architectural bug in this project: a shared
   render helper whose CSS lived in a PAGE-SCOPED stylesheet. pages.css does not
   load on the homepage, so the first time sacom_ph_media() was used there the
   placeholder rendered completely unstyled — a giant black picture glyph on
   white. `.ph-tag` did exactly this twice before and CLAUDE.md already records
   the rule; the base plate had simply never been moved with it.
   Anything sacom_ph_*() renders belongs in app.css. Page-specific POSITION
   overrides may stay in the page sheet; the component itself may not.
   ========================================================================== */
.ph-media{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:10px;width:100%;height:100%;min-height:130px;border-radius:inherit;overflow:hidden;text-align:center;
  background:radial-gradient(120% 100% at 50% 0%,#1b1f1d 0%,#121614 58%,#0e1110 100%)}
/* §NA — the placeholder plate's 2px green left bar is removed: it is exactly
   the decorative accent line the rule bans. The plate keeps its ground, its
   mono label and the honesty tag, which is what actually does the work. */
.ph-media .ph-corner{display:none}
/* The placeholder's own green radial glow is RETIRED (2026-07-30). With the shared
   --tile-grad device now on the tile container, a placeholder carried three green
   layers — greenish plate + this glow + the shared gradient — and read visibly
   greener than the real photographs beside it. The shared gradient is the single
   source of brand tint; the plate and edge rule still identify it as a placeholder. */
.ph-mark{width:clamp(26px,3vw,36px);height:auto;color:var(--brand-lift);opacity:.62;position:relative;z-index:1}
.ph-label{position:relative;z-index:1;max-width:88%;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;line-height:1.4;color:#9DA6A1}
.ph-media.is-video .ph-mark{opacity:.78}


/* ---- placeholder DOWNLOAD (moved from pages.css 2026-07-31) ----
   Found by the proactive audit after .ph-media broke: this is the same helper
   family — sacom_ph_download() is rendered by the same sitewide block file — and
   its styles lived in the page-scoped sheet too. It has not broken yet only
   because no homepage template has used a download placeholder. Moved before it
   does. The rest of the page-scoped set (p-hero-*, p-step*, figblock-*, crumb)
   is inner-page-only by construction and is documented in visual-audit.md
   rather than moved, because relocating 40 rules risks 26 inner pages for no
   current benefit. */
.p-dl{display:flex;align-items:center;gap:14px;padding:18px;border:1px solid var(--hair-l);border-radius:var(--r);transition:border-color .2s,background .2s}
.p-dl:hover{border-color:var(--brand-graphic);background:var(--off)}
.p-dl svg{width:26px;height:26px;color:var(--brand-ink);flex:none}
.p-dl b{font-family:var(--f-dsp);font-weight:600;font-size:.98rem;color:var(--tx-l);display:block}
.p-dl span{font-size:.78rem;color:var(--tx-l-dim)}
/* ---------- download button with no file yet ---------- */
.p-dl.is-pending span{display:flex;flex-direction:column}
.p-dl.is-pending span span{font-size:.76rem;color:var(--tx-l-dim);letter-spacing:.04em}
.p-dl.is-pending svg{color:var(--brand-ink)}

/* The marquee mask is a percentage of the track's width, so on a 390px screen a
   7% fade is only ~27px — not enough to fade a word out, so marks were HARD-CUT
   mid-word at the edge and read as broken rather than continuous. The fade is
   widened on narrow screens where there is proportionally less room. */
.creds{overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 7%,#000 93%,transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0,#000 7%,#000 93%,transparent 100%)}
/* ⚠ 2026-08-12 — WIDENING THE FADE WAS TRIED AND CANNOT WORK AT PHONE WIDTHS.
   The brief was to widen it again, as on 2026-07-31. It was widened to
   `min(40%,132px)`, deployed and LOOKED AT: still truncated at both edges.

   The arithmetic is decisive, and the earlier fix was tuned against a wrong
   number. A fade can only dissolve a mark if it is at least as wide as that
   mark; anything beyond the fade zone paints at FULL opacity, so you read a
   word fragment rather than a mark fading out. The widest mark here is
   **"Royal Commission for Jubail & Yanbu" at 302px** — measured on the rendered
   wall, where an earlier 132px reading had accidentally measured boxes already
   clipped by the viewport. 302px does not fit inside a 360px screen alongside
   any usable opaque region, so there is no fade width that solves this: with
   ~1300px of marks in a 360-414px window you are always looking at part of a
   mark at both edges.

   So the marquee is retired below 720px in favour of the STATIC WRAPPED LIST
   that already exists for reduced motion. All five earned credentials are
   readable at once, nothing is truncated, and a battery-powered device stops
   running a perpetual 42s animation. The desktop marquee is untouched. */
@media (max-width:720px){
  .creds{-webkit-mask-image:none;mask-image:none}
  .creds-track{animation:none;flex-wrap:wrap;justify-content:center;
    width:auto;gap:14px 24px;row-gap:14px}
}
/* THE DUPLICATE HALF MUST BE HIDDEN WHENEVER THE TRACK WRAPS.
   The marquee needs two copies of the list to loop seamlessly (front-page.php
   renders the set twice, the second carrying aria-hidden). Wrapping the track
   without hiding that copy shows **every credential twice** — which is a
   PRE-EXISTING defect in the reduced-motion treatment, not something this
   change introduced; it has been shipping to reduced-motion users since the
   wall was built. Fixed for both states here. */
.creds-track .creds-item[aria-hidden="true"]{display:none}
@media (min-width:721px){
  /* the marquee needs the duplicate back, unless motion is reduced */
  .creds-track .creds-item[aria-hidden="true"]{display:flex}
}
@media (min-width:721px) and (prefers-reduced-motion:reduce){
  .creds-track .creds-item[aria-hidden="true"]{display:none}
  .creds{-webkit-mask-image:none;mask-image:none}
}

/* `white-space:nowrap` was safe when this label was the single word "ONLY". The
   claim label is longer, so nowrap could push it out of a narrow facts column —
   it now wraps, and only the numeric facts stay unbreakable. */
.cta-facts b{font-family:var(--f-dsp);font-weight:600;letter-spacing:-.02em;
  font-size:clamp(1.05rem,1.45vw,1.28rem);color:var(--brand-ink);
  font-variant-numeric:tabular-nums;text-wrap:balance;display:block;margin-bottom:5px}

/* ==========================================================================
   §M4 · TOUCH ERGONOMICS — SITEWIDE (2026-08-12, mobile pass)
   Lives HERE and not in home.css deliberately. Everything below is emitted by
   sitewide helpers (sacom_ph_media / the engineered-framing caption / the
   footer + drawer nav) on 19 pages, and home.css only loads on the front page.
   A shared component styled from a page-scoped stylesheet looks correct exactly
   where it was built and nowhere else — the bug this project has already
   recorded three times. Appended, never spliced.

   Measured across all 19 pages at 390px before changing anything: zero
   horizontal overflow anywhere, and that is unchanged — but a consistent 18
   controls per page under 44px, all of them nav and footer links at 31-34px,
   plus text as small as 9px.
   ========================================================================== */
@media (max-width:720px){
  /* --- text that was too small to read on a handset --- */
  /* The honesty flag was 9px at <=560px. 9px is not a size, it is a rumour, and
     a marker nobody can read cannot do the job the marker exists to do. */
  .ph-tag{font-size:10.5px;letter-spacing:.09em;padding:4px 7px}
  /* `.ph-media .ph-tag` (the flag INSIDE a placeholder plate) is a two-class
     selector and beat the single-class rule above, so every plate on every
     inner page kept its 9px. Matched at the same specificity here. */
  .ph-media .ph-tag{font-size:10.5px}
  .ph-label{font-size:11.5px}
  /* The mobile drawer's group labels. They are labels, not body copy, but at
     10.9px they were the smallest type a visitor meets while navigating. */
  .mega-lbl,.mnav .mgroup-lbl{font-size:12px;letter-spacing:.12em}
  /* The engineered-framing captions carry VERIFIABLE FACTS (part, spec,
     location) — 26 of them on /gallery/ alone — and were set at 11.2px. */
  .eframe-cap{font-size:12.2px;letter-spacing:.07em;line-height:1.45}
  .foot-cr{font-size:12.2px}
  /* The footer's column headings — the labels a visitor navigates BY. */
  .foot-col h4,.foot-brand h4{font-size:12.2px;letter-spacing:.12em}
  /* `.p-cell .n` was written here and MOVED to pages.css. It is an inner-page
     component, and pages.css is enqueued after app.css, so an identical rule
     here loses on source order and silently does nothing. Verified by
     re-measuring, not assumed. */

  /* --- tap targets ---
     44px is the floor. These are BLOCK navigation links in a list, not inline
     links inside a sentence: an inline link takes its line box and is exempt,
     which is why the rules below are scoped to the nav/footer lists rather
     than applied to `a` generally. */
  .foot-col a{padding:11px 0;min-height:44px;display:flex;align-items:center}
  .foot-col.contact li a{display:inline;min-height:0;padding:0}
  .foot-col.contact li{padding:11px 0}
  .mnav a{padding:11px 0;min-height:44px;display:flex;align-items:center}
  .mnav .mnav-sub a{min-height:44px}
  .mnav .cta{min-height:48px;display:inline-flex;align-items:center}
  /* Every button on the site measured 43.6px — under the floor by less than
     half a pixel, which no visual check would ever surface. */
  .cta{min-height:46px}
  /* The drawer grew by ~13px a link across ~16 links; it already scrolls
     (justify-content:flex-start was set for exactly this reason), so this
     cannot clip the last item — re-verified at 360, 390 and 414. */

  /* The breadcrumb is a 21px row of small links at the top of every inner page.
     Its type stays as designed; the ROW gains height so the links are hittable
     without the crumb becoming a visual element it was never meant to be. */
  .crumb a,.crumb [aria-current]{display:inline-block;padding-block:12px}
  /* 11px landed it on 43 — one pixel short, which is exactly the kind of miss
     that a "looks about right" pass keeps and a measurement catches. */

  /* Form controls were 37-41px. A 37px select is the single hardest control to
     hit on this site and it sits in the RFQ path.
     The two real form classes are `.p-form` (RFQ + contact) and `.cta-form`
     (the enquiry form in the closing band, which is on every page). An earlier
     draft of this rule guessed `.rfq-form`, which does not exist — caught by
     re-measuring and finding the homepage inputs still at 41px. */
  .p-form input:not([type=checkbox]),.p-form select,.p-form textarea,
  .cta-form input:not([type=checkbox]),.cta-form select,.cta-form textarea{min-height:48px}
  /* The consent checkbox is 16-18px. The BOX stays that size — a giant checkbox
     looks wrong — so its label carries the target, which is what a thumb aims
     at anyway. Scoped to the consent label ONLY: `.p-form label` would have
     caught every field label, which sits ABOVE its input, and turning those
     into 44px flex rows would have wrecked the form's rhythm. */
  .p-consent{min-height:44px;padding-block:6px}
  /* `.cta-form .p-consent input` is a two-class selector and beat a bare
     `.p-consent input`, so the closing-band checkbox stayed 16px on every page.
     Matched at equal specificity — the same cascade miss as `.ph-media .ph-tag`
     above, found the same way: by re-measuring instead of assuming. */
  .p-consent input,.cta-form .p-consent input{width:22px;height:22px}
}

/* ==========================================================================
   §CM · ILLUSTRATED SERVICE / PRODUCT CARDS (2026-08-16)
   The client's August image drop gave a named picture for most of the service
   and product names that were rendering as bare text chips. This is the one
   component that shows them.

   It lives in app.css because it is used on four capability pages AND on
   /specialised-products/ — putting it in pages.css would have been correct
   today and wrong the first time the homepage used it, which is exactly how
   .ph-media, .ph-tag and .p-dl each broke once already.
   ========================================================================== */
/* FIXED column counts, not auto-fill. auto-fill laid down 5 tracks at 1512 for a
   12-item list, orphaning 2 cards on a third row beside 3 empty tracks — the
   footer defect and the videos `auto-fill` defect, for the third time. 4 / 3 / 2
   divides every list on the site (7, 12, 13) without an orphan row. */
.svc-grid{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(16px,1.8vw,24px)}
.svc{display:flex;flex-direction:column;gap:11px;min-width:0}
.svc-media{position:relative;display:block;aspect-ratio:4/3;overflow:hidden;
  border-radius:var(--r);background:#0f1114}
/* The engineered-framing edge, on the same axis as .prod-media so a page that
   mixes the two reads as one system. --brand-lift, not --brand: the plate is
   #0f1114 even inside a light section, where the base green drops to 2.85:1. */
.svc-media::after{display:none}
.svc-media img{display:block;width:100%;height:100%;object-fit:cover;
  transition:transform .6s var(--ease)}
.svc-media img.fit-contain{object-fit:contain;padding:clamp(12px,2vw,22px)}
.svc:hover .svc-media img{transform:scale(1.04)}
.svc-name{font-family:var(--f-dsp);font-weight:600;font-size:1rem;line-height:1.32;
  letter-spacing:-.012em;color:var(--tx-l)}
.scene-dark .svc-name,.p-block.dark .svc-name{color:var(--tx-d)}
/* A placeholder plate has no photograph to crop, so it keeps its own minimum
   height rather than being forced into the 4:3 box at a tile size where the
   label would not fit. */
.svc.no-img .svc-media{background:none}
.svc.no-img .ph-media{position:absolute;inset:0}

@media (max-width:1079px){
  .svc-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
}

@media (max-width:760px){
  /* One column at 390 would run a 13-item list to ~3,600px. Two columns keep
     the section scannable; the name stays at full size and simply wraps. */
  .svc-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
  .svc-name{font-size:.92rem}
}

/* Photographs on the products page fill their frame. The pre-existing tiles are
   CUT-OUTS on a dark plate and must stay contain-fit — applying cover to those
   is the mistake already recorded for 2026-07-28, where it rendered them as
   black plates. The modifier is opt-in per item, never global. */
.prod.photo .prod-media img{object-fit:cover;padding:0}

/* ==========================================================================
   §M5 · FOOTER TAP TARGETS (2026-08-16)
   Found by re-measuring at 360/390/414, not by reading the CSS: four footer
   links were still 15-17px tall on EVERY page — the phone number, the email,
   LinkedIn and Privacy Policy. The 2026-08-12 mobile pass raised the nav
   columns, the crumb and the form controls, all of which are matched by a
   class; these four carry no class of their own and were missed by every
   class-scoped rule. The height comes from padding so nothing moves on desktop.
   ========================================================================== */
@media (max-width:720px){
  .foot-col.contact ul a,.foot-base a{display:inline-block;padding-block:13px;min-height:44px}
  /* address and the hours line are not links and keep their own rhythm */
  .foot-col.contact ul li{line-height:1.5}
}

/* The contact page's phone numbers and email are the primary action on the page
   for a visitor holding a phone, and they were 18px tall inline links inside a
   .p-defs value. WCAG 2.2 exempts inline links in a sentence, so the sweep is
   right not to call this a failure — but "technically exempt" is not the same as
   "hittable", and these are not prose, they are the contact details.
   The consent-label and prose links are left inline on purpose: those ARE
   sentences, and boxing them would break the line. */
@media (max-width:720px){
  .p-defs span a[href^="tel:"],.p-defs span a[href^="mailto:"]{
    display:inline-block;padding-block:12px;min-height:44px}
}

/* ==========================================================================
   §X · AUTHORED SERVICE SCHEMATICS (2026-08-17)
   Two automation tiles have no photograph they can honestly carry, so they
   were rendering as sacom_ph_media() plates — a dark box with the browser's
   generic PICTURE GLYPH, a mono label and "INDICATIVE VISUAL". Bhavana read
   them as broken images sitting beside six tiles that have photographs, which
   is exactly what they looked like.
   They now take an authored drawing (inc/svc-lineart.svg.php) with its own
   designation inside the panel, so the tile reads as a diagram that is meant
   to be a diagram.
   ⚠ Lives in app.css, not pages.css: .svc-* is used on four capability pages
   AND /specialised-products/, and page-scoped CSS for a shared component is
   the bug this project has now hit five times.
   ========================================================================== */
.svc-media-draw{background:radial-gradient(74% 74% at 50% 46%,#191d23 0%,#0f1114 78%)}
.svcl-fig{display:block;width:100%;height:100%;
  padding:clamp(10px,1.6vw,20px) clamp(10px,1.6vw,20px) clamp(24px,2.6vw,32px);
  color:var(--brand-lift)}
/* The designation. It is the label that stops a drawing being mistaken for a
   missing photograph, so it is a real text element, not a dimmed one — the
   .ph-tag lesson: the one element whose whole job is to be read must not be
   the least legible thing on the plate. */
.svc-desig{position:absolute;left:10px;bottom:8px;z-index:4;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  padding:3px 7px;border-radius:3px;
  color:rgba(255,255,255,.92);background:rgba(8,9,10,.72)}
/* no zoom-on-hover: there is no photograph to zoom, and scaling linework
   thickens nothing and just makes the drawing drift under the frame */
.svc:hover .svc-media-draw .svcl-fig{transform:none}
@media (max-width:760px){
  .svcl-fig{padding:8px 8px 22px}
  .svc-desig{font-size:11px;left:8px;bottom:6px}
}

/* The arrow's default size — see sacom_arrow() in blocks.php. A single-class
   selector, so every existing per-component override still wins. Without this
   an unstyled consumer renders it at its intrinsic 54px, which has happened
   three times. */
.ar{width:16px;height:16px;flex:none}

/* §B7 · svc cards go single-column on a phone.
   Measured at 390: `.svc-grid` held TWO 168px tracks, and a `.svc` carries a
   4:3 image, a display-weight name and a designation label — at 168px the
   name wrapped to three lines and the card read as crushed. The 4/3/2 ladder
   was right down to tablet and simply had no final step. One column below
   560px; the ladder above is untouched. */
@media (max-width:560px){
  .svc-grid{grid-template-columns:minmax(0,1fr);gap:16px}
}
