/*
 * Phase 3 RESET -- visible-bug overrides
 *
 * Loaded AFTER css/spax.css (the frozen design system token bundle) so cascade wins.
 * Each rule below addresses a bug from RESEARCH.md section D + Plan 02 INVENTORY.md.
 * Bug numbering matches RESEARCH section D.
 *
 * IMPORTANT: every rule cites which bug it fixes inline. If a fix becomes
 * unnecessary (e.g. INVENTORY shows current state matches the upstream
 * reference), document the deletion reason in this file's git history.
 *
 * BUILD-04 invariant: this file does NOT modify css/spax.css. New non-hashed
 * rules introduced here are not subject to the CSS contract test (which only
 * verifies that JS-referenced HASHED classes resolve in css/spax.css).
 */

/* =====================================================================
 * Bug 1: section title contrast
 *
 * Several section titles render at insufficient contrast against their
 * wrapper background:
 *   - Highlights  ".Highlights_sectionTitle__YJS1U" -- wrapper bg:black,
 *     title has font rule but NO color rule -> inherits browser default
 *     (black) -> black-on-black = invisible
 *   - VideoSection ".VideoSection_title__cMgaj" -- title sits on the
 *     transparent canvas region; spax.css supplies font but no color
 *   - FeatureCards bare <h2> ("What we deliver") -- no class hash assigned
 *     in markup; flex-child of FeatureCards_wrapper__sctdU on white bg
 *     renders unstyled and is visually swallowed
 *
 * Fix policy:
 *   - On dark wrappers (Highlights, BusinessModel) -> color:var(--color-light-blue)
 *     (matches AccordionSection's existing intentional palette)
 *   - On light wrappers (FeatureCards, BuildingFuture) -> color:var(--color-black)
 *   - VideoSection title rides over the canvas; force white for legibility
 *
 * AccordionSection_title__cJwJ_ ("Trusted by") + AccordionSection_accordionTitle__rrUUn
 * ("Featured projects") already inherit color:var(--color-light-blue) from
 * .AccordionSection_left___jPIf (verified against the frozen rule);
 * intentionally NOT overridden here -- that palette is reference-correct.
 * ===================================================================== */
.Highlights_sectionTitle__YJS1U {
  /* dark wrapper Highlights_wrapper__GVmnJ has background:var(--color-black) */
  color: var(--color-light-blue);
}
.BusinessModel_top__3DoQ0 .BusinessModel_title__0Ay0g {
  /* dark wrapper BusinessModel_wrapper__Fogfa has background:var(--color-black) */
  color: var(--color-light-blue);
}
/* VideoSection_title__cMgaj: spax.css already has color:var(--color-white) — correct for
 * the black videoContainer expanded state. No override needed.
 * VideoSection_videoContainer__PvaTp: intentionally starts hidden via GSAP autoAlpha:0
 * in VideoSection.js. No CSS override needed; JS controls its lifecycle. */
.BuildingFuture_title__yzpbD {
  /* light wrapper BuildingFuture_wrapper__5FwHT has background:var(--color-white) */
  color: var(--color-black);
}
.FeatureCards_wrapper__sctdU > h2 {
  /* bare h2 ("What we deliver") on white wrapper -- spax.css has no rule for it.
   * Match the typography size of sibling section titles + force black for contrast. */
  color: var(--color-black);
  font-family: LayGrotesk-Semibold, sans-serif;
  font-weight: 400;
  font-size: calc(48 * calc(1 * var(--vr)) * 1);
  line-height: calc(98 / 100 * 48 * calc(1 * var(--vr)) * 1);
  letter-spacing: calc(-0.48 * var(--vr));
  margin: 0 0 calc(46 * var(--vr));
  width: 100%;
  flex: 0 0 100%;
}

/* =====================================================================
 * Bug 3: footer layout overlap
 *
 * css/spax.css defines .Footer_content__9KGB4 as a 4-column grid with
 * grid-template-areas: "logo . textLogo textLogo" / ". . navList navList" / ". . . ." / "titleContainer . linksContainer designMentions"
 * and gives only these children explicit grid-area names:
 *   .Footer_logo__x3Zdo               -> logo
 *   .Footer_textLogo__PQKYe           -> textLogo (NOT in our markup)
 *   .Footer_navList__fICF8            -> navList   (we have TWO of these -> collide)
 *   .Footer_titleContainer__hEgIR     -> titleContainer
 *   .Footer_linksContainer__K_nCe     -> linksContainer (NOT in our markup)
 *   .Footer_designMentions__eYRVZ     -> designMentions
 *
 * Result with current SPAX markup: the offices <ul>, the nav <ul>, the
 * legalLinks <ul>, the socialsLinks <ul>, and the rights <p> all fall back
 * to grid-area:auto and stack at row 1 / column 1 -> visual overlap.
 *
 * Fix policy: keep BUILD-04 frozen-spax.css intact and force a vertical
 * full-width fallback for every direct child of Footer_content. Each list /
 * paragraph occupies its own row.
 * ===================================================================== */
/* Footer flex-column override REMOVED 2026-05-07.
   The override was a workaround for missing markup (Footer_textLogo,
   Footer_linksContainer were absent). After the footer.html restructure
   added all icomat children + grid-areas, the spax.css 4-column grid lays
   out correctly without the override. */

/* Light styling for the new linksContainer + nav items (icomat doesn't ship
   these and we want them legible). */
.Footer_wrapper__r9Ps3 .Footer_navList__fICF8 {
  list-style: none;
  padding: 0;
  margin: 0;
}
.Footer_wrapper__r9Ps3 .Footer_subLinks__8uF5P {
  list-style: none;
  padding: 0;
  margin: calc(8 * var(--vr)) 0 0 calc(16 * var(--vr));
}
.Footer_wrapper__r9Ps3 .Footer_navList__fICF8 a,
.Footer_wrapper__r9Ps3 .Footer_navList__fICF8 button,
.Footer_wrapper__r9Ps3 .Footer_legalLinks__26cwe a,
.Footer_wrapper__r9Ps3 .Footer_socialsLinks__WpSUM a {
  color: inherit;
  text-decoration: none;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.Footer_wrapper__r9Ps3 .Footer_legalLinks__26cwe ul,
.Footer_wrapper__r9Ps3 .Footer_socialsLinks__WpSUM {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* =====================================================================
 * Bug 4: FeatureCards black-bar artifact
 *
 * css/spax.css frozen rule (verified): .FeatureCards_cardTitle__qSosC
 * sets { background:var(--color-black); color:var(--color-white) }. In
 * the upstream design system this class is applied to a wrapper-style
 * "card title" variant where the entire card body is a black panel.
 * Our SPAX markup applies the class to the <h3> element nested INSIDE
 * a regular .FeatureCards_card__1p4am (which has background:#f1f1f1) --
 * so the h3 renders as a black bar sitting on top of the light card.
 * That is the visible "black bar" behind every reference card label.
 *
 * Fix policy: when .FeatureCards_cardTitle__qSosC is a CHILD of a
 * .FeatureCards_card__1p4am (i.e. the variant we ship), neutralize the
 * background and color so the h3 inherits the card's text colour. The
 * frozen :hover rule on the parent card still flips card->black/text->white
 * as designed; the cardTitle child stays transparent so colour cascades.
 * Use !important because we cannot remove the artifact-causing rule at
 * its source (css/spax.css frozen).
 * ===================================================================== */
.FeatureCards_card__1p4am > .FeatureCards_cardTitle__qSosC {
  background: transparent !important;
  color: inherit !important;
  /* Defensive: if any ::before / ::after pseudo-element ships in spax.css
   * for this class (none observed at audit time), neutralize them too. */
  text-shadow: none !important;
}
.FeatureCards_card__1p4am > .FeatureCards_cardTitle__qSosC::before,
.FeatureCards_card__1p4am > .FeatureCards_cardTitle__qSosC::after {
  content: none !important;
}
/* Subtitle inside the now-transparent cardTitle should pick up the card's
 * standard description tone, not the spax.css default of hsla(0,0%,100%,.59)
 * (which assumes the cardTitle is a black-panel variant). */
.FeatureCards_card__1p4am > .FeatureCards_cardTitle__qSosC + .FeatureCards_subTitle__5apuX,
.FeatureCards_card__1p4am > .FeatureCards_subTitle__5apuX {
  color: rgba(8, 8, 8, 0.6);
}

/* =====================================================================
 * Bug 5: AccordionSection layout proportions -- DEFERRED.
 *
 * Per Plan 01 INVENTORY.md section D, current proportions match the upstream
 * design (left column is intentionally minimal -- title-only with parallax;
 * spec source homepage/deobfuscated.js:564 + AccordionSection.md). No CSS
 * override needed in this plan. Plan 05 (pixel-tune) verifies side-by-side
 * via compare.html and tunes if drift surfaces.
 * ===================================================================== */

/* =====================================================================
 * Bug 6: Hero -> AccordionSection transition spacing -- RETIRED (Plan 04).
 *
 * The temporary padding-top hack on .AccordionSection_accordionSection__xXClX
 * has been retired now that Plan 04 ships the Spacer module
 * (data-module="spacer", styles_space__Spo9r) between the Hero and Accordion
 * sections in pages/home.html. The frozen .styles_space__Spo9r rule in
 * css/spax.css drives padding via the --space-desktop / --space-mobile CSS
 * variables set inline on the spacer wrapper.
 *
 * Spec source: chunks/143:101-119 (Spacer / eN.A component, desktop=50 mobile=50).
 * ===================================================================== */

/* =====================================================================
 * Phase 3 Plan 05 — pixel-tune HotLinks label slide-on-hover
 *
 * The HotLinks JS module (tuned 2026-05-05 per Plan 05 spec audit) now wraps
 * each [data-hotlinks-label] inner content in a <span class="line-inner">
 * that yPercent-slides on hover (verbatim params from chunks/143:168-181):
 *   - mouseenter: yPercent: -110, duration: 0.7s, delay: 0.3s, ease: back.out(2)
 *   - mouseleave: yPercent: 0, duration: 0.8s, ease: back.out(2)
 *
 * For the slide to clip cleanly at the label's bottom edge, the label needs
 * overflow:hidden + display:inline-block. The JS sets these inline as a
 * defense-in-depth, but an upstream-style CSS rule documents the intent
 * declaratively for any markup that doesn't go through the JS wrapper path.
 * ===================================================================== */
[data-hotlinks-label] {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

[data-hotlinks-label] > .line-inner {
  display: inline-block;
  will-change: transform;
}

/* =====================================================================
 * HotLinks image fill fallback (no-JS)
 *
 * HotLinks_image__OEkiS has position:absolute!important from spax.css
 * but no width/height/inset rules — those are set by the HotLinks JS
 * module via GSAP. Without JS, images render at natural size rather than
 * filling the card. This rule fills them until JS takes over.
 * ===================================================================== */
.HotLinks_image__OEkiS {
  top: 0 !important;
  left: 0 !important;
  width: 100% !important;
  height: 100% !important;
}

/* =====================================================================
 * CTA label/arrow z-index lift
 *
 * The CTA has 3D-flipped background halves (CTA_backgroundTop rotateX(-180deg))
 * that interact with text rendering — top half of label appears split/chromatic.
 * Force label and arrow above the background layers with explicit z-index +
 * isolated stacking to keep text crisp.
 * ===================================================================== */
.CTA_cta__2gZWE { isolation: isolate; }
.CTA_ctaLabel__vB_JV,
.CTA_arrowContainer__1O_b_ {
  position: relative;
  z-index: 2;
}
.CTA_background__PQ3mj { z-index: 0; }

/* =====================================================================
 * IndustriesCarousel — pre-JS fallback rules REMOVED 2026-05-06.
 *
 * The previous `:not(.Caroussel_active__HPvSL) { display: none }` rule
 * matched the OLD DOM contract where the carousel JS toggled .active on
 * each <article> slide. After the spec-driven rewrite, JS toggles .active
 * on the nav BUTTONS instead (matching icomat) — slides are hidden via
 * GSAP-driven clip-path inset instead. The old rule was hiding all slides
 * permanently, painting the section flat black. See
 * `.planning/industries-carousel-bug.md` for the full root-cause writeup.
 *
 * The progressBar `opacity: 1 !important` rule was also removed — icomat's
 * spax.css ships `.Caroussel_active__HPvSL .Caroussel_progressBar__5WCXX
 * { opacity: 1 }` correctly; the override forced ALL bars visible.
 * ===================================================================== */

/* =====================================================================
 * HotLinks card label — REMOVED 2026-05-07.
 *
 * The previous override styled `[data-hotlinks-label]` as a custom pill
 * because the unstyled span had no visual treatment. After restructuring
 * the HotLinks HTML to match icomat (chunks/143:97 — Picture + GlassLink
 * are SIBLINGS, link uses `GlassLink_glassLink__lI1yx` class with proper
 * background/glow/label child spans), the spax.css `GlassLink_*` rules now
 * provide the correct pill styling natively. No override needed.
 *
 * Position the link inside the card centered via existing GlassLink rules:
 * .GlassLink_glassLink__lI1yx { display:flex; justify-content:center; align-items:center }
 * Combined with .HotLinks_link__6Uerh { width:100%; height:100% }.
 * ===================================================================== */
.HotLinks_card__C79Cl .HotLinks_link__6Uerh {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: auto !important;
  height: auto !important;
  z-index: 2;
}

/* GlassLink — restore icomat's white-frost background (was overridden to dark).
   This translucent white tint over the dark photograph creates the visible
   pill "border" look at rest. Per .planning/glasslink-final-spec.md §1. */
.HotLinks_link__6Uerh .GlassLink_background__o4T7f {
  background: hsla(0, 0%, 100%, 0.19) !important;
  backdrop-filter: blur(38px) !important;
  -webkit-backdrop-filter: blur(38px) !important;
}

/* GlassLink label — simple color flip (the line-mask text-shadow trick
   was making text appear black at rest because GSAP overrode the CSS
   translateY required for the trick to work). White text on dark glass
   at rest, dark text on white pill at hover. */
/* Override icomat's `display: flow-root; margin-top: 1.5%` which causes
   asymmetric text clipping across cards (margin-top % varies by link width,
   so wider labels like "FEATURED PROJECTS" get clipped at top while shorter
   labels like "BEGIN ENQUIRY" don't). Use simple display:block + no margin. */
.HotLinks_link__6Uerh .GlassLink_label__gDlrE {
  display: block !important;
  margin-top: 0 !important;
  position: relative;
  z-index: 2;
  text-transform: uppercase;
  line-height: 1.6;
  color: #fff !important;
  transition: color 0.4s ease 0.15s;
  white-space: nowrap;
}
.HotLinks_link__6Uerh:hover .GlassLink_label__gDlrE {
  color: #080808 !important;
}
/* Ensure the conic-gradient glow halo is visible on hover. spax.css sets
   z-index:-1 which would put it behind the link, but with isolation:isolate
   on the link, the glow renders above the dark background but below the label. */
.HotLinks_link__6Uerh { isolation: isolate; }

/* Ensure the white sweep-in panel starts off-screen (icomat sets yPercent:110
   via gsap.set at mount). HotLinks.js will manage transform via GSAP. */
.GlassLink_backgroundWhite__12lVL {
  transform: translateY(110%);
  will-change: transform;
}

/* =====================================================================
 * Phase 3 ROUND-2 fixes — moved here from inline <style> in header.html
 * (2026-05-07 cleanup pass). All rules below were originally embedded in
 * partials/header.html during iterative bugfixing; consolidated here so
 * the override surface lives in one place.
 * ===================================================================== */

/* ---------- Document-level fixes ---------- */

/* Prevent horizontal scrollbar caused by off-screen slide-in panels
   (ContactSection, Dialog) that use translateX(100%) while hidden.
   icomat achieves this via React's viewport management; we need it explicit. */
html { overflow-x: hidden; }

/* Hide the native vertical scrollbar — Lenis drives smooth scroll, the
   browser's default 15px gray track is just visual noise on a marketing
   site. Matches icomat.co.uk and the rest of the premium-engineering-firm
   aesthetic. Scroll still works (wheel, trackpad, keyboard, touch); only
   the rendered track is hidden. 2026-05-09: added in response to "what
   is this bar" — it was the native browser scrollbar. */
html {
  scrollbar-width: none;        /* Firefox */
  -ms-overflow-style: none;     /* legacy Edge / IE11 */
}
html::-webkit-scrollbar {
  display: none;                /* Chromium, Safari */
  width: 0;
  height: 0;
}

/* Body must be position:relative so the canvas (position:absolute; bottom:0
   in footerMode) anchors to the bottom of the document, not the viewport.
   Without this, the canvas stays at the top when scrolled to the footer. */
body { position: relative; }

/* Body scroll lock used by Dialog and mobile menu (icomat reference).
   Only `overflow: hidden` — touch-action:none would also block scrolling
   INSIDE the open menu wrapper (Nav_wrapper has its own overflow-y:scroll
   for menu content that exceeds viewport height). */
html.lock-scroll, body.lock-scroll { overflow: hidden !important; }
/* The Nav_wrapper handles its own internal scroll on mobile when open. */
.Nav_wrapper__EfAHX.Nav_show__gn0zM {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* ---------- Nav text-shadow ghost-row fix ---------- */
/* icomat's nav uses <div class="line-mask"><div class="line">text</div></div>
   where line-mask has overflow:clip to mask the text-shadow.
   Our HTML uses <span class="Nav_label__EHdHO"><span class="line">text</span></span>
   so we need to make the label a block-level mask and the .line a block child. */
.Nav_label__EHdHO { display: inline-block; overflow: hidden; vertical-align: middle; }
.Nav_label__EHdHO .line { display: block; }

/* ---------- Hero ---------- */

/* Dark gradient overlay for text legibility over the video.
   Applied as a pseudo-element on Hero_inner__1lTbi (position:relative)
   placed after the video (z-index 1) but before content (z-index 2+).
   2026-05-08: darkened across the board (was 0.55/0.25/0.15/0.50) and
   especially at the bottom where The New Grid™ headline + subline sit
   over the Geylang Methodist drone footage. */
.Hero_inner__1lTbi::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.65) 0%,
    rgba(0, 0, 0, 0.40) 30%,
    rgba(0, 0, 0, 0.35) 55%,
    rgba(0, 0, 0, 0.65) 80%,
    rgba(0, 0, 0, 0.85) 100%
  );
  pointer-events: none;
}
/* Lift hero content above the gradient overlay (z-index 1) so text reads.
   ONLY target the content — do NOT touch the video, which must keep its
   spax.css `position:absolute; inset:0` to fill the inner container. */
.Hero_inner__1lTbi > .Hero_content__nG8Ry {
  position: relative;
  z-index: 2;
}

/* Hero trademark — icomat ships translateX(-25%) which was tuned to sit
   above ICOMAT's flat-top "T". On "Grid" (rounded "d"), the negative
   translate pushes the ™ into the "d". Reset translateX and add a small
   left margin so the trademark sits cleanly to the right of "Grid". */
.Hero_trademark__LT07p {
  transform: translateY(0) !important;
  margin-left: calc(2 * var(--vr));
  vertical-align: super;
}

/* ---------- CompositeDual ---------- */
/* 1. Panel wrapper has no native background — was showing white page bg.
      Set black so it blends with our Kling videos (which have black bg).
   2. Title: white text now visible against black panel.
   3. Description: simple white text at bottom-left, no gradient needed
      since the panel is uniformly dark. */
.CompositeDual_compositeItemWrapper__3nsRx {
  background: #000 !important;
}
.CompositeDual_title___Gma2 { color: rgba(255, 255, 255, 0.85) !important; }
.CompositeDual_description__COUV1 {
  color: #fff !important;
  max-width: calc(280 * var(--vr)) !important;
  bottom: calc(26 * var(--vr)) !important;
  z-index: 5;
}

/* ---------- AccordionSection ---------- */
/* Keep section dark and visible text. The canvas gradient
   (CanvasGradient.js) draws across the full ~300vh wrapper per icomat
   spec — gradient center is intentionally below viewport so only the top
   arc rises into view. Do NOT cap wrapper height. */
.AccordionSection_accordionSection__xXClX { background: #080808 !important; }
.AccordionSection_left___jPIf,
.AccordionSection_accordionTitle__rrUUn,
.AccordionSection_title__cJwJ_ { color: #fff !important; }
.AccordionSection_backgroundWrapper__Wa2Sy { pointer-events: none; }

/* ---------- Timeline (Our production process) ---------- */
/* Match icomat layout: title at top with reasonable padding, cards at
   bottom (space-between), wider landscape image cards that fit in
   viewport without overflow. icomat uses 300vr padding around title
   which is too much when --vr is small; scale down to fit all 7 cards. */
.Timeline_mainTitle__zdZIs {
  text-align: left !important;
  padding-top: calc(80 * var(--vr)) !important;
  margin-bottom: calc(60 * var(--vr)) !important;
  padding-left: calc(var(--grid-gutter-width) * 2) !important;
}
.Timeline_inner__XaZJy {
  justify-content: space-between !important;
  padding-bottom: calc(80 * var(--vr)) !important;
}
.Timeline_imageWrapper__ImgHt {
  aspect-ratio: 16 / 9 !important;
  max-height: 30vh !important;
}
.Timeline_card__oR91S { padding: 0 calc(20 * var(--vr)); }

/* Timeline dotted lines — icomat ships
   `clip-path: inset(0 100% 0 0)` to hide dots until scroll-in animation
   reveals them. Our Timeline.js doesn't animate that, so the dots were
   completely invisible. Override clip-path to none and match the
   opacity of the "01/02/03" label (rgba 0.27) so the line reads as a
   subtle accent, not a bright white bar. */
.Timeline_dots__R7xO_ {
  clip-path: none !important;
}
.Timeline_lineContainer__nkc_3 {
  opacity: 0.27 !important;
}

/* ---------- LargeComposite ---------- */
/* Make the hero video TRULY edge-to-edge. Stock CSS adds
   `padding: 0 1 grid-gutter` and the section is also wrapped in a Barba
   container that may have its own padding. Force width: 100vw and use a
   negative margin trick so the section escapes any parent padding and
   spans the full viewport regardless. */
.LargeComposite_wrapper__y9NAa {
  padding: 0 !important;
  width: 100vw !important;
  margin-left: calc(50% - 50vw) !important;
  margin-right: calc(50% - 50vw) !important;
  max-width: 100vw !important;
}
.LargeComposite_sequenceContainer__IpAmz {
  width: 100vw !important;
}

/* ---------- DialogTriggerButton ---------- */
/* backgroundWhite pill is meant to scale in on hover only. Without the
   hover JS, it stays visible and hides the white label. Initial state:
   scale(0); on button hover, scale(1) reveals the white pill, and the
   label color flips to black so it's still readable. */
.DialogTriggerButton_backgroundWhite__cVy1Y {
  transform: scale(0);
  transform-origin: center;
  transition: transform 0.4s ease;
}
.DialogTriggerButton_btn__QK7_c:hover .DialogTriggerButton_backgroundWhite__cVy1Y {
  transform: scale(1);
}
.DialogTriggerButton_label__xYpmq { transition: color 0.3s ease; }
.DialogTriggerButton_btn__QK7_c:hover .DialogTriggerButton_label__xYpmq { color: #000; }

/* ---------- BurgerMenu icon swap (icomat-spec parity) ----------
   icomat ships two SVG glyphs:
   - LinesIcon: 14×7 viewBox, 2 rows × 7 cols of dots (the "halftone" hamburger)
   - CloseIcon: 10×10 viewBox, 12 dots arranged as an X
   We embed them inline in header.html. CSS just sizes/positions/animates. */
.BurgerMenu_iconContainer__KwWkE { position: relative; isolation: isolate; }

.BurgerMenu_linesIcon__E5VXV,
.BurgerMenu_closeIcon__639IX {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  transition: opacity 0.3s ease;
  display: block;
  color: currentColor;
}

/* Lines icon — icomat sizes at 30% of container width. ViewBox 14:7 keeps
   aspect via display block + width only. */
.BurgerMenu_linesIcon__E5VXV {
  width: 30%;
  height: auto;
  opacity: 1;
}

/* Close (X) icon — icomat: width 20%, square viewBox 10:10. */
.BurgerMenu_closeIcon__639IX {
  width: 20%;
  height: auto;
  opacity: 0;
}

/* ---------------------------------------------------------------
   Header SPAX wordmark - per-page color for visibility against the
   page's hero background.

   2026-05-14: tried mix-blend-mode:difference for true auto-inversion
   but it doesn't engage through Lottie's filter-using SVG stacking
   context.  Falling back to per-page deterministic color via body
   class:
     - dark-hero pages (home, manifesto) -> WHITE wordmark
     - light-hero pages (capabilities, projects, about, legal, etc) -> BLACK wordmark

   Header.js hides the header on scroll-down past 50px, so the
   wordmark is only visible at the top of the page (its hero) and
   when user scrolls back up.  Per-page color matches the hero
   surface cleanly.

   Implementation: filter:brightness(0) flattens the Lottie SVG to
   pure black regardless of authored fill.  Light-hero pages stop
   there.  Dark-hero pages add invert(1) to flip black -> white.
   --------------------------------------------------------------- */
.Header_brandLogo__AXo7t .BrandLogo_lottie__SZi57,
.Header_brandLogo__AXo7t svg,
.Header_brandLogo__AXo7t img {
  /* default: black wordmark for light-bg pages */
  filter: brightness(0);
}
body.page-home .Header_brandLogo__AXo7t .BrandLogo_lottie__SZi57,
body.page-home .Header_brandLogo__AXo7t svg,
body.page-home .Header_brandLogo__AXo7t img,
body.page-manifesto .Header_brandLogo__AXo7t .BrandLogo_lottie__SZi57,
body.page-manifesto .Header_brandLogo__AXo7t svg,
body.page-manifesto .Header_brandLogo__AXo7t img {
  /* dark hero video pages need white wordmark for contrast */
  filter: brightness(0) invert(1);
}

/* When menu is open: hide lines, show X */
.Header_header__qBw1J.Nav_show__gn0zM .BurgerMenu_linesIcon__E5VXV { opacity: 0; }
.Header_header__qBw1J.Nav_show__gn0zM .BurgerMenu_closeIcon__639IX { opacity: 1; }

/* Hide the "MENU" text label when the menu is open (icomat does this in JSX
   by conditionally rendering an empty span — for our CSS-only setup we
   target [data-burger-label]). */
.BurgerMenu_btn__jOz22 [data-burger-label] {
  text-transform: uppercase;
  transition: opacity 0.2s ease, visibility 0.2s ease;
}
.Header_header__qBw1J.Nav_show__gn0zM [data-burger-label] {
  opacity: 0;
  visibility: hidden;
}

/* ---------- Mobile menu — DO NOT add CSS transitions to Nav_wrapper ----------
   icomat drives the mobile open/close entirely via GSAP (BurgerMenu.js
   _mobileOpen/_mobileClose). A CSS opacity transition fights the inline
   GSAP-driven transform/opacity values and causes flicker. Per
   .planning/reference-research-2026-05-06.md §3 "Pitfalls". */

/* ---------- Mobile nav item thumbnails (Nav_img) ----------
   spax.css sizes the Nav_img__K3qsj container to 71vr × 71vr at mobile but
   doesn't style the <img> inside it. Force the image to fill its parent box
   with object-fit:cover so the SPAX project photo crops nicely into a
   square thumbnail. Also ensure the link is a flex row so image + label
   sit side-by-side. */
.Nav_wrapper__EfAHX .Nav_img__K3qsj {
  overflow: hidden;
}
.Nav_wrapper__EfAHX .Nav_img__K3qsj img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@media (max-width: 768px) {
  .Nav_wrapper__EfAHX .Nav_link__Vx_8M {
    display: flex !important;
    align-items: center;
  }
}

/* ---------- Footer layout swap ---------- */
/* After removing the large SPAX wordmark, the navList was floating in
   row 2 with a big gap above it. Move it into the textLogo grid area
   (row 1, cols 3-4) so it sits top-right, aligned with the logo.
   The empty textLogo placeholder takes the navList area to keep the
   grid-template-areas valid. */
.Footer_content__9KGB4 > .footer-nav-top {
  grid-area: textLogo !important;
  align-self: start;
  margin-top: 0 !important;
  margin-bottom: 0;
}
.Footer_content__9KGB4 > .Footer_textLogo__PQKYe:empty {
  grid-area: navList !important;
}

/* MOBILE FOOTER FIX (2026-05-08) — on ≤768px the desktop swap above caused
   the nav <ul> to collide with the SPAX logo on row 1 (both elements
   competing for "logo" + "textLogo" grid columns). icomat's mobile grid
   intentionally puts the nav on its own full-width row 2 ("navList navList
   navList"). Revert the swap on mobile so the nav lands there. Also let the
   nav and legal links wrap to a second line if they overflow the viewport. */
@media (max-width: 768px) {
  .Footer_content__9KGB4 > .footer-nav-top {
    grid-area: navList !important;
    justify-self: start !important;
    align-self: start;
    margin-top: calc(20 * var(--vr)) !important;
    flex-wrap: wrap !important;
  }
  .Footer_content__9KGB4 > .Footer_textLogo__PQKYe:empty {
    grid-area: textLogo !important;
  }
  .Footer_wrapper__r9Ps3 .Footer_navList__fICF8 {
    flex-wrap: wrap !important;
    row-gap: calc(8 * var(--vr));
  }
}

/* Footer legal links — horizontal single line instead of vertical list */
.footer-legal-inline {
  display: flex !important;
  flex-direction: row !important;
  gap: calc(14 * var(--vr));
  align-items: center;
}

/* MOBILE: legal links wrap if they don't fit on one line (Privacy /
   Cookies / Terms / Data privacy can overflow at 390px). */
@media (max-width: 768px) {
  .footer-legal-inline {
    flex-wrap: wrap !important;
    row-gap: calc(6 * var(--vr));
  }
}

/* Footer copyright — light weight, not bold.
   2026-05-14: copyright row now carries three parts separated by mid-dots:
     © SPAX Engineering Pte Ltd 2026 · UEN 202217300M · All rights reserved.
   Flex layout + gap so the separators render evenly and the line wraps
   gracefully to two/three lines on narrow viewports. */
.Footer_rights__cFF6s {
  font-family: AkkuratMono, sans-serif !important;
  font-weight: 400 !important;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.45em;
  line-height: 1.6;
  margin-top: calc(6 * var(--vr));
}
.Footer_rightsSep {
  opacity: 0.45;
  user-select: none;
}
@media (max-width: 600px) {
  .Footer_rights__cFF6s {
    gap: 0.4em;
    font-size: calc(13 * var(--vr));
  }
}

/* VideoSection videoContainer — autoplay background video lives INSIDE this
   panel so the icomat scale + clip-path scroll animation masks the video too
   (the video reveals along with the panel). Background must stay transparent
   so the video itself is the visible content. */
.VideoSection_videoContainer__PvaTp {
  background: transparent !important;
  overflow: hidden !important;
}

/* VideoSection background div — icomat's AccordionSection_backgroundWrapper
   has height:300% and overflow:visible on the section, so the gradient canvas
   extends ~1500px PAST the AccordionSection bottom into the VideoSection.
   spax.css defaults this to opaque white, which hides that bleed-through.
   Make it transparent so the gradient wraps from the AccordionSection into
   the top of the VideoSection naturally. The bottom of VideoSection still
   gets its dark backdrop from the body (--color-black). */
.VideoSection_background__uk3LX {
  background: transparent !important;
}

/* AccordionSection wordmarks — icomat sized these tiny and animates a
   filter:invert(0→1) during open. That works for real coloured logos but
   makes our flat black SVG invisible on both white-active and dark-inactive
   states. Override: bump size, kill the invert filter, force opacity, and
   use mix-blend-mode:difference so the wordmark stays readable on EVERY
   background tone (white, gray, dark gradient).  */
.AccordionSection_logo__DVH1F {
  height: calc(40 * var(--vr)) !important;
  width: auto !important;
  max-width: calc(440 * var(--vr)) !important;
  max-height: none !important;
  opacity: 1 !important;
  filter: none !important;
  mix-blend-mode: difference !important;
}

/* Align the wordmark SVG to the left of the accordion button with a
   consistent left padding, so all 4 cards share the same anchor. */
.AccordionSection_button__r2Cp_ {
  display: flex !important;
  justify-content: flex-start;
  align-items: center;
  width: 100%;
  padding-left: calc(20 * var(--vr));
}


/* =====================================================================
 * Manifesto images (Sections 06+ ImageGrid, full-bleed, QuoteSection)
 * — kill the opacity-0 default on .styles_image__8mDPx
 *
 * spax.css ships:
 *   .styles_pictureComponent__qmXpD .styles_image__8mDPx {
 *     opacity:0; transition: opacity 1s ease-in-out;
 *   }
 * icomat's React triggers the reveal by toggling .styles_loaded__C3h5n
 * via an onLoad handler. We bypass that path: show images at opacity 1
 * directly for the manifesto page (no fade-in transition needed).
 * Inline opacity set via JS was getting stuck mid-tween — direct CSS
 * is the reliable path. Scoped to .manifesto to keep the default
 * behavior on other pages (homepage, etc.) that still rely on the
 * React-driven loaded toggle. */
.manifesto .styles_image__8mDPx {
  opacity: 1 !important;
  transition: none !important;
}


/* =====================================================================
 * Nav visibility — force-show on desktop even without Nav_show class
 *
 * spax.css baseline: .Nav_wrapper__EfAHX { opacity: 0 }, .Nav_nav__ABerH
 * { visibility: hidden }. These are toggled visible only when the
 * Nav_show__gn0zM class is added (which icomat does via React state on
 * page load + scroll thresholds). If that JS path doesn't fire, the
 * entire nav vanishes — and clicking The Company dropdown is impossible
 * because there's no trigger to click.
 *
 * Defensive override: at ≥769px, force the nav visible regardless of
 * Nav_show class. Mobile (≤768px) is untouched so the burger-menu
 * flyout still works as intended (nav hidden until burger toggled).
 */
@media (min-width: 769px) {
  /* spax.css baseline: .Nav_wrapper { opacity:0 }, .Nav_nav { visibility:
     hidden }. icomat's React adds .Nav_show__gn0zM to flip both visible.
     Defensive force-visible at desktop so the dropdown trigger is always
     clickable even if the show-class JS hasn't run. */
  .Nav_wrapper__EfAHX {
    opacity: 1 !important;
  }
  .Nav_nav__ABerH {
    visibility: visible !important;
  }

  /* Nav label line-mask clip. Without overflow:hidden on .line-mask, the
     frozen text-shadow copy (1.8em below) would be visible at rest
     stacked beside the label. Clipping the mask hides the shadow until
     Nav.js's GSAP hover tween slides the .line up via yPercent.
     Display:inline-block + line-height:1.6 give the mask a single text
     row height so the shadow falls cleanly below the clip rect. */
  .Nav_nav__ABerH .Nav_mainMenu__pDzJf .Nav_label__EHdHO .line-mask {
    overflow: hidden;
    display: inline-block;
    line-height: 1.6;
    vertical-align: bottom;
  }
  .Nav_nav__ABerH .Nav_mainMenu__pDzJf .Nav_label__EHdHO .line {
    display: block;
  }
}


/* =====================================================================
 * "The Company" dropdown — icomat-parity hover styling + transitions
 *
 * Structural prerequisites (in partials/header.html — NOT here):
 *   1. <div class="Nav_submenu__EKbU9"> is a SIBLING of <div class="Nav_nav">
 *      inside <nav class="Nav_wrapper">. NOT nested inside the trigger li.
 *   2. Each item's label is wrapped in <div class="line-mask"><div class="line">…
 *      so the frozen spax.css rule
 *        .CompanySubmenuItem_link__ZOT0M .line { text-shadow: 0 1.8em #000 }
 *      can paint a black copy of the text ONE em below the white original.
 *      .line-mask is overflow:clip — when the white line slides up by
 *      translateY(-100%), it disappears into the mask and the shadow's
 *      black copy slides into the visible window from below.
 *
 * Frozen rules in spax.css that we rely on (no override needed):
 *   - .Nav_submenu { width:100%; margin-top:11vr; left:0; display:none }
 *   - .Nav_submenu.Nav_visible { display:flex }
 *   - .CompanySubmenuItem_submenuItemBackground { absolute; #080808 0.7
 *     + backdrop-filter blur(43px) }
 *   - .CompanySubmenuItem_submenuItemBackgroundInner { absolute; bg
 *     #f8f8f8; opacity:0 } — the hover wash element
 *
 * We add only the hover transitions on top.
 */
@media (min-width: 769px) {
  /* Inner white-wash fades in 0 → 1 over 650ms with a deep ease-out.
     Sampled from icomat live: 0.20 @50ms, 0.54 @150ms, 0.88 @350ms, 1.0
     @650ms — fits cubic-bezier(0.16, 1, 0.3, 1). */
  .CompanySubmenuItem_submenuItemBackgroundInner__MIdIG {
    transition: opacity 0.65s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .CompanySubmenuItem_submenuItem__irMc_:hover
    .CompanySubmenuItem_submenuItemBackgroundInner__MIdIG {
    opacity: 1;
  }
  /* Line-mask clip — frozen spax.css doesn't set overflow on .line-mask,
     so the text-shadow copy (1.8em below the .line) is visible at rest
     too, producing duplicate labels stacked under each other. Force the
     mask to clip to a single text-row height so only the active .line
     shows. The block-axis line-height matches the .line's natural
     line-height (1.6) used by spax.css. */
  .CompanySubmenuItem_link__ZOT0M .line-mask {
    overflow: hidden;
    display: inline-block;
    line-height: 1.6;
    vertical-align: bottom;
  }
  /* Line crossfade: white .line slides up 100% so the black text-shadow
     copy (painted 1.8em below per frozen rule) slides into the visible
     window. No `color` transition — the shadow IS the second color. */
  .CompanySubmenuItem_link__ZOT0M .line {
    display: block;
    transition: transform 0.65s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .CompanySubmenuItem_submenuItem__irMc_:hover
    .CompanySubmenuItem_link__ZOT0M .line {
    transform: translateY(-100%);
  }
  /* WAAPI item enter/exit baseline — JS sets inline opacity + transform
     during open/close. will-change hints the compositor. */
  .CompanySubmenuItem_submenuItem__irMc_ {
    will-change: opacity, transform;
  }
}



/* ─────────────────────────────────────────────────────────────
 * Project detail page galleries (added 2026-05-13).
 *
 * The icomat ImageGrid_wrapper__t2LDk is hard-coded for exactly 12
 * photos via :nth-child(1..12) grid-column/grid-row rules and a
 * fixed 1400/848 aspect ratio. When we ship galleries with 13-23
 * photos on project detail pages (Boustead 19, Monfort 15,
 * Sembawang 23) the extra cards have no grid placement and cascade
 * messily, plus the aspect-ratio:1400/848 squashes everything.
 *
 * Scoped override: on body.page-projects-detail, ImageGrid becomes
 * a uniform 3-col grid (2-col mobile, 4-col on wide desktop) with
 * 4:3 tile aspect ratio, gap, no aspect-ratio constraint on the
 * wrapper. Auto-flow handles any photo count cleanly.
 * ────────────────────────────────────────────────────────────── */
body.page-projects-detail .ImageGrid_wrapper__t2LDk {
  aspect-ratio: unset !important;
  height: auto !important;
  grid-template-columns: repeat(3, 1fr) !important;
  grid-template-rows: auto !important;
  column-gap: 12px;
  row-gap: 12px;
  padding: 0 5vw;
}

body.page-projects-detail .ImageGrid_imageContainer__owWmI,
body.page-projects-detail .ImageGrid_imageContainer__owWmI:nth-child(n) {
  grid-column: auto !important;
  grid-row: auto !important;
  aspect-ratio: 4 / 3;
}

body.page-projects-detail .ImageGrid_imageWrapper__j5EZJ,
body.page-projects-detail .ImageGrid_imageContainer__owWmI .styles_pictureComponent__qmXpD {
  width: 100%;
  height: 100%;
}

@media (max-width: 768px) {
  body.page-projects-detail .ImageGrid_wrapper__t2LDk {
    grid-template-columns: repeat(2, 1fr) !important;
    column-gap: 8px;
    row-gap: 8px;
  }
}

@media (min-width: 1440px) {
  body.page-projects-detail .ImageGrid_wrapper__t2LDk {
    grid-template-columns: repeat(4, 1fr) !important;
  }
}


/* ─────────────────────────────────────────────────────────────
 * Lightbox (project detail page gallery viewer) , added 2026-05-13.
 * Spec: v2/11-PROJECT-GALLERY-INTERACTION-SPEC.md
 *
 * Pattern: hand-rolled GSAP Flip modal, inherits the visual language
 * of the icomat contact drawer (backdrop blur, frosted dark panel,
 * power3/power2 easing). Singleton built at runtime by Lightbox.js
 * and appended to <body>, so the rules below cover both the runtime-
 * created root AND the stitching marker from partials/lightbox.html.
 * ────────────────────────────────────────────────────────────── */

/* Thumbnail affordance , only on detail-page galleries */
body.page-projects-detail [data-lightbox-item] {
  cursor: zoom-in;
  outline: 2px solid transparent; /* baseline for focus-visible */
  outline-offset: 4px;
  transition: outline-color 180ms ease;
}
body.page-projects-detail [data-lightbox-item]:focus-visible {
  outline-color: rgba(255, 255, 255, 0.85);
}
body.page-projects-detail [data-lightbox-item] .styles_image__8mDPx {
  transition: transform 420ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
body.page-projects-detail [data-lightbox-item]:hover .styles_image__8mDPx {
  transform: scale(1.025);
}

/* Lightbox root , fixed full viewport, hidden by default */
.spax-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
  align-items: center;
  justify-content: center;
}
.spax-lightbox[aria-hidden="false"] {
  display: flex;
}

/* Backdrop , frosted dark glass over the page */
.spax-lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 12, 14, 0.92);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  opacity: 0;
  cursor: pointer;
}

/* Stage , centered, contained image fits inside without distortion */
.spax-lightbox-stage {
  position: relative;
  z-index: 1;
  width: min(92vw, calc(88vh * 1.7777));
  max-width: min(92vw, 1600px);
  max-height: 88vh;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.04);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none; /* let backdrop receive clicks outside the image */
}
.spax-lightbox-stage > img {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 88vh;
  object-fit: contain;
  display: block;
  background: #0a0c0e;
  pointer-events: auto;
}

/* Close button , top right */
.spax-lightbox-close {
  position: absolute;
  top: clamp(12px, 2vw, 28px);
  right: clamp(12px, 2vw, 28px);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  z-index: 2;
  transition: background 180ms ease, border-color 180ms ease;
}
.spax-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.28);
}

/* Chevrons , left / right center */
.spax-lightbox-prev,
.spax-lightbox-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  z-index: 2;
  transition: background 180ms ease, border-color 180ms ease, transform 180ms ease;
}
.spax-lightbox-prev { left: clamp(12px, 3vw, 36px); }
.spax-lightbox-next { right: clamp(12px, 3vw, 36px); }
.spax-lightbox-prev:hover,
.spax-lightbox-next:hover {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.28);
}
.spax-lightbox-prev:hover { transform: translateY(-50%) translateX(-2px); }
.spax-lightbox-next:hover { transform: translateY(-50%) translateX(2px); }

/* Counter , bottom right (desktop), bottom center (mobile) */
.spax-lightbox-counter {
  position: absolute;
  bottom: clamp(16px, 3vw, 28px);
  right: clamp(16px, 3vw, 28px);
  color: rgba(255, 255, 255, 0.78);
  font-size: 13px;
  letter-spacing: 0.06em;
  font-feature-settings: "tnum";
  opacity: 0;
  z-index: 2;
}

/* Caption , below stage, centered, hidden when empty */
.spax-lightbox-caption {
  position: absolute;
  bottom: clamp(16px, 3vw, 28px);
  left: 50%;
  transform: translateX(-50%);
  max-width: 50em;
  padding: 0 5vw;
  color: rgba(255, 255, 255, 0.72);
  font-size: 13px;
  line-height: 1.5;
  text-align: center;
  opacity: 0;
  z-index: 2;
}

/* Screen-reader-only live region for "Image N of M" announcements */
.spax-lightbox-live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Focus rings on all interactive elements */
.spax-lightbox button:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* Mobile: hide chevrons (rely on swipe), shrink stage, counter at bottom center */
@media (max-width: 768px) {
  .spax-lightbox-stage {
    width: 94vw;
    max-height: 74vh;
    border-radius: 6px;
  }
  .spax-lightbox-prev,
  .spax-lightbox-next {
    display: none;
  }
  .spax-lightbox-counter {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
    bottom: 60px;
  }
  .spax-lightbox-caption {
    bottom: 18px;
    font-size: 12px;
  }
  .spax-lightbox-close {
    width: 38px;
    height: 38px;
  }
}

/* Body lock while lightbox is open , match contact drawer convention */
body.lock-scroll {
  overflow: hidden;
}

/* Reduced motion , skip the Flip morph, plain visibility toggle */
@media (prefers-reduced-motion: reduce) {
  .spax-lightbox-stage > img,
  .spax-lightbox-backdrop,
  .spax-lightbox-close,
  .spax-lightbox-prev,
  .spax-lightbox-next,
  .spax-lightbox-counter,
  .spax-lightbox-caption {
    transition: none !important;
  }
}




/* ─────────────────────────────────────────────────────────────
 * Home Featured Projects cards , force text white on hover.
 * 2026-05-13.
 *
 * The icomat FeatureCards card flips to a dark background on hover.
 * The description gets light text via icomat rules, but the bare
 * <p> title (no class) stays dim , unreadable on the dark bg.
 * Scope: only home-page anchor-wrapped cards (the 4 project cards).
 * ────────────────────────────────────────────────────────────── */
body.page-home a.FeatureCards_card__1p4am:hover,
body.page-home a.FeatureCards_card__1p4am:hover p,
body.page-home a.FeatureCards_card__1p4am:hover .FeatureCards_description__wEb3r {
  color: #ffffff !important;
}


/* ─────────────────────────────────────────────────────────────
 * /solutions Section 2.5 , scroll-pinned solar panel anatomy.
 * 2026-05-13. Reuses icomat /tech mechanic (pin + scrub) on a
 * compressed Shutterstock cell-handling clip. Section height
 * 300vh on desktop (100vh visible while pinned + 200vh of scroll
 * runway to scrub through the video). Mobile: section collapses
 * to a regular auto-play loop, no pin.
 * ────────────────────────────────────────────────────────────── */
/* Full-bleed escape from the .manifesto wrapper padding + the section
   itself is 300vh tall (1x viewport visible while sticky + 2x viewport
   of scroll runway for the 55-frame scrub). */
body.page-capabilities .solar-mount-section {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  height: 300vh;
  background: #0a0a0a;
}

/* The inner div is sticky , stays pinned at viewport top during the
   300vh scroll. Canvas + copy live inside it. */
body.page-capabilities .solar-mount-section__pin {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

/* Full-bleed canvas , the image sequence renders here */
body.page-capabilities .solar-mount-section__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Dark overlay over the canvas for text legibility.
   2026-05-14 UI-fix #1: strengthened gradient + extended right falloff so the
   caption block reliably sits over rgba(0,0,0,~.55) instead of the photo
   highlights at the previous .35 mid-stop (failed WCAG 4.5:1). */
body.page-capabilities .solar-mount-section__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(0, 0, 0, 0.62) 0%,
    rgba(0, 0, 0, 0.45) 35%,
    rgba(0, 0, 0, 0.18) 65%,
    rgba(0, 0, 0, 0) 100%);
  pointer-events: none;
}

/* Copy block , positioned on the left, vertically centered, light text */
body.page-capabilities .solar-mount-section__copy {
  position: absolute;
  top: 50%;
  left: clamp(24px, 8vw, 120px);
  transform: translateY(-50%);
  max-width: min(46ch, 44vw);
  color: #ffffff;
  z-index: 2;
}

body.page-capabilities .solar-mount-section__eyebrow {
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #ffffff;
  opacity: 0.7;
  margin: 0 0 18px;
}

body.page-capabilities .solar-mount-section__title {
  margin: 0 0 22px;
  font-size: clamp(2.2rem, 4.8vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  font-weight: 600;
  color: #ffffff;
}

body.page-capabilities .solar-mount-section__caption {
  margin: 0;
  font-size: clamp(0.95rem, 1.1vw, 1.05rem);
  line-height: 1.55;
  /* 2026-05-14 UI-fix #1: lifted opacity (.82 -> .95), bumped weight, added
     soft text-shadow so the caption is readable over photo highlights. */
  color: rgba(255, 255, 255, 0.95);
  font-weight: 500;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
  max-width: 56ch;
}

/* Mobile , reduce scroll runway, tighten copy padding */
@media (max-width: 768px) {
  body.page-capabilities .solar-mount-section {
    height: 200vh;
  }
  body.page-capabilities .solar-mount-section__overlay {
    background: linear-gradient(180deg,
      rgba(0, 0, 0, 0.65) 0%,
      rgba(0, 0, 0, 0.4) 50%,
      rgba(0, 0, 0, 0.65) 100%);
  }
  body.page-capabilities .solar-mount-section__copy {
    left: 24px;
    right: 24px;
    max-width: none;
    top: auto;
    bottom: 40px;
    transform: none;
  }
}


/* ─────────────────────────────────────────────────────────────
 * /solutions Solar-mount Part A (head) + Part C (tail) , 2026-05-13.
 * Light-bg in-flow text sections that bookend the full-bleed dark
 * scroll-pinned Part B. Together they form a head-body-tail
 * narrative: "we don't make the panels" -> [panel anatomy reveal]
 * -> "SPAX scope starts after panels arrive".
 * ────────────────────────────────────────────────────────────── */
body.page-capabilities .solar-mount-head,
body.page-capabilities .solar-mount-tail {
  position: relative;
  width: 100%;
  background: #f6f6f6;
  padding: clamp(70px, 9vw, 130px) 0;
}
body.page-capabilities .solar-mount-head__inner,
body.page-capabilities .solar-mount-tail__inner {
  width: min(92vw, 1200px);
  margin: 0 auto;
}
body.page-capabilities .solar-mount-head__eyebrow,
body.page-capabilities .solar-mount-tail__eyebrow {
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #1a1a1a;
  opacity: 0.6;
  margin: 0 0 16px;
  font-weight: 500;
}
body.page-capabilities .solar-mount-head__title,
body.page-capabilities .solar-mount-tail__title {
  margin: 0 0 22px;
  font-size: clamp(1.8rem, 3.4vw, 2.8rem);
  line-height: 1.12;
  letter-spacing: -0.018em;
  font-weight: 600;
  color: #0a0a0a;
  max-width: 26ch;
}
body.page-capabilities .solar-mount-head__body,
body.page-capabilities .solar-mount-tail__body {
  margin: 0;
  font-size: clamp(1rem, 1.05vw, 1.1rem);
  line-height: 1.6;
  color: #1a1a1a;
  opacity: 0.82;
  max-width: 55ch;
}

@media (max-width: 768px) {
  body.page-capabilities .solar-mount-head,
  body.page-capabilities .solar-mount-tail {
    padding: 50px 0;
  }
  body.page-capabilities .solar-mount-head__inner,
  body.page-capabilities .solar-mount-tail__inner {
    width: 88vw;
  }
}

/* ─────────────────────────────────────────────────────────────
 * LegalSection - shared styling for /privacy + /terms pages
 *
 * Re-uses the existing brand typography (LayGrotesk-Semibold for
 * headlines, system body) but adds:
 *   - max-width container so prose doesn't span 1440px
 *   - hero block with eyebrow + h1 + effective-date meta line
 *   - section spacing between h2 blocks
 *   - readable line-height + size for body copy
 *
 * 2026-05-13 - shipped with /privacy + /terms placeholder pages.
 * ───────────────────────────────────────────────────────────── */
body.page-legal {
  background: #f6f6f6;
}
body.page-legal .LegalSection_inner__placeholder {
  width: min(92vw, 780px);
  margin: 0 auto;
  padding: clamp(80px, 12vw, 160px) 0 clamp(80px, 10vw, 140px);
  color: #1a1a1a;
}
body.page-legal .LegalSection_hero {
  margin-bottom: clamp(40px, 6vw, 72px);
  padding-bottom: clamp(28px, 4vw, 48px);
  border-bottom: 1px solid rgba(0,0,0,.10);
}
body.page-legal .LegalSection_eyebrow {
  font-family: "LayGrotesk-Semibold", system-ui, sans-serif;
  font-size: clamp(12px, 1.1vw, 13px);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(0,0,0,.55);
  margin: 0 0 clamp(16px, 2vw, 24px);
}
body.page-legal .LegalSection_title {
  font-family: "LayGrotesk-Semibold", system-ui, sans-serif;
  font-size: clamp(34px, 5.6vw, 64px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 clamp(18px, 2vw, 26px);
  color: #0a0a0a;
}
body.page-legal .LegalSection_meta {
  font-size: clamp(13px, 1.05vw, 14px);
  line-height: 1.5;
  color: rgba(0,0,0,.55);
  margin: 0;
}
body.page-legal .LegalSection_body {
  font-size: clamp(15px, 1.25vw, 17px);
  line-height: 1.65;
  color: #1f1f1f;
}
body.page-legal .LegalSection_body > p:first-child {
  font-size: clamp(17px, 1.45vw, 19px);
  line-height: 1.55;
  color: #0a0a0a;
  margin: 0 0 clamp(40px, 5vw, 56px);
}
body.page-legal .LegalSection_body h2 {
  font-family: "LayGrotesk-Semibold", system-ui, sans-serif;
  font-size: clamp(19px, 1.7vw, 22px);
  line-height: 1.25;
  letter-spacing: -0.005em;
  color: #0a0a0a;
  margin: clamp(40px, 5vw, 56px) 0 clamp(14px, 1.6vw, 20px);
}
body.page-legal .LegalSection_body p {
  margin: 0 0 clamp(14px, 1.6vw, 20px);
}
body.page-legal .LegalSection_body ul {
  list-style: disc;
  padding-left: 1.4em;
  margin: 0 0 clamp(14px, 1.6vw, 20px);
}
body.page-legal .LegalSection_body li {
  margin: 0 0 .55em;
}
body.page-legal .LegalSection_body li:last-child {
  margin-bottom: 0;
}
body.page-legal .LegalSection_body a {
  color: #0050c8;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
body.page-legal .LegalSection_body a:hover {
  color: #003a96;
}
body.page-legal .LegalSection_body strong {
  font-weight: 600;
  color: #0a0a0a;
}
@media (max-width: 768px) {
  body.page-legal .LegalSection_inner__placeholder {
    width: 88vw;
    padding-top: clamp(60px, 18vw, 100px);
  }
}

/* ─────────────────────────────────────────────────────────────
 * Mobile QA fix - HeroText H1 word-break on narrow viewports.
 *
 * 2026-05-13 mobile QA: word "subcontractor." rendered at the
 * mobile H1 font size (~58px on iPhone-class viewports) is ~380px
 * wide. With 20px hero left-padding it overflows a 390px viewport
 * by 10px on /projects ("...One Singapore subcontractor.") and
 * /about ("...mounting subcontractor.").
 *
 * Fix: allow the long unbreakable word to wrap at any character
 * boundary, but only when it would otherwise overflow. No visual
 * change on desktop or for shorter words.
 * ───────────────────────────────────────────────────────────── */
@media (max-width: 480px) {
  .HeroText_title__ncrt1 {
    overflow-wrap: anywhere;
  }
}

/* ============================================================================
 * 2026-05-14 — /capabilities UI pass
 *
 * Five fixes shipped as one commit per the screenshot-ui-analyzer + ui-ux-pro-max
 * review. Scoped to body.page-capabilities so /home and detail pages stay intact.
 *
 *   #2  Mobile FeatureCards: horizontal scroll-snap carousel with peek-affordance
 *   #3  "What we install" eyebrow (markup change in pages/capabilities.html)
 *   #4  Desktop FeatureCards: 2x2 grid (was 3 + orphaned 1)
 *   #5  Hero HeroText_text right column: typography lift so it reads as subhead
 *
 *   Fix #1 (solar-mount caption contrast) is applied inline above the @media
 *   max-width:480px block - see lines around 1175 + 1215.
 * ============================================================================ */

/* ---- Fix #3: "What we install" eyebrow ---- */
body.page-capabilities .what-we-install__eyebrow {
  font-family: "LayGrotesk-Semibold", system-ui, sans-serif;
  font-size: clamp(11px, 0.9vw, 13px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, 0.55);
  margin: 0 0 clamp(14px, 1.6vw, 20px);
  width: 100%;
  flex: 0 0 100%;
}

/* ---- Fix #4: FeatureCards desktop 2x2 grid ----
 * Steelwork was orphaned on row 2 alone. Force a balanced 2x2 inside the
 * new .FeatureCards_cards container.  Eyebrow + h2 stay block-level
 * children of the outer wrapper and are unaffected by the grid. */
@media (min-width: 769px) {
  body.page-capabilities .FeatureCards_cards {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: clamp(20px, 2vw, 32px);
    align-items: stretch;
  }
  body.page-capabilities .FeatureCards_wrapper__sctdU {
    display: block;     /* override the flex/wrap inherited from spax.css */
  }
  body.page-capabilities .FeatureCards_card__1p4am {
    width: auto;        /* override flex-basis from spax.css */
    flex: unset;
    min-width: 0;
  }
}

/* ---- Fix #2: Mobile FeatureCards carousel + peek-affordance ----
 * On <=768px the .FeatureCards_cards container becomes a horizontal
 * scroll-snap container with 80vw card widths and right-side padding that
 * always leaves ~20% of the next card visible, so users see there's more
 * content offscreen. */
@media (max-width: 768px) {
  body.page-capabilities .FeatureCards_wrapper__sctdU {
    display: block;
    padding: 0;
  }
  body.page-capabilities .FeatureCards_cards {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-left: 20px;
    gap: 14px;
    padding: 0 20px 16px;
    /* peek the next card */
    padding-right: calc(100vw - 80vw - 32px);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  body.page-capabilities .FeatureCards_cards::-webkit-scrollbar { display: none; }

  body.page-capabilities .FeatureCards_card__1p4am {
    flex: 0 0 80vw;
    max-width: 340px;
    scroll-snap-align: start;
  }

  /* CSS-only swipe-affordance: a small "Swipe to see more ->" hint below the
     carousel.  Pure ::after on the wrapper, so no HTML or JS change needed
     for this affordance variant. */
  body.page-capabilities .FeatureCards_wrapper__sctdU + .swipe-hint,
  body.page-capabilities .swipe-hint {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    margin: 4px 0 24px;
    font-family: "LayGrotesk-Semibold", system-ui, sans-serif;
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(0, 0, 0, 0.45);
  }
  body.page-capabilities .swipe-hint::before {
    content: "";
    width: 18px;
    height: 1px;
    background: currentColor;
  }
  body.page-capabilities .swipe-hint::after {
    content: "\2192"; /* arrow */
    font-size: 14px;
    transform: translateX(0);
    animation: swipe-nudge 1.6s ease-in-out infinite;
  }
  @keyframes swipe-nudge {
    0%, 100% { transform: translateX(0); }
    50% { transform: translateX(6px); }
  }
  @media (prefers-reduced-motion: reduce) {
    body.page-capabilities .swipe-hint::after { animation: none; }
  }
}

/* Hide the swipe hint on desktop where the grid is fully visible */
@media (min-width: 769px) {
  body.page-capabilities .swipe-hint { display: none; }
}

/* ---- Fix #5: Hero secondary copy typography ----
 * The right-column body paragraph next to the H1 reads as a footnote because
 * the H1 owns all weight.  Lift to clear subhead treatment: bigger size,
 * better line-height, controlled line-length, slight color separation. */
@media (min-width: 769px) {
  body.page-capabilities .HeroText_text__aN7co {
    font-size: clamp(15px, 1.25vw, 18px);
    line-height: 1.55;
    max-width: 38ch;
    color: rgba(0, 0, 0, 0.72);
    font-weight: 400;
    margin-top: clamp(20px, 2vw, 28px);
  }
}
@media (min-width: 1280px) {
  body.page-capabilities .HeroText_text__aN7co {
    /* push slightly down so it aligns near the H1 baseline */
    padding-top: clamp(40px, 4vw, 64px);
  }
}
