/* ==========================================================================
   ROOFTOP ENERGY — SITE STYLESHEET

   Token values come from two places and they agree:
     1. The `rooftop-visual` brand skill (dark-mode palette, Inter type scale,
        radius scale). This is authoritative.
     2. Colours sampled directly out of the Figma render where the brand skill
        does not specify a value (the page background gradient, the near-black
        hero, the footer plate).

   Layout numbers are the Figma frame's own: a 1440px canvas with 100px side
   padding, made fluid so the design survives below 1440.
   ========================================================================== */

/* ==========================================================================
   1. DESIGN TOKENS
   ========================================================================== */
:root {
  /* ---- Surfaces -------------------------------------------------------- */
  /* Flat page canvas, sampled from the RooftopIQ Product Overview deck
     (2026-08-10) — the deck paints #070e1d across 86% of its area. This
     replaces the old #232e41 -> #050a1c gradient; the deck is flat, so the
     site is too. The EPC page is unaffected: it runs body.theme-light.     */
  --c-bg:          #070e1d;  /* every dark page, top to bottom              */
  --c-hero:        #070e1d;  /* hero plate — matched so index has no seam    */
  --c-footer:      #070b17;  /* footer plate                                */
  --c-section:     #101623;  /* brand token: section                        */
  --c-card:        #161d27;  /* brand token: card                           */
  --c-card-alt:    #222934;  /* brand token: secondary / gray-800           */
  --c-brand:       #d1b151;  /* brand token: Rooftop gold                   */

  /* ---- Ink ------------------------------------------------------------- */
  --c-white:       #ffffff;
  --c-white-70:    rgba(255, 255, 255, 0.7);   /*  9.60:1 on --c-bg          */
  --c-white-60:    rgba(255, 255, 255, 0.6);   /*  7.27:1                    */
  --c-white-50:    rgba(255, 255, 255, 0.5);   /*  5.34:1 — AA floor for text*/
  /* Below this line the ratio drops under WCAG AA (4.5:1) on --c-bg, so
     these two are for BORDERS AND RULES ONLY — never `color`. The Figma
     frames set several captions at 40% and one at 30%; those measured
     3.77:1 and 2.62:1 on the live site and were repointed to --c-white-50
     on 2026-08-26. Accessibility outranks the frame here.                 */
  --c-white-40:    rgba(255, 255, 255, 0.4);   /*  3.77:1 — not for text     */
  --c-white-30:    rgba(255, 255, 255, 0.3);   /*  2.62:1 — not for text     */

  /* ---- Lines and veils ------------------------------------------------- */
  --c-border:      rgba(255, 255, 255, 0.1);   /* brand: border-medium      */
  --c-border-lt:   rgba(255, 255, 255, 0.15);  /* brand: border-light       */
  --c-surface-sub: rgba(249, 250, 251, 0.045); /* brand: surface-subtle     */
  --c-ring:        #6b7280;                    /* brand: ring (dark mode)   */

  /* ---- Typography ------------------------------------------------------
     Weight discipline (rooftop-visual: "Inter / font-weight: 400 ... do not
     substitute alternate weights", and the RooftopIQ deck's rooftopiq theme,
     which sets p:400 and h1/h2:500):

       body copy, paragraphs, captions   400
       headings, card titles, names      500   <- the global h1-h4 rule
       buttons and call-to-action links  600   <- label scale only

     Card titles were listed at 600 here until 2026-08-27, but the Software
     page — the reference for the whole site — sets every card title at 500.
     EPC was the only page still following the old note, on 19 titles, and it
     is what made that page read heavier than the rest.

     Careers was the page that broke this: 500 on its hero and "looking"
     paragraphs and 600 on its section headings, which read visibly heavier
     than About and RooftopIQ. Do not raise body copy above 400.           */
  --font: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Fluid type: min on a 360px viewport, max on the 1440px Figma canvas.

     ONE SCALE FOR THE WHOLE SITE (Janice, 2026-08-27). Until now these tokens
     carried ten sizes for sixteen roles, and the six uncovered roles were
     hand-set page by page — six different sizes were rendering the same <h2>.
     The Software page was the exception: it had a complete private type layer
     (section 12b) built from its Figma frame, and Janice named it as the page
     whose type she wants everywhere. So the values below ARE that page's
     values, promoted up. Section 12b now consumes these tokens instead of
     restating them.

     Every step is fluid. Nothing here is a fixed rem — a fixed body size was
     why every page except Software stayed locked at 14.4px on a phone.       */
  --fs-hero:    clamp(2.25rem, 1.2rem + 3.6vw, 3.5rem);        /*  36 -> 56   */
  --fs-title:   clamp(1.75rem, 0.95rem + 4.2vw, 2.5rem);       /*  28 -> 40   */
  --fs-h2-lg:   clamp(1.75rem, 1.1rem + 2.2vw, 2.25rem);       /*  28 -> 36   */
  --fs-h2:      clamp(1.6rem, 1.15rem + 1.6vw, 2rem);          /*25.6 -> 32   */
  --fs-pull:    clamp(1.25rem, 1rem + 1vw, 1.5rem);            /*  20 -> 24   */
  --fs-sub-lg:  clamp(1.0625rem, 0.97rem + 0.42vw, 1.1875rem); /*  17 -> 19   */
  --fs-lead:    clamp(1rem, 0.9rem + 0.55vw, 1.125rem);        /*  16 -> 18   */
  --fs-body-lg: clamp(0.9375rem, 0.87rem + 0.42vw, 1.0625rem); /*  15 -> 17   */
  --fs-sub:     clamp(0.9375rem, 0.87rem + 0.35vw, 1rem);      /*  15 -> 16   */
  --fs-body:    clamp(0.875rem, 0.83rem + 0.28vw, 0.9375rem);  /*  14 -> 15   */
  --fs-small:   clamp(0.8125rem, 0.78rem + 0.2vw, 0.875rem);   /*  13 -> 14   */
  --fs-meta:    clamp(0.75rem, 0.73rem + 0.14vw, 0.7875rem);   /*  12 -> 12.6 */

  /* Aliases kept so older rules still resolve. --fs-hero-sub and --fs-label
     were both 18px roles and --fs-statement a 16px one; they are the same
     steps as --fs-lead and --fs-sub now, not separate sizes.                 */
  --fs-hero-sub:  var(--fs-lead);
  --fs-label:     var(--fs-lead);
  /* Folded into --fs-lead on 2026-08-28. It was a 16px step, and it put the two
     intro statements on About a size BELOW the Mission and Vision paragraphs
     directly under them. One lead size now, everywhere. */
  --fs-statement: var(--fs-lead);

  /* ---- Line height ------------------------------------------------------
     The frame's own px pairs as ratios (62/56 = 1.107). Before this, ONE
     value — 1.1 — was applied to h1 through h4, which is right for a 56px
     display line and crowds a 16px card title.                              */
  --lh-hero:   1.107;
  --lh-h2:     1.19;
  --lh-title:  1.28;   /* card titles, h3/h4 scale */
  --lh-body:   1.55;
  --lh-tight:  1.43;   /* chips, pills, meta rows  */
  --lh-prose:  1.65;   /* long-form legal columns  */

  /* Tracking. STATE THESE IN em, NEVER px: `letter-spacing` inherits as an
     absolute length, so a px value set on body reaches a 56px headline and a
     12.6px caption as the same physical amount — which is five different
     ratios on one page. See the note above section 12b.

     Six rules across Careers, Contact and EPC used to say `letter-spacing:
     inherit`, which does exactly the wrong thing: it re-inherits body's
     computed -0.72px, so the same declaration read as -3.3% on a 21.6px
     heading and -5.7% on a 12.6px caption. Those are gone, and the base rule
     below gives every text element its own proportional value rather than
     leaving it to inheritance. --tr-careers and --tr-tight (both -0.05em,
     defined in stray one-line :root blocks halfway down the file) folded into
     --tr-base.                                                               */
  --tr-base:      -0.05em;   /* global default, applied on body               */
  --tr-sub:       -0.08em;   /* subheadings only                             */
  --tr-hero:      var(--tr-base);
  --tr-statement: var(--tr-base);
  --tr-lead:      var(--tr-base);
  --tr-small:     var(--tr-base);

  /* ---- Motion ----------------------------------------------------------
     One easing and two durations for the whole site. Entrances are ease-OUT
     only: fast off the mark, gentle settle. The old `ease` on .reveal was
     slow-in AND slow-out, which is what made it feel sluggish to start.
     easeOutCubic rather than the previous easeOutQuint (0.22,1,0.36,1) —
     quint decelerates so hard it reads as a slide.                        */
  --ease-out:   cubic-bezier(0.33, 1, 0.68, 1);
  --dur-fast:   0.2s;
  --dur-mid:    0.45s;
  --lift:       16px;   /* travel for every scroll entrance */
  --stagger:    60ms;

  /* ---- Spacing (brand scale, 4px base) --------------------------------- */
  --sp-2xs: 0.625rem;  /* 10 */
  --sp-xs:  1rem;      /* 16 */
  --sp-sm:  1.5rem;    /* 24 */
  --sp-md:  2.25rem;   /* 36 */
  --sp-lg:  3.5rem;    /* 56 */
  --sp-xl:  5.25rem;   /* 84 */

  /* Side gutter: 100px on the Figma canvas, tightening on small screens.   */
  --gutter: clamp(1.25rem, -0.4rem + 7.3vw, 6.25rem);
  --maxw:   1440px;

  /* ---- Radius (brand scale) -------------------------------------------- */
  --r-sm:   6px;
  --r-md:   8px;
  --r-lg:   10px;
  --r-xl:   14px;
  --r-card: 20px;   /* the service cards, measured off the frame           */
  --r-full: 9999px;

  /* ---- Hero background media -------------------------------------------
     Two independent controls, both tunable without touching the video file:

       --hero-video-opacity  fades the footage toward the black plate behind
                             it. Lower = darker, flatter, more recessive.
       --hero-scrim-*        a gradient painted ON TOP of the footage. Use
                             this to protect the headline specifically,
                             rather than dimming the whole clip.

     Dim with the scrim first and only drop the opacity if the footage is
     still too busy — the scrim keeps the image's contrast where the text
     isn't.                                                                */
  --hero-video-opacity: 0.55;
  --hero-scrim-top:     0.55;   /* behind the navbar                       */
  --hero-scrim-mid:     0.35;   /* behind the headline                     */
  --hero-scrim-bottom:  0.85;   /* blends into the page below              */

  /* Logo lockup, enlarged from the frame's 106px for prominence. The floor
     is deliberately much lower than the ceiling: 168px reads well on a
     desktop header but would eat a third of a 320px phone.                */
  --logo-w:   clamp(84px, 5.8vw, 104px);

  /* Fallback only. main.js measures the real header and overwrites this, so
     the hero's top padding always clears the sticky bar — the logo scales
     with the viewport, so no single hardcoded value can be right.         */
  --header-h: 140px;
}

/* ==========================================================================
   2. RESET AND BASE
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Footer and FAQ anchors used to jump instantly. The reduced-motion block
     at the end of this file overrides this back to `auto`. */
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.5;
  /* Global tracking. Display type overrides this with the tighter values the
     Figma frames specify; everything else inherits from here. */
  letter-spacing: var(--tr-base);
  color: var(--c-white);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img,
svg { display: block; max-width: 100%; }

/* An HTML `height` attribute plus `max-width:100%` distorts an image once the
   container is narrower than its intrinsic width. `height:auto` neutralises
   that everywhere; the cropped/absolute images override it by class. */
img { height: auto; }

a { color: inherit; text-decoration: none; }

p { margin: 0; }

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 500;
}

/* Line height by heading role, not one value for all four. A single 1.1 is
   right for a 56px display line and crowds a 16px card title, which is what
   made the small headings on EPC, About and Careers feel tighter than the
   Software page's. Ratios are the Figma frame's own px pairs.               */
h1 { line-height: var(--lh-hero); }
h2 { line-height: var(--lh-h2); }
h3, h4 { line-height: var(--lh-title); }

/* Give every text element its OWN tracking rather than letting it inherit
   body's computed -0.72px. `em` resolves against the element's own size, so
   this is the one declaration that keeps the ratio constant across a 56px
   headline and a 12.6px caption. It sits at element specificity (0,0,1), so
   every class-based rule below — including the -0.08em subheading set — still
   wins. Form controls are in the list deliberately: they do not inherit
   typography from body, so without this the Contact fields rendered at
   `normal` while their own labels sat at -5%.                              */
h1, h2, h3, h4, h5, h6,
p, li, a, span, strong, em, small, code,
div, summary, label, figcaption, blockquote, dt, dd, td, th, time, address,
button, input, select, textarea {
  letter-spacing: var(--tr-base);
}

/* Paragraph leading. Component rules override where the frame is specific. */
p { line-height: var(--lh-body); }

/* Size fallbacks so a heading that matches no component rule still lands on the
   scale rather than on the UA default (h2 is 1.5em there). Every class-based
   rule below outranks these. */
h1 { font-size: var(--fs-title); }
h2 { font-size: var(--fs-h2); }
h3, h4 { font-size: var(--fs-sub); }

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* Visible focus for keyboard users only — the design has no focus state,
   so this is added rather than reproduced. Accessibility floor. */
:focus-visible {
  outline: 2px solid var(--c-ring);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link — not in the Figma, added so keyboard users can bypass the nav. */
.skip-link {
  position: absolute;
  left: var(--sp-xs);
  top: -100px;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--c-card);
  border: 1px solid var(--c-border-lt);
  border-radius: var(--r-md);
  transition: top 0.15s ease;
}
.skip-link:focus { top: var(--sp-xs); }

/* ==========================================================================
   3. PAGE SHELL
   The Figma frame paints a single slate-700 -> slate-950 gradient across the
   whole canvas; the hero and footer sit on top of it as opaque plates. That
   is reproduced here as a gradient on the main content area only, so the
   effect holds no matter how tall the page gets.
   ========================================================================== */
.page-body {
  background: var(--c-bg);
}

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ==========================================================================
   4. HEADER / NAVIGATION
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: transparent;
  transition: background-color 0.25s ease, backdrop-filter 0.25s ease;
}

/* Scrolled state. The bar itself stays transparent; this is only a faint
   blur so nav text never sits unreadably on top of passing content. It is
   the one concession to legibility now that the opaque plate is gone —
   remove the whole rule if the reference calls for nothing at all. */
.site-header.is-stuck {
  background-color: rgba(5, 9, 21, 0.55);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* On the white EPC page the same treatment has to invert. */
.theme-light .site-header.is-stuck {
  background-color: rgba(255, 255, 255, 0.72);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
  padding-block: clamp(0.625rem, 1.1vw, 1rem);
}

.site-logo img { width: var(--logo-w); height: auto; }

/* The footer lockup scales with the header one. */
.site-footer__brand img { width: clamp(210px, 19vw, 288px); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(0.375rem, 0.8vw, 0.75rem);
}

/* Transparent pill with a hairline outline. Geometry is tokenised so the
   reference values can be dialled in from one place. */
:root {
  --pill-radius:  var(--r-full);
  --pill-border:  rgba(255, 255, 255, 0.55);
  --pill-pad-x:   clamp(0.875rem, 1.3vw, 1.25rem);
  --pill-pad-y:   0.5rem;
}

.nav a {
  padding: var(--pill-pad-y) var(--pill-pad-x);
  font-size: var(--fs-meta);
  letter-spacing: var(--tr-base);
  color: var(--c-white);
  background: transparent;
  border: 1px solid var(--pill-border);
  border-radius: var(--pill-radius);
  white-space: nowrap;
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.nav a:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.9);
}

/* Active page: bold, as the Figma nav has it, plus a brighter outline so the
   current pill is distinguishable from its neighbours. */
.nav a[aria-current='page'] {
  /* 500, not 700. The border and the background fill below already carry the
     active state on their own, so the weight was redundant — and at 700 this
     pill was the heaviest text anywhere on the site, against a brand spec of
     Inter 400. */
  font-weight: 500;
  border-color: var(--c-white);
  background: rgba(255, 255, 255, 0.12);
}

/* ---- Light-page variant --------------------------------------------------
   The EPC page is white. White pills on white are invisible, so on
   `body.theme-light` the pill inverts to ink-on-light. Without this, removing
   the black plate would leave that page with no visible navigation at all.

   The body itself has to go white too: the white is on <main>, but the sticky
   header sits above it over the body background, which would otherwise leave
   a dark strip behind the inverted pills.
   ------------------------------------------------------------------------- */
body.theme-light { background: #ffffff; }

.theme-light .nav a {
  color: #101623;
  border-color: rgba(16, 22, 35, 0.4);
}

.theme-light .nav a:hover {
  background: rgba(16, 22, 35, 0.08);
  border-color: rgba(16, 22, 35, 0.85);
}

.theme-light .nav a[aria-current='page'] {
  border-color: #101623;
  background: rgba(16, 22, 35, 0.1);
}

.theme-light .nav-toggle { border-color: rgba(16, 22, 35, 0.35); }
.theme-light .nav-toggle span { background: #101623; }

/* ---- Mobile nav --------------------------------------------------------- */
.nav-toggle {
  display: none;
  padding: 0.5rem;
  background: none;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  cursor: pointer;
}

.nav-toggle span {
  display: block;
  width: 20px;
  height: 2px;
  margin: 4px 0;
  background: var(--c-white);
  transition: transform 0.2s ease, opacity 0.2s ease;
}

@media (max-width: 860px) {
  .nav-toggle { display: block; }

  .nav {
    position: absolute;
    inset: 100% 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
    padding: var(--sp-xs) var(--gutter) var(--sp-sm);
    /* The panel still needs a backdrop: stacked pills alone would sit on top
       of page content with nothing behind them. */
    background: rgba(5, 9, 21, 0.97);
    border-bottom: 1px solid var(--c-border);
    display: none;
  }

  .theme-light .nav { background: rgba(255, 255, 255, 0.98); }

  .nav.is-open { display: flex; }
  .nav a { width: 100%; justify-content: center; padding-block: 0.75rem; }
}

/* ==========================================================================
   5. HERO
   ========================================================================== */
.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xs);
  min-height: clamp(420px, 56vw, 810px);
  padding: clamp(4rem, 14vw, 16.375rem) var(--gutter);
  text-align: center;
  background: var(--c-hero);
  /* The hero plate sits under the sticky header, so pull it up by the
     header height and pad it back — otherwise the bar cuts into the top. */
  margin-top: calc(var(--header-h) * -1);
  padding-top: calc(clamp(4rem, 14vw, 16.375rem) + var(--header-h));
}

.hero h1 {
  max-width: 20ch;
  font-size: var(--fs-hero);
  /* 500, matching the global h1-h4 rule and every other page hero. This was
     400, which is the body-copy weight — it read visibly thinner than the
     Software page's hero beside it. */
  font-weight: 500;
  line-height: var(--lh-hero);
  letter-spacing: var(--tr-hero);
}

.hero p {
  max-width: 46ch;
  font-size: var(--fs-hero-sub);
  color: var(--c-white-70);
  letter-spacing: var(--tr-base);
}

/* ==========================================================================
   6. STATEMENT / PROSE BLOCKS
   The Figma sets these as a single text run where the second half of each
   sentence drops to 40% white. `.dim` reproduced that literally until
   2026-08-26, when 40% measured 3.77:1 on the live canvas and failed WCAG AA.
   Both dim steps now resolve to --c-white-50 (5.34:1). `.dim-3` is kept as a
   separate class so the markup does not have to change, but it is no longer
   a third step — if a genuine third level is ever needed it has to come from
   size or weight, not opacity.
   ========================================================================== */
.dim   { color: var(--c-white-50); }
.dim-3 { color: var(--c-white-50); }
.dim-7 { color: var(--c-white-70); }

.statement {
  display: flex;
  flex-direction: column;
  /* The statement paragraph and the mission/vision pair are separate blocks,
     so this gap is deliberately generous — it is what tells them apart. */
  gap: clamp(3.5rem, 6.5vw, 6rem);
  padding-block: clamp(2.5rem, 5vw, 3.375rem);
}

/* Mission and Vision belong together, so they sit closer to each other than
   the pair sits to the statement above. */
.statement__pair {
  display: flex;
  flex-direction: column;
  gap: clamp(1.75rem, 3vw, 2.5rem);
}

.statement__lead {
  font-size: var(--fs-statement);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-statement);
}

.statement__lead p + p { margin-top: 1.4em; }

.stat-block {
  display: flex;
  flex-direction: column;
  gap: 0.9375rem;
}

.stat-block h2,
.stat-block h3 {
  font-size: var(--fs-lead);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.stat-block p {
  max-width: 71ch;
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-lead);
}

/* ---- SEDA certification band -------------------------------------------- */
.certified {
  display: flex;
  align-items: center;
  gap: var(--sp-lg);
  padding-block: clamp(2.5rem, 5vw, 3.375rem);
}

.certified__copy {
  display: flex;
  flex-direction: column;
  gap: 1.6875rem;
  flex: 1 1 auto;
}

.certified__copy h2 {
  font-size: var(--fs-h2);
  letter-spacing: var(--tr-base);
}

.certified__copy p {
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-lead);
}

.certified__marks {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  flex: 0 0 auto;
}

.certified__marks .seda { width: 105px; height: 105px; }

/* The Dyna wordmark is a square PNG with the logo band across its middle;
   the frame crops to that band rather than scaling the whole square.

   These crops are positioned, not margined: a percentage margin resolves
   against the container's WIDTH, but the Figma offsets are proportions of
   its HEIGHT. `top` on an absolutely positioned child resolves against
   height, which is what the numbers actually mean. */
.certified__marks .dyna {
  position: relative;
  width: 137px;
  height: 49px;
  overflow: hidden;
  flex: 0 0 auto;
}
.certified__marks .dyna img {
  position: absolute;
  left: 0;
  top: -80.99%;
  width: 100%;
  height: 281.25%;
  max-width: none;
  object-fit: cover;
}

@media (max-width: 820px) {
  .certified { flex-direction: column; align-items: flex-start; gap: var(--sp-sm); }
}

/* ==========================================================================
   7. SERVICE CARDS
   ========================================================================== */
.services {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.9375rem;
  padding-block: var(--sp-xl);
}

.services h2 {
  font-size: var(--fs-h2);
  text-align: center;
  letter-spacing: var(--tr-base);
}

.service-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2.625rem;
  width: 100%;
}

.service-card {
  position: relative;
  display: block;
  aspect-ratio: 528 / 304;
  overflow: hidden;
  border-radius: var(--r-card);
  background: var(--c-card);
  transition: transform 0.28s ease, box-shadow 0.28s ease;
}

.service-card img.service-card__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Bottom scrim so the wordmark stays readable over the photography. */
/* Bottom scrim so the wordmark stays readable over the photography.
   Only the EPC card needs one: its artwork is a raw photo with the wordmark
   overlaid on top. The RooftopIQ card is a flattened export that already
   contains the design's own gradient AND its wordmark, so adding a scrim here
   paints a second gradient over the logo and fades it out. */
.service-card--epc::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(217, 217, 217, 0) 0%, #2d2d2d 100%);
  mix-blend-mode: multiply;
}

.service-card__mark {
  position: absolute;
  left: 50%;
  bottom: 20px;
  z-index: 2;
  transform: translateX(-50%);
  overflow: hidden;
}

/* Both wordmarks are cropped out of larger square exports, same as the
   Dyna mark above — the crop values come straight off the frame, and for
   the same reason they are applied as offsets on a positioned child. */
.service-card__mark img {
  position: absolute;
  max-width: none;
}

.service-card__mark--iq { width: 161.686px; height: 38px; }
.service-card__mark--iq img {
  width: 118.83%;
  height: 507.11%;
  top: -189.09%;
  left: -6.23%;
}

.service-card__mark--epc { width: 90.615px; height: 38px; }
.service-card__mark--epc img {
  width: 291.5%;
  height: 384.25%;
  top: -133.56%;
  left: -0.05%;
}


@media (max-width: 760px) {
  .service-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-sm); }
}

/* ==========================================================================
   8. RECOGNITION BADGES
   ========================================================================== */
.recognition {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.75rem;
  padding-block: 2.5rem;
}

.recognition h2 {
  font-size: var(--fs-label);
  font-weight: 500;
  color: var(--c-white-70);
  letter-spacing: var(--tr-base);
}

.recognition__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md) 3rem;
}

.recognition__row img { height: auto; }
/* Widths are each asset's own box on the frame. The Great Place To Work
   export is the padded container (95.8 x 149), not the inner badge. */
.recognition__row .md   { width: 209px; }
.recognition__row .gptw { width: 96px; }
.recognition__row .bwit { width: 146px; }

/* ==========================================================================
   9. FOOTER
   ========================================================================== */
.site-footer {
  background: var(--c-footer);
  padding-block: clamp(2.5rem, 5vw, 4.1875rem);
}

.site-footer__inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-lg);
}

.site-footer__brand {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xs);
}

.site-footer__brand img { height: auto; }

.site-footer__brand p {
  font-size: var(--fs-meta);
  font-weight: 400;
  color: var(--c-white-50);
  letter-spacing: var(--tr-small);
}

.footer-nav {
  display: flex;
  gap: var(--sp-sm);
  width: min(388px, 100%);
  flex: 0 1 auto;
}

.footer-nav ul {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 1.0625rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-nav a {
  font-size: var(--fs-meta);
  font-weight: 500;
  letter-spacing: var(--tr-small);
  opacity: 0.9;
  transition: opacity 0.18s ease;
}

.footer-nav a:hover { opacity: 1; text-decoration: underline; }

@media (max-width: 820px) {
  .site-footer__inner { flex-direction: column; gap: var(--sp-md); }
  .footer-nav { width: 100%; }
}

@media (max-width: 460px) {
  .footer-nav { flex-wrap: wrap; gap: var(--sp-sm) var(--sp-md); }
  .footer-nav ul { flex: 0 0 calc(50% - var(--sp-md)); }
}

/* ==========================================================================
   10. SCROLL REVEAL
   Scoped to .js so the page is fully readable when JavaScript never runs.
   ========================================================================== */
.js .reveal {
  opacity: 0;
  transform: translateY(var(--lift));
  transition: opacity var(--dur-mid) var(--ease-out),
              transform var(--dur-mid) var(--ease-out);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .js .reveal { opacity: 1; transform: none; }
}

/* ==========================================================================
   11. PAGE BODY VARIANT
   On the About page the gradient starts below the black hero; on every other
   page it runs from the top of the content. Both endpoints are sampled from
   the Figma render rather than guessed.
   ========================================================================== */
.page-body--full {
  background: var(--c-bg);
}

/* The sticky header sits in normal flow ABOVE <main>, so the top ~80px of the
   page shows the BODY background rather than the page's own. With one flat
   canvas this is simply the same colour, so the header area is continuous and
   no band can appear. `body` already carries --c-bg, so this only needs to
   restate it for clarity; epc.html handles itself with body.theme-light. */
body:has(> .page-body--full) { background: var(--c-bg); }

/* ==========================================================================
   12. ROOFTOPIQ PAGE (v2 — rewritten 2026-08-24)
   Replaces the split hero / "How it Works" layout with a centered, Linear-
   style structure carrying Tristan Lim's August 2026 product briefing
   (docs/rooftopiq-product-briefing-2026-08.md): live-today scope only,
   an honest quarterly roadmap instead of paused-module claims, and a new
   GensetIQ section. Silver replaces brand gold as this page's own accent —
   --c-brand (gold) stays untouched for the rest of the site.
   ========================================================================== */
:root {
  --c-accent: #c7cdd6;
}

.rq-eyebrow {
  display: inline-flex;
  padding: 0.375rem 0.875rem;
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: var(--tr-small);
  color: var(--c-accent);
  background: rgba(199, 205, 214, 0.08);
  border: 1px solid rgba(199, 205, 214, 0.35);
  border-radius: var(--r-full);
}
/* The eyebrow is a bordered pill, not a text label, so its bottom edge IS its
   ink — there is no half-leading under it to act as a gap. It had no margin at
   all, which left ~3px between the pill and the cap-height of the heading and
   read as touching (Janice, 2026-08-27). 20px box -> ~23px visual, in step with
   the ~28px the heading now takes to its subheading below.

   Two sections set this rhythm themselves and would otherwise double up: the
   pivot carries it on .rq-pivot__body's margin-top, the FAQ head on a flex
   gap. Both are zeroed just below. */
.rq-eyebrow { margin-bottom: 1.25rem; }

.rq-pivot .rq-eyebrow,
.rq-faqs__head .rq-eyebrow { margin-bottom: 0; }

.rq-eyebrow--center { display: flex; width: fit-content; margin-inline: auto; }

.rq-h2 {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}
.rq-h2--center { text-align: center; }

.rq-sub {
  /* Same orphan fix as .careers-hero h1: the About services subhead was
     breaking with a single word on its second line. */
  text-wrap: balance;
  margin-top: 1.25rem;
  font-size: var(--fs-body);
  color: var(--c-white-70);
  letter-spacing: var(--tr-base);
}
.rq-sub--center { text-align: center; max-width: 52ch; margin-inline: auto; }

/* ---- Buttons -------------------------------------------------------------- */
.rq-btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.rq-btn {
  display: inline-flex;
  align-items: center;
  padding: 0.75rem 1.375rem;
  font-size: var(--fs-body);
  font-weight: 600;
  letter-spacing: var(--tr-base);
  border-radius: var(--r-md);
  transition: opacity 0.18s ease, border-color 0.18s ease;
}
.rq-btn--primary { background: var(--c-accent); color: #10151f; }
.rq-btn--primary:hover { opacity: 0.85; }
.rq-btn--ghost { border: 1px solid var(--c-border-lt); }
.rq-btn--ghost:hover { border-color: var(--c-accent); color: var(--c-accent); }

/* ---- Hero ------------------------------------------------------------------ */
.rq-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1.5rem;
  padding-block: clamp(3rem, 8vw, 7rem) clamp(3rem, 6vw, 6rem);
}

.rq-hero__title {
  max-width: 28ch;
  font-size: var(--fs-hero);
  font-weight: 500;
  letter-spacing: var(--tr-hero);
}

/* RooftopIQ wordmark, cropped out of a 700x700 padded square export. The
   frame shows a 166 x 38.733 window; that ratio is held with aspect-ratio so
   the window scales, and the offsets are percentages on an absolutely
   positioned child. A percentage margin-top would resolve against WIDTH and
   land in the wrong place — same trap the .crop-* marks document. */
.rq-wordmark {
  position: relative;
  display: block;
  width: clamp(120px, 11.5vw, 166px);
  aspect-ratio: 166 / 38.733;
  overflow: hidden;
}

.rq-wordmark img {
  position: absolute;
  width: 118.667%;    /* 196.987 / 166  */
  height: 508.573%;   /* 196.987 / 38.733 */
  left: -6.333%;      /* -10.513 / 166  */
  top: -190.002%;     /* -73.593 / 38.733 */
  /* The base `img { max-width: 100% }` would clamp the width back to the crop
     window while the height stayed at 508%, squashing a square source into a
     0.84 ratio. Every other cropped sprite here opts out the same way. */
  max-width: none;
}

/* GensetIQ is a separate product with its own teal mark; it is an SVG, so it
   only needs a width and the viewBox keeps the ratio. */
.rq-genset-logo {
  width: clamp(140px, 13.3vw, 192px);
  height: auto;
  margin-inline: auto;
}

.rq-hero__sub {
  max-width: 68ch;
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-lead);
  color: var(--c-white-70);
}

.rq-stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.25rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-body);
  color: var(--c-white-50);
}
.rq-stats li + li { border-left: 1px solid var(--c-border-lt); padding-left: 1.25rem; }

/* Transparent PNG: no plate, no radius, no border — matches the current
   Figma frame, which drops the bordered card in favour of a plain image
   with a drop shadow on the artwork itself (the site's older convention,
   see .platform__art history). */
.rq-hero__art {
  width: 100%;
  margin-top: clamp(1.5rem, 4vw, 3rem);
}
.rq-hero__art img {
  width: 100%;
  height: auto;
  filter: drop-shadow(0 24px 32px rgba(0, 0, 0, 0.45));
}

/* ---- Platform / feature grid ---------------------------------------------- */
.rq-platform {
  text-align: center;
}

.rq-feature-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem;
  margin-top: clamp(2rem, 4vw, 3.5rem);
  text-align: left;
}
.rq-feature-grid--genset { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.rq-feature-card {
  padding: clamp(1.75rem, 3vw, 2.25rem);
  background: var(--c-section);
  border: 1px solid var(--c-border);
  border-radius: var(--r-card);
}

.rq-icon-tile {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  color: var(--c-accent);
  background: rgba(199, 205, 214, 0.1);
  border-radius: var(--r-lg);
}

/* The GensetIQ cards sit one step tighter than the platform pair. */
.rq-icon-tile--sm { width: 44px; height: 44px; border-radius: var(--r-md); }

.rq-feature-card h3 {
  margin-top: 1rem;
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.rq-feature-card p {
  margin-top: 0.5rem;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-white-70);
}

@media (max-width: 700px) {
  .rq-feature-grid,
  .rq-feature-grid--genset { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Service cards (About page, Figma 2195:676) ------------------------------
   Three cards instead of the two flattened image cards. Same card shell as
   .rq-feature-card; what is added is a header row that carries either a
   product lockup + status tag or an icon tile + title, and a button pinned to
   the bottom so all three line up however long the copy runs. */
.rq-service-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  margin-top: clamp(2rem, 4vw, 3rem);
  text-align: left;
}

.rq-service-card {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* Fixed height so the three lockups sit on one baseline — the RooftopIQ
   wordmark, the GensetIQ SVG and the EPC icon tile are all different heights. */
.rq-service-card__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.875rem;
  min-height: 92px;
}

.rq-service-card__logo { width: clamp(104px, 8.75vw, 126px); height: auto; }

.rq-service-card__title {
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.rq-service-card__copy {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-white-70);
  letter-spacing: var(--tr-base);
  /* Pushes the button down, so the three buttons align regardless of copy. */
  flex: 1 1 auto;
}

/* Status pills. "LIVE TODAY" reads brighter than "IN DEVELOPMENT" on purpose —
   the difference is the point, and it is the same distinction the Software
   page's copy has to hold. */
.rq-tag {
  display: inline-flex;
  padding: 0.1875rem 0.5rem;
  font-size: var(--fs-meta);
  font-weight: 500;
  letter-spacing: var(--tr-base);
  color: rgba(255, 255, 255, 0.75);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: var(--r-full);
}

/* The pill is a <p> inside .rq-feature-card, and `.rq-feature-card p` (0,1,1)
   outranks a bare `.rq-tag` (0,1,0) — so the 0.625rem this rule used to declare
   was dead, and the pill silently tracked the card's body size instead. It has
   rendered at the card-copy size all along. Scoped up so the pill owns its own
   step; --fs-meta is the size it was actually rendering at before 2026-08-27. */
.rq-feature-card .rq-tag,
.rq-service-card .rq-tag {
  font-size: var(--fs-meta);
  line-height: var(--lh-tight);
}

.rq-tag--dev {
  color: rgba(255, 255, 255, 0.5);
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}

/* The EPC tile is a step larger than the feature-grid tiles, per the frame. */
.rq-icon-tile--lg { width: 52px; height: 52px; border-radius: var(--r-lg); }

/* The button is a flex item, so it would stretch full width without this. */
.rq-service-card .rq-btn { align-self: flex-start; }

@media (max-width: 900px) {
  .rq-service-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .rq-service-grid { grid-template-columns: minmax(0, 1fr); }
  .rq-service-card__head { min-height: 0; }
}

/* ---- Pivot / how-we-work ---------------------------------------------------- */
.rq-pivot {
  padding-block: var(--band-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1rem;
}

.rq-pivot__body {
  max-width: 72ch;
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-lead);
  color: var(--c-white-70);
}

.rq-pivot__body p + p { margin-top: 1em; }

.rq-pivot__pull {
  margin-top: 0.75rem;
  max-width: 52ch;
  font-size: var(--fs-pull);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

/* ---- Who it's for / segments ------------------------------------------------ */
.rq-segments {
  padding-block: var(--band-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1.25rem;
}

.rq-segments__copy {
  max-width: 72ch;
  font-size: var(--fs-body);
  color: var(--c-white-60);
  letter-spacing: var(--tr-base);
}

.rq-chip-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem; }

.rq-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1rem;
  font-size: var(--fs-small);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--c-border);
  border-radius: var(--r-full);
}
.rq-chip img { width: 20px; height: 20px; opacity: 0.85; }

/* ---- Roadmap ---------------------------------------------------------------- */
.rq-roadmap { text-align: center; }

.rq-roadmap-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.25rem;
  margin-top: clamp(2rem, 4vw, 3rem);
  text-align: left;
}

.rq-roadmap-card {
  padding: 1.5rem;
  background: rgba(22, 29, 39, 0.6);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
}
.rq-roadmap-card h3 { font-size: var(--fs-sub); font-weight: 500; letter-spacing: var(--tr-base); }
.rq-roadmap-card p {
  margin-top: 0.375rem;
  font-size: var(--fs-small);
  line-height: var(--lh-tight);
  color: var(--c-white-50);
}

.rq-roadmap__after {
  margin-top: 1.5rem;
  font-size: var(--fs-small);
  color: var(--c-white-50);
}

@media (max-width: 900px) {
  .rq-roadmap-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .rq-roadmap-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---- GensetIQ ---------------------------------------------------------------- */
.rq-gensetiq { text-align: center; }

/* ---- Closing call to action --------------------------------------------------
   Full-bleed banner, not a bordered card: matches the current Figma frame,
   which replaced the card with a real GensetIQ screenshot (same product as
   the section above) and a scrim for legibility, heading and buttons
   anchored to the left. */
.rq-cta {
  position: relative;
  overflow: hidden;
  /* `width: 100%` is mandatory here, not tidiness. An aspect-ratio that also
     carries a min-height resolves the ratio against the HEIGHT and sets the
     width from it: 352px x 1440/829 = 611px, which overflowed every phone
     (measured 611px of scrollWidth on a 360px viewport). Same bug the
     .map__holder rule documents. */
  width: 100%;
  max-width: 100%;
  aspect-ratio: 1440 / 829;
  min-height: 22rem;
  display: flex;
  align-items: center;
}

.rq-cta__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: right center;
}

/* The mockup PNG is shot on a grey studio backdrop (rgb 143,157,162), not
   transparent, so at full size its grey meets the page's #070e1d with a hard
   horizontal seam top and bottom. The frame lays a black gradient over the
   lower 587px only, which leaves the top seam showing; reproducing the frame's
   165% zoom instead loses the laptop. So: the image sits at cover, as drawn,
   and this scrim fades both edges into the canvas. Middle stays clear so the
   screen is still legible. */
.rq-cta::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Two layers, and the horizontal one is the load-bearing half. The mockup's
     grey backdrop sits in the upper-LEFT of the frame, beside the laptop. The
     vertical fade alone is transparent by 34% height, and section 12's
     .rq-cta__inner::before only spans the band's vertical middle (it is
     inset -10vw off a ~180px-tall box), so the top-left corner was covered by
     neither and the grey read as a hard-edged rectangle. This layer takes the
     left third to the canvas colour outright, which is also what the heading
     needs to sit on. */
  background:
    /* Horizontal does the real work: the grey sits left of the laptop, which
       starts around 37% across. Opaque to 24%, released by 52% so the screen
       is untouched. */
    linear-gradient(to right,
      var(--c-bg) 0%,
      var(--c-bg) 24%,
      rgba(7, 14, 29, 0.82) 34%,
      rgba(7, 14, 29, 0.25) 44%,
      rgba(7, 14, 29, 0) 52%),
    /* Vertical only softens the band's own top and bottom edges against the
       page. Kept clear through the middle so the laptop reads. */
    linear-gradient(to bottom,
      var(--c-bg) 0%,
      rgba(7, 14, 29, 0.45) 5%,
      rgba(7, 14, 29, 0) 16%,
      rgba(7, 14, 29, 0) 84%,
      rgba(7, 14, 29, 0.45) 95%,
      var(--c-bg) 100%);
}

/* Heading and buttons sit above the scrim. */
.rq-cta__inner { z-index: 2; }

.rq-cta__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.5rem;
}
.rq-cta__inner::before {
  content: '';
  position: absolute;
  inset: -10vw -10vw -10vw -10vw;
  z-index: -1;
  background: linear-gradient(100deg,
    rgba(7, 14, 29, 0.95) 0%,
    rgba(7, 14, 29, 0.8) 28%,
    rgba(7, 14, 29, 0.35) 55%,
    rgba(7, 14, 29, 0) 75%);
}

.rq-cta__inner h2 {
  max-width: 22ch;
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

/* ---- FAQs, two-column ---------------------------------------------------------
   This page's FAQ is a heading rail beside the rows, not the centred stack the
   other six pages use, so it does NOT take the shared `.faqs` class. The rows
   are still `.faq__item`, so the native <details> collapse, the chevron
   rotation and the light-page overrides all apply unchanged. */
.rq-faqs {
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

.rq-faqs__head { display: flex; flex-direction: column; align-items: flex-start; gap: 1.25rem; }

/* min-width:0 so a long question cannot push the column past its track. */
.rq-faqs__list { min-width: 0; }

.rq-faqs__sub {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-white-50);
  letter-spacing: var(--tr-base);
}

/* The rail stacks above the rows before the two columns get too narrow to
   read; 360px + 1fr cannot survive much below this. */
@media (max-width: 860px) {
  .rq-faqs { grid-template-columns: minmax(0, 1fr); }
}


/* ==========================================================================
   12b. SOFTWARE PAGE — frame-accurate type and spacing
   Figma "06 SOFTWARE — Linear style" (2163:274), 2026-08-26.

   Section 12 mapped this frame onto the site's --fs-* tokens, which had been
   cut ~10-20% on 2026-08-20. Against the frame that read too small — Platform
   headings at 28.8px where the frame draws 36, card copy at 12.6px where it
   draws 15. Janice asked for the page to follow the frame, so every size,
   line-height, colour, radius and section pad below is the frame's own value.

   ALL of it is scoped to `.page-software`, deliberately. The .rq-* classes are
   now shared with the About page's services section, which is drawn to a
   different frame (2195:676) — unscoped overrides would silently resize it.
   Line-heights are the frame's px pairs expressed as ratios (56/62 = 1.107).
   ========================================================================== */

/* ---- Hero -------------------------------------------------------------- */
.page-software .rq-hero { padding-block: clamp(3rem, 8vw, 7rem); }

.page-software .rq-hero__title {
  font-size: var(--fs-hero);   /* -> 56px */
}

.page-software .rq-hero__sub {
  font-size: var(--fs-lead);   /* -> 18px */
}

.page-software .rq-stats {
  font-size: var(--fs-body);                                    /* 14.4px  */
  color: rgba(255, 255, 255, 0.55);
}

/* ---- Buttons and eyebrow ----------------------------------------------- */
.page-software .rq-btn { font-size: var(--fs-body); line-height: 1.389; padding: 0.75rem 1.375rem; }
.page-software .rq-eyebrow { font-size: var(--fs-meta); line-height: 1.27; }

/* ---- Section headings --------------------------------------------------- */
/* Platform is the one oversized head in the frame (36px); the rest are 32. */
.page-software .rq-platform .rq-h2 {
  font-size: var(--fs-h2-lg);  /* -> 36px */
}

.page-software .rq-h2 {
  font-size: var(--fs-h2);     /* -> 32px */
}

.page-software .rq-sub {
  font-size: var(--fs-sub); /* -> 16px */
  color: rgba(255, 255, 255, 0.6);
  max-width: 46ch;
}

/* ---- Platform feature cards -------------------------------------------- */
.page-software .rq-feature-card h3 {
  font-size: var(--fs-sub-lg);  /* -> 19px */
}

.page-software .rq-feature-card p {
  font-size: var(--fs-body);   /* -> 15px */
  color: rgba(255, 255, 255, 0.65);
}

/* ---- Pivot -------------------------------------------------------------- */
.page-software .rq-pivot { padding-block: 1rem clamp(4rem, 8.3vw, 7.5rem); }

.page-software .rq-pivot__body {
  font-size: var(--fs-body-lg); /* -> 17px */
  color: rgba(255, 255, 255, 0.75);
}

.page-software .rq-pivot__pull {
  font-size: var(--fs-pull);            /* -> 24px */
  line-height: 1.333;                                        /* 32/24   */
}

/* The pivot's rhythm, set deliberately rather than by one flex gap.

   Measured before: eyebrow->grey 23px, grey para->para 27px, grey->white 37px
   (ink to ink). Two faults. The gap INSIDE the grey block was larger than the
   gap separating the eyebrow from it, and the white line sat only 10px further
   out than one paragraph is from another — so it read as a third grey
   paragraph that happened to be white, instead of its own beat.

   The frame has the same flaw (24px above and below the block, with a full
   blank line between the paragraphs), so this is a correction to it, not a
   match. Now strictly ascending: 22 within the block, 30 to the eyebrow, 44 to
   the white line. Box values are ink targets minus half-leading. */
.page-software .rq-pivot { gap: 0; }
.page-software .rq-pivot__body { margin-top: 1.5625rem; }        /* -> ~30 ink */
.page-software .rq-pivot__body p + p { margin-top: 0.75rem; }    /* -> ~22 ink */
/* The white line is a standalone statement, so it sits equidistant from the
   grey block above and the grey copy of the next section below (Janice,
   2026-08-26). The gap below it is the section break — 120px of .rq-pivot
   padding, which every section on this page uses — so the balance is struck by
   raising the gap ABOVE to match, rather than by shortening the break and
   desyncing this section from the other five. Measured: 44 above / 128 below
   before; ~128 on both sides now. */
.page-software .rq-pivot__pull { margin-top: clamp(4rem, 8.3vw, 7.5rem); }

/* Note the value is the SAME clamp as .rq-pivot's padding-bottom, not a fixed
   px equivalent of it. A fixed 119px matched at 1440 and drifted everywhere
   else — at 360 the gap below shrinks to its 4rem floor while a fixed top gap
   would have stayed at 119px, so the line would sit off-centre on every phone.
   Sharing the clamp keeps it balanced at every width. */

/* ---- Segments ----------------------------------------------------------- */
.page-software .rq-segments { padding-block: 0 clamp(4rem, 8.3vw, 7.5rem); }

/* Same treatment where grey meets the chips. */
.page-software .rq-segments { gap: 0; }
.page-software .rq-chip-row { margin-top: 2.1875rem; }           /* -> ~44 ink */

.page-software .rq-segments__copy {
  font-size: var(--fs-sub);      /* -> 16px */
  color: rgba(255, 255, 255, 0.6);
}

.page-software .rq-chip {
  font-size: var(--fs-small);   /* -> 14px */
  line-height: var(--lh-tight);
  padding: 0.625rem 1rem;
  color: rgba(255, 255, 255, 0.85);
}

/* ---- Roadmap ------------------------------------------------------------ */
.page-software .rq-roadmap { padding-block: 0 clamp(4rem, 8.3vw, 7.5rem); }

.page-software .rq-roadmap-card {
  padding: 1.5rem;
  border-radius: 16px;                                       /* frame: 16, not --r-lg */
  background: rgba(16, 22, 35, 0.6);
  border-color: rgba(255, 255, 255, 0.08);
}

.page-software .rq-roadmap-card h3 {
  font-size: var(--fs-body);   /* -> 15px */
}

.page-software .rq-roadmap-card p {
  font-size: var(--fs-small);   /* -> 13px */
  line-height: var(--lh-tight);
  color: rgba(255, 255, 255, 0.55);
}

.page-software .rq-roadmap__after {
  font-size: var(--fs-small);   /* -> 14px */
  line-height: var(--lh-tight);
  color: rgba(255, 255, 255, 0.45);
}

/* ---- GensetIQ ----------------------------------------------------------- */
/* Its cards are a step tighter than the Platform pair in the frame: 28px pad
   and an 18px radius against 36 and 20. */
.page-software .rq-gensetiq { padding-block: 0 clamp(4rem, 8.3vw, 7.5rem); }

.page-software .rq-feature-grid--genset .rq-feature-card {
  padding: clamp(1.5rem, 2.3vw, 1.75rem);
  border-radius: 18px;
}

.page-software .rq-feature-grid--genset .rq-feature-card h3 {
  font-size: var(--fs-sub);      /* -> 16px */
}

.page-software .rq-feature-grid--genset .rq-feature-card p {
  font-size: var(--fs-small); /* -> 13.5px */
  line-height: var(--lh-tight);
  color: rgba(255, 255, 255, 0.6);
}

/* ---- FAQs --------------------------------------------------------------- */
.page-software .rq-faqs { padding-block: 0 clamp(4.5rem, 9.7vw, 8.75rem); }

.page-software .rq-faqs__sub {
  font-size: var(--fs-body);  /* -> 15px */
  color: rgba(255, 255, 255, 0.55);
}

.page-software .rq-faqs .faq__item summary {
  font-size: var(--fs-sub);      /* -> 16px */
  line-height: 1.5;
  color: var(--c-white);
}

.page-software .rq-faqs .faq__item p {
  font-size: var(--fs-small);
}

/* ---- Tracking ------------------------------------------------------------
   Two things fixed here.

   1. `letter-spacing` INHERITS AS AN ABSOLUTE LENGTH. `body` sets -0.05em at
      14.4px, which computes to -0.72px, and every element that does not
      declare its own tracking inherits that fixed -0.72px whatever its own
      size is. Before 12b everything sat near 14.4px so it went unnoticed; with
      sizes now spanning 13-56px the same -0.72px reads as -5.54% at 13px and
      -4.8% at 15px. Five different ratios on one page. Every text rule below
      therefore states tracking in `em` so it is genuinely proportional.

   2. Subheadings go to -0.08em (Janice, 2026-08-26). Note the frame itself
      currently draws its Subhead nodes at -0.05em (16px / -0.8px), so this is
      a deliberate departure from it, not a match to it.
   -------------------------------------------------------------------------- */

/* Subheadings — the -8% set. */
.page-software .rq-hero__sub,
.page-software .rq-sub,
.page-software .rq-segments__copy,
.page-software .rq-faqs__sub { letter-spacing: -0.08em; }

/* Everything else stated in em so nothing inherits body's absolute -0.72px. */
.page-software .rq-hero__title,
.page-software .rq-stats,
.page-software .rq-stats li,
.page-software .rq-btn,
.page-software .rq-eyebrow,
.page-software .rq-h2,
.page-software .rq-feature-card h3,
.page-software .rq-feature-card p,
.page-software .rq-pivot__body,
.page-software .rq-pivot__body p,
.page-software .rq-pivot__pull,
.page-software .rq-chip,
.page-software .rq-roadmap-card h3,
.page-software .rq-roadmap-card p,
.page-software .rq-roadmap__after,
.page-software .rq-faqs .faq__item summary,
.page-software .rq-faqs .faq__item p,
.page-software .rq-cta__inner h2 { letter-spacing: -0.05em; }

/* ---- Closing CTA -------------------------------------------------------- */
.page-software .rq-cta__inner h2 {
  font-size: var(--fs-h2);          /* -> 32px */
  max-width: 18ch;
}


/* ==========================================================================
   13. CAREERS PAGE
   Every text run on this frame is tracked at -0.08em, so it gets one token
   rather than six near-identical pixel values.
   ========================================================================== */

.careers-hero {
  display: flex;
  flex-direction: column;
  gap: clamp(1.75rem, 4vw, 3.9375rem);
  padding-block: clamp(4rem, 15vw, 13.875rem);
  letter-spacing: var(--tr-base);
}

.careers-hero h1 {
  max-width: 1061px;
  /* Evens out the line lengths so the last line cannot be left with a single
     word — this heading was breaking as "... future of energy / right." The
     nbsp in the markup is the belt to this braces: where text-wrap: balance is
     unsupported, the last two words still cannot be split. */
  text-wrap: balance;
  font-size: var(--fs-title);
  font-weight: 500;
  line-height: var(--lh-hero);
  /* Stated in em, not inherited. `inherit` passes down body's COMPUTED
     -0.72px, which is -0.05em at 14.4px body but only -0.018em on a 40px
     heading — so the heading rendered nearly untracked. Same fix the
     .page-software block already applies. */
  letter-spacing: -0.05em;
}

.careers-hero p {
  font-size: var(--fs-statement);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

/* ---- Life at Rooftop Energy --------------------------------------------- */
.life {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2rem, 4.5vw, 3.6875rem);
  padding-block: clamp(3rem, 6vw, 4.5rem);
}

.life h2,
.team h2 {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.life h2 { text-align: center; }

.life__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 382px));
  justify-content: center;
  gap: 2.5rem;
  width: 100%;
}

.life__card { display: flex; flex-direction: column; gap: 1.3125rem; }

/* Photo frames. Each image is cropped differently by the frame; the offsets
   are applied to a positioned child so `top` resolves against height and
   `left` against width, matching Figma's own semantics. */
.photo-frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-card);
  background: var(--c-card);
}

.photo-frame img {
  position: absolute;
  max-width: none;
}

.life__card .photo-frame { aspect-ratio: 382 / 360; }

.crop-ownership { width: 100%;     height: 159.15%; left: -0.09%;  top: -12.71%; }
.crop-values    { width: 141.34%;  height: 100%;    left: -26.17%; top: 0.09%; }
.crop-growth    { width: 150.16%;  height: 106.24%; left: -25%;    top: -6.29%; }

.life__card h3 {
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.life__card p {
  margin-top: 0.9375rem;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
  color: var(--c-white-70);
}

@media (max-width: 900px) {
  .life__grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
}

/* ---- Looking for / open roles -------------------------------------------- */
.looking {
  display: flex;
  flex-direction: column;
  gap: 2.125rem;
  padding-block: clamp(3rem, 6vw, 4.5rem);
  letter-spacing: var(--tr-base);
}

.looking p {
  max-width: 929px;
  font-size: var(--fs-statement);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

.looking__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  align-self: flex-start;
  font-size: var(--fs-body);
  font-weight: 600;
  letter-spacing: var(--tr-base);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.looking__cta img {
  width: 24px;
  height: 24px;
  transition: transform 0.2s ease;
}

.looking__cta:hover img { transform: translateX(4px); }

/* ---- Our team ------------------------------------------------------------ */
/* The cards carried a photo, a name and a role only, at 218px wide and centred.
   Bios were added 2026-08-28, and a four-sentence paragraph in a 218px column
   reads as a ribbon — so the grid now follows .life__grid, the same page's own
   photo-heading-paragraph pattern, and the text left-aligns under the portrait.
   The crop offsets are percentages, so they survive the wider frame untouched. */
.team {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2rem, 4.5vw, 3.6875rem);
  padding-block: clamp(3rem, 6vw, 4.5rem);
}

.team__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 300px));
  justify-content: center;
  gap: 2.5rem;
  width: 100%;
}

.team__card { display: flex; flex-direction: column; }

/* Square frame, at a smaller card. It was 382px square and dominated the card;
   the card is now 300px, which is 38% less photo area but keeps the square
   Janice asked for (2026-08-28).

   The old .crop-* offsets are gone. They positioned an absolutely placed image
   at `height: 149.98%` of the frame, which is undistorted ONLY while the frame
   is square, so the whole approach broke the moment the frame changed shape.
   object-fit is aspect-agnostic and survives any future reshape; the framing
   lives in object-position. The life cards keep the % crops.

   All three portraits are from the same shoot at 466x700 with the subject
   framed alike, so one position serves them. */
.team__card .photo-frame { aspect-ratio: 1; }

.team__card .photo-frame img {
  position: static;
  inset: auto;
  width: 100%;
  height: 100%;
  max-width: 100%;
  object-fit: cover;
  object-position: 50% 22%;
}

.team__card__name {
  margin-top: 1.3125rem;
  /* Size and weight of .life__card h3 — the other card title on this page. */
  font-size: var(--fs-sub);
  font-weight: 500;
  line-height: var(--lh-title);
  letter-spacing: var(--tr-base);
}

.team__card__role {
  margin-top: 4px;
  font-size: var(--fs-meta);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-base);
  color: var(--c-white-70);
}

.team__card__bio {
  margin-top: 0.9375rem;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
  color: var(--c-white-70);
}

/* Two-up earlier than .life__grid does it: these bios run to four sentences,
   and three columns below ~1120px puts them in ribbons about 14 lines deep. */
@media (max-width: 1120px) {
  .team__grid { grid-template-columns: repeat(2, minmax(0, 300px)); }
}

@media (max-width: 680px) {
  .team { align-items: stretch; }
  .team h2 { text-align: left; }
  .team__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   14. CONTACT PAGE
   ========================================================================== */

.contact-top {
  display: grid;
  grid-template-columns: minmax(0, 499fr) minmax(0, 633fr);
  align-items: start;
  gap: clamp(2.5rem, 6vw, 5rem);
  padding-block: clamp(3.5rem, 10vw, 8.375rem);
  letter-spacing: var(--tr-base);
}

.contact-top h1 {
  font-size: var(--fs-title);
  font-weight: 500;
  /* em, not inherit — see .careers-hero h1. */
  letter-spacing: -0.05em;
}

/* v2 feedback: more air between the "Contact Us" heading and the copy. Only
   the first paragraph moves; the two paragraphs stay close to each other. */
.contact-intro h1 + p { margin-top: clamp(1.5rem, 3.2vw, 2.75rem); }

.contact-intro p {
  margin-top: 0.8125rem;
  font-size: var(--fs-lead);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

/* ---- Form ----------------------------------------------------------------
   The Figma draws these controls at 28px tall with 10px placeholder text.
   That is a mock-scale artefact, not a usable control: it is below the
   minimum touch target and the placeholder is unreadable. The visual
   language is kept exactly (slate-700 fill, 7px radius, same labels and
   placeholder copy) and only the control size is brought up to a real one.
   -------------------------------------------------------------------------- */
.form h2 {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.form__note {
  margin-top: 0.8125rem;
  font-size: var(--fs-body);
  font-weight: 400;
  letter-spacing: var(--tr-base);
  color: var(--c-white-70);
}

.field { margin-top: 1.3125rem; }

.field > label {
  display: block;
  margin-bottom: 0.5625rem;
  font-size: var(--fs-body);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 44px;
  padding: 0.6rem 0.75rem;
  /* Matches .field > label and .form__note, both --fs-body. It was --fs-sub
     (15-16px), which read visibly larger than every label and note around it.
     See the touch-viewport guard at the end of this block: below 768px this
     goes back up to 16px, because under 16px iOS zooms the viewport on focus.
     Desktop gets the visual parity; phones keep the working behaviour. */
  font-size: var(--fs-body);
  background: #334155;               /* tailwind slate-700, straight off the frame */
  border: 1px solid transparent;
  border-radius: 7px;
  color: var(--c-white);
  transition: border-color 0.18s ease, background-color 0.18s ease;
}

/* Under 16px iOS zooms the viewport when a field is focused, and --fs-body
   tops out at 15px. Touch devices therefore keep 16px; the size match above is
   a desktop concern and this is where it would actually break something.

   `pointer: coarse` as well as the width test on purpose — a width-only guard
   misses an iPad in landscape (1024px wide, still iOS, still zooms). */
@media (max-width: 768px), (pointer: coarse) {
  .field input,
  .field select,
  .field textarea { font-size: 1rem; }
}

.field textarea {
  min-height: 130px;
  resize: vertical;
  line-height: var(--lh-body);
}

/* Was #94a3b8 (slate-400), which sat brighter than the real values it stands
   in for. White at 0.42 over the #334155 field reads ~rgb(137,145,156) —
   clearly secondary, still legible, and every field has a visible label above
   it so the placeholder is never the only cue. */
.field input::placeholder,
.field textarea::placeholder { color: rgba(255, 255, 255, 0.42); }

.field select { color: var(--c-white); }

/* The Inquiry Type placeholder is a disabled empty option, not a real
   placeholder, so it was rendering full white while the inputs beside it were
   grey. The select is `required` with that empty option selected, so it is
   :invalid until a real choice is made — which is exactly the placeholder
   state. The phone code select always has +60 selected, so it stays white. */
.field select:invalid { color: rgba(255, 255, 255, 0.42); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--c-ring);
  background: #3b4759;
}

/* Phone: a country-code select beside the number input. The frame drew a
   fixed +60 plate; it is now a real control, styled to match that plate so
   the row still reads as one field. */
.field--phone .field__row {
  display: flex;
  gap: 5px;
}

.field--phone .field__code {
  /* Pinned rather than auto: a select sizes to its widest option, which let
     the longest country name set the whole control's width. */
  flex: 0 0 148px;
  width: 148px;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-inline: 0.6rem 0.4rem;
  /* background-COLOR, not the `background` shorthand. The shorthand resets
     background-image, and this rule (0,2,0) outranks `.field select` (0,1,1),
     so it was wiping the chevron that rule provides — while `appearance: none`
     still suppressed the native one, leaving this select with no arrow at all. */
  background-color: #334155;
  border: 1px solid transparent;
  border-radius: 7px;
  font-size: var(--fs-body);
  color: #cbd5e1;
}

/* The option list is painted by the OS, so it needs its own colours or it
   inherits the dark control colour and renders unreadable on some platforms. */
.field--phone .field__code option,
.field--phone .field__code optgroup { background: #1e293b; color: var(--c-white); }

.field--phone .field__prefix {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 56px;
  padding-inline: 0.75rem;
  background: #334155;
  border-radius: 7px;
  font-size: var(--fs-body);
  color: #cbd5e1;
}

.field--phone input { flex: 1 1 auto; }

/* The native select arrow is drawn hard against the right edge and its inset
   cannot be styled, so both selects drop it and draw their own. Path is
   relative to THIS FILE (assets/styles.css), not the page. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("img/icon-chevron.svg");
  background-repeat: no-repeat;
  background-position: right 0.875rem center;
  background-size: 12px 7px;
  /* Clears the chevron: 14px inset + 12px glyph + 8px gap. */
  padding-right: 2.125rem;
}

/* Same treatment, and it must come after the .field__code padding-inline
   shorthand above or that would win. */
.field--phone .field__code {
  padding-right: 2.125rem;
  background-position: right 0.75rem center;
}

.form__submit {
  margin-top: 1.5rem;
  width: 255px;
  max-width: 100%;
  min-height: 44px;
  padding: 0.7rem 1.5rem;
  background: #334155;
  border: 1px solid var(--c-border);
  border-radius: 14px;
  color: var(--c-white);
  font-size: var(--fs-body);
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.18s ease;
}

.form__submit:hover { background: #3f4c60; }

.form__sent {
  margin-top: 0.875rem;
  font-size: var(--fs-meta);
  color: var(--c-white-70);
}

@media (max-width: 880px) {
  .contact-top { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Get in touch --------------------------------------------------------- */
.touch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2.5rem, 5vw, 4.375rem);
  padding-block: clamp(2rem, 4vw, 3rem);
}

.touch h2,
.faqs h2 {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.touch__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(2rem, 6vw, 6.0625rem);
  width: 100%;
}

.touch__item {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 264px;
}

/* Icons are sized to their own viewBox so each keeps its designed optical
   size inside a common 24px line — they are not a uniform square set. */
.touch__item .icon {
  display: flex;
  align-items: center;
  height: 24px;
}

.icon img { height: auto; }
.icon .i-mail     { width: 22px; }
.icon .i-linkedin { width: 24px; }
.icon .i-mappin   { width: 18px; }

.touch__item p,
.touch__item a {
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

.touch__item a:hover { text-decoration: underline; }

/* ---- FAQs ------------------------------------------------------------------
   Native <details> so the accordion works with JavaScript disabled. Open by
   default, matching the frame, which shows every answer expanded.
   ---------------------------------------------------------------------------- */
.faqs {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
  padding-block: clamp(3rem, 7vw, 5.1875rem);
}

/* Full gutter measure, deliberately: the band lines up with the navbar and
   every other section on the page. Do not centre it on a narrower column. */
.faqs__list { width: 100%; }

.faq__item { border-bottom: 1px solid var(--c-border-lt); }

/* The FAQ heading rail is tuned for a dark canvas. On the light page the silver
   eyebrow sits on a near-white fill and the white-50 subline disappears
   outright, so both are restated in ink. Added when the two-column FAQ rolled
   out to all seven pages (2026-08-26) — before that only the dark pages used
   this block. */
.theme-light .rq-eyebrow {
  color: #3f4652;
  background: rgba(16, 22, 35, 0.05);
  border-color: rgba(16, 22, 35, 0.22);
}

.theme-light .rq-faqs__sub { color: rgba(16, 22, 35, 0.6); }

/* On the light page the white hairline and the near-white chevron asset are
   both invisible. Same problem the EPC arrow had, same shape of fix. */
.theme-light .faq__item { border-bottom-color: rgba(16, 22, 35, 0.18); }
.theme-light .faq__chev img { filter: invert(1); }
/* The answer copy is white-70, which is invisible on the light page. */
.theme-light .faq__item p { color: rgba(16, 22, 35, 0.72); }

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
  padding-block: 1.5rem;
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
  cursor: pointer;
  list-style: none;
}

.faq__item summary::-webkit-details-marker { display: none; }

.faq__chev {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  transition: transform 0.22s ease;
}

.faq__chev img { width: 23.33px; height: 13.33px; }

.faq__item[open] .faq__chev { transform: rotate(180deg); }

.faq__item p {
  padding-bottom: 1.75rem;
  max-width: 1143px;
  /* Was 0.8685rem (13.9px), below the 14.4px body size, on every FAQ block
     on the site. Raised to the body token 2026-08-26. */
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
  color: var(--c-white-70);
}

/* ==========================================================================
   15. EPC PAGE
   This frame is explicitly `bg-white` while the other four are dark. That is
   the design's own decision, kept as drawn: white page, dark navbar plate,
   black service and phase cards, slate-900 feature cards, dark footer.
   ========================================================================== */
.page-light {
  background: #ffffff;
  color: #000000;
}

.epc-hero {
  padding-block: clamp(4rem, 13vw, 12.4375rem) clamp(2rem, 5vw, 4rem);
  /* Sized to the page's own h1 (Janice, 2026-08-28) — it is the first thing on
     the page and was reading as a caption in a large empty band. Display
     leading, not body leading: 1.55 at 40px opens the lines too far apart. */
  font-size: var(--fs-title);
  line-height: var(--lh-h2);
  letter-spacing: var(--tr-base);
}

.epc-intro {
  display: flex;
  flex-direction: column;
  gap: clamp(1.5rem, 3vw, 2.4375rem);
  padding-block: clamp(1rem, 3vw, 2rem);
  letter-spacing: var(--tr-base);
}

.epc-intro h1 {
  font-size: var(--fs-title);
  font-weight: 500;
  /* em, not inherit — see .careers-hero h1. */
  letter-spacing: -0.05em;
}

.epc-intro p {
  max-width: 823px;
  font-size: var(--fs-lead);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

/* ---- Wide banner --------------------------------------------------------- */
.epc-banner {
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
}

.epc-banner .photo-frame {
  aspect-ratio: 1354 / 372;
  background: #e5e7eb;
}

.crop-banner { width: 100%; height: 272.98%; left: -0.02%; top: -141.68%; }

/* ---- Section headings on the light page ---------------------------------- */
.epc-h {
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: var(--tr-base);
  color: #000000;
}

.epc-section { padding-block: clamp(2rem, 4vw, 2.25rem); }

.epc-h--center { text-align: center; }

/* ---- Our EPC services ----------------------------------------------------- */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  margin-top: var(--sp-sm);
}

.svc-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 340px;
  padding: clamp(1.5rem, 3vw, 2.25rem);
  overflow: hidden;
  border-radius: var(--r-card);
  background: #000000;
  color: var(--c-white);
}

.svc-card img {
  position: absolute;
  inset: 0;
  z-index: 0;                 /* must be stated: the text rule below would
                                 otherwise lift the photo above the scrim */
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The frame masks each photo with an alpha gradient so it dissolves into the
   black card behind the caption. Reproduced as an overlay scrim. */
.svc-card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 22%, rgba(0, 0, 0, 0.78) 46%, #000 64%);
}

.svc-card h3,
.svc-card p { position: relative; z-index: 2; }

.svc-card h3 {
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.svc-card p {
  margin-top: 0.9rem;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

@media (max-width: 880px) {
  .svc-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
}

/* ---- Project phases -------------------------------------------------------- */
.phase-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  margin-top: var(--sp-sm);
}

/* ---- Project phases: click-to-expand ---------------------------------------
   Padding moved off the box and onto the toggle and body, so the whole header
   row is the click target rather than a strip of text inside a padded box.
   -------------------------------------------------------------------------- */
.phase {
  background: #000000;
  border-radius: var(--r-card);
  color: var(--c-white);
  overflow: hidden;
}

.phase__head { margin: 0; font-size: inherit; font-weight: inherit; }

.phase__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
  width: 100%;
  padding: 1.5rem 1.75rem;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
  transition: background-color 0.25s ease;
}

.phase__toggle:hover { background: rgba(255, 255, 255, 0.06); }

.phase__title {
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.phase__chev {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  color: var(--c-white-70);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), color 0.25s ease;
}

.phase__chev svg { width: 20px; height: 20px; }

.phase__toggle[aria-expanded="true"] .phase__chev {
  transform: rotate(180deg);
  color: var(--c-white);
}

/* `height: auto` cannot be transitioned, so main.js writes a measured pixel
   height and clears it once the transition ends. */
.phase__body {
  overflow: hidden;
  transition: height 0.42s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.3s ease;
}

.phase__body > p {
  padding: 0 1.75rem 1.5rem;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
  color: var(--c-white-70);
}

/* Collapsed state, only ever applied once main.js has taken control — without
   JavaScript the bodies stay open and readable. */
.js .phase__body.is-closed {
  height: 0;
  opacity: 0;
}

/* ---- Project portfolio ------------------------------------------------------
   Five 489px cards on a 1440px canvas: the strip overflows the frame by
   design. Kept as a marquee rather than reflowed, so the overflow reads as
   intentional instead of broken.
   ------------------------------------------------------------------------------ */
.portfolio-band {
  position: relative;
  margin-top: var(--sp-sm);
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  background: #0f172a;
}

.proj {
  position: relative;
  flex: 0 0 min(489px, 82vw);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: 373px;
  padding: clamp(1.25rem, 2.5vw, 2.25rem);
  overflow: hidden;
  border: 0;
  border-radius: var(--r-card);
  background: none;
  font: inherit;
  text-align: left;
  scroll-snap-align: start;
  color: var(--c-white);
  cursor: pointer;
}

.proj:focus-visible {
  outline: 2px solid var(--c-brand);
  outline-offset: 3px;
}

.proj img {
  position: absolute;
  z-index: 0;
  max-width: none;
}

.proj::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 34%, rgba(0, 0, 0, 0.55) 60%, rgba(0, 0, 0, 0.9) 100%);
}

.proj h3,
.proj p { position: relative; z-index: 2; }

.proj h3 {
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.proj p {
  margin-top: 4px;
  font-size: var(--fs-body);
  letter-spacing: var(--tr-base);
}

/* Per-card crops, straight off the frame. */
.crop-fill     { inset: 0; width: 100%; height: 100%; object-fit: cover; }
.crop-cheras   { width: 161%;   height: 158.3%;  left: -30.5%;  top: -29.15%; }
.crop-jasin    { width: 131%;   height: 128.8%;  left: -15.55%; top: -23.55%; }
.crop-langkawi { width: 166.4%; height: 163.61%; left: -61.28%; top: -63.51%; }

/* ---- Project lightbox --------------------------------------------------------
   One dialog, reused for every card. main.js (section m) reads the source
   image and the heading/copy off whichever card was clicked, so there is
   nothing here to keep in sync with the grid above.
   ------------------------------------------------------------------------------ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1.5rem, 5vw, 4rem);
}

.lightbox[hidden] { display: none; }

body.lightbox-locked { overflow: hidden; }

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  border: 0;
  padding: 0;
  background: rgba(4, 8, 16, 0.86);
  backdrop-filter: blur(6px);
  cursor: pointer;
}

.lightbox__dialog {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: min(1100px, 92vw);
  max-height: 92vh;
  opacity: 0;
  transform: scale(0.96);
  transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}

.lightbox.is-open .lightbox__dialog {
  opacity: 1;
  transform: scale(1);
}

.lightbox__figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  min-width: 0;
}

.lightbox__img {
  display: block;
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  height: auto;
  border-radius: var(--r-card);
  object-fit: contain;
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.8);
}

.lightbox__caption {
  font-size: var(--fs-body);
  letter-spacing: var(--tr-base);
  color: var(--c-white);
  text-align: center;
}

.lightbox__close {
  position: absolute;
  top: -3rem;
  right: 0;
  z-index: 2;
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--c-border-lt);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  color: var(--c-white);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.lightbox__close:hover,
.lightbox__close:focus-visible { background: rgba(255, 255, 255, 0.16); }

.lightbox__close svg { width: 1.125rem; height: 1.125rem; }

/* ---- Why choose ------------------------------------------------------------- */
.why-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-sm);
  margin-top: var(--sp-sm);
}

.why-card {
  display: flex;
  flex-direction: column;
  gap: 1.625rem;
  padding: clamp(2rem, 4vw, 4.25rem) clamp(1.25rem, 2.5vw, 1.9375rem);
  background: #0f172a;
  border-radius: var(--r-card);
  color: var(--c-white);
}

.why-card h3 {
  font-size: var(--fs-sub);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.why-card p {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-base);
}

@media (max-width: 880px) {
  .why-grid { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
}

/* ---- Closing CTA -------------------------------------------------------------- */
/* The section above ends with its own bottom padding, which stacked on top of
   this one and made the gap above the CTA roughly three times the gap below.
   Zeroing that neighbour's bottom padding leaves this block's own padding as
   the only spacing, so top and bottom match. */
.epc-section:has(+ .epc-cta) { padding-bottom: 0; }

.epc-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  padding-block: clamp(5rem, 10vw, 9rem);
  text-align: center;
  letter-spacing: var(--tr-base);
}

.epc-cta h2 {
  font-size: var(--fs-h2);
  font-weight: 500;
  /* em, not inherit — see .careers-hero h1. */
  letter-spacing: -0.05em;
}

.epc-cta a {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-size: var(--fs-lead);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

/* Both CTAs stack centred. The gap is owned here rather than by .epc-cta so
   the space between the two links is independent of the space under the
   heading. */
.epc-cta__actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.125rem;
}

/* The second CTA is subordinate: same size and the same underline sweep, but
   muted so the consultation link stays the primary action. */
.epc-cta__secondary { color: rgba(0, 0, 0, 0.6); }
.epc-cta__secondary:hover { color: rgba(0, 0, 0, 0.85); }

/* The rule sits on the label, not the link, so it stays under the words and
   does not run beneath the arrow. It is drawn as a pseudo-element so it can
   sweep in from the left on hover rather than just changing colour. */
.epc-cta__label {
  position: relative;
  padding-bottom: 0.12em;
}

.epc-cta__label::before,
.epc-cta__label::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
}

.epc-cta__label::before { background: rgba(0, 0, 0, 0.2); }

.epc-cta__label::after {
  background: #000;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}

.epc-cta a:hover .epc-cta__label::after,
.epc-cta a:focus-visible .epc-cta__label::after { transform: scaleX(1); }

.epc-cta__arrow {
  flex: 0 0 auto;
  width: 0.55em;
  height: 0.55em;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}

.epc-cta a:hover .epc-cta__arrow,
.epc-cta a:focus-visible .epc-cta__arrow { transform: translateX(0.4em); }

/* On the light page the focus ring needs a dark colour to be visible. */
.page-light :focus-visible { outline-color: #334155; }

/* ==========================================================================
   16. MOTION SYSTEM
   Parallax, scroll reveals and the marquee. Every effect is opt-in via a
   class or data attribute, scoped to `.js`, and switched off wholesale under
   `prefers-reduced-motion` at the end of this section.
   ========================================================================== */

/* ---- Parallax ------------------------------------------------------------
   `data-parallax="<speed>"` on any element. main.js writes the offset into
   --parallax-y on each frame; the transform lives here so the element keeps
   whatever other positioning it already had.
   -------------------------------------------------------------------------- */
.js [data-parallax] {
  /* No `will-change` here. Transforms are composited anyway, and declaring it
     permanently kept every parallax target on its own layer for the life of
     the page for no gain. */
  /* Both the parallax offset and any hover zoom live in this one transform.
     Declaring `transform: scale()` separately on a parallax image would
     replace the offset instead of adding to it. */
  transform: translate3d(0, var(--parallax-y, 0px), 0) scale(var(--img-zoom, 1));
}

/* Parallax inside a clipped frame: the image is oversized so it can travel
   without exposing an edge. */
.parallax-frame {
  position: relative;
  overflow: hidden;
}

.parallax-frame > [data-parallax] {
  position: absolute;
  inset: -12% 0;
  width: 100%;
  height: 124%;
  object-fit: cover;
}

/* ---- Word-by-word reveal -------------------------------------------------- */
.js .word-reveal .word {
  display: inline-block;
  /* Floor raised from 0.12: at 29 words the headline sat nearly invisible for
     the whole cascade. Total run is now ~1.2s against ~2.5s before (29 words
     x 55ms + 0.95s), which is the difference between an effect and a wait. */
  opacity: 0.35;
  transform: translateY(0.18em);
  transition: opacity 0.5s var(--ease-out),
              transform 0.5s var(--ease-out);
  transition-delay: calc(min(var(--w, 0) * 25ms, 700ms));
}

.js .word-reveal .word.is-in {
  opacity: 1;
  transform: none;
}

/* ---- Rise-in (project phases, EPC service cards) --------------------------
   v2 feedback: these used to fall in from above. They now fade in and rise
   from below as the section is scrolled into view.
   -------------------------------------------------------------------------- */
/* Entrance offsets use the `translate` property, NOT `transform`.

   `transform` is already owned by the hover lift, and
   `.js .rise-in.is-visible { transform: none }` (0,3,0) outranks
   `.svc-card:hover` (0,2,0) — so writing the entrance as a transform silently
   killed the hover on every card that also animates in. `translate` is a
   separate property that composes with `transform`, so the two coexist. */
.js .rise-in {
  opacity: 0;
  translate: 0 var(--lift);
  /* Longhands, deliberately. As a shorthand listing only opacity+translate
     this rule (0,2,0) outranked the box-hover block's own transition (0,1,0),
     so `transform` and `box-shadow` were never transitioned and the 6px hover
     lift snapped. Declaring all four here fixes that — and the per-property
     delay keeps the entrance stagger OFF the hover, which a shorthand could
     not do: hover would have inherited up to 3 x 60ms of delay. */
  transition-property: opacity, translate, transform, box-shadow;
  transition-duration: var(--dur-mid), var(--dur-mid), 0.4s, 0.4s;
  transition-timing-function: var(--ease-out), var(--ease-out), var(--ease-out), ease;
  transition-delay: calc(var(--i, 0) * var(--stagger)),
                    calc(var(--i, 0) * var(--stagger)), 0s, 0s;
}

.js .rise-in.is-visible {
  opacity: 1;
  translate: 0 0;
}

/* ---- Sequential drop-in (EPC project phases) -------------------------------
   Boxes fall from above in document order, so the cascade reads Design ->
   Build -> Certify -> Maintain. `--i` is assigned per container by main.js,
   so this group starts its count at zero regardless of what precedes it.
   -------------------------------------------------------------------------- */
.js .drop-seq {
  opacity: 0;
  /* Rises now, like everything else. Dropping from above (-46px) read heavier
     than rising and was the loudest entrance on the site; the class name is
     kept so the EPC markup and the main.js selector do not have to change. */
  translate: 0 var(--lift);
  transition-property: opacity, translate, transform, box-shadow;
  transition-duration: var(--dur-mid), var(--dur-mid), 0.4s, 0.4s;
  transition-timing-function: var(--ease-out), var(--ease-out), var(--ease-out), ease;
  transition-delay: calc(var(--i, 0) * var(--stagger)),
                    calc(var(--i, 0) * var(--stagger)), 0s, 0s;
}

.js .drop-seq.is-visible {
  opacity: 1;
  translate: 0 0;
}

/* ---- Interactive boxes -----------------------------------------------------
   Every card on the site shares one hover treatment: a lift with a shadow,
   plus an image zoom where the card carries photography. A click adds
   `is-poked` for one cycle so touch devices, which have no hover at all, get
   the same feedback.
   ---------------------------------------------------------------------------- */
.service-card,
.svc-card,
.phase,
.proj,
.life__card,
.team__card,
.who__card,
.how__card,
.why-card {
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 0.4s ease,
              border-color 0.3s ease,
              background-color 0.3s ease;
}

.service-card:hover,
.service-card:focus-visible,
.svc-card:hover,
.phase:hover,
.proj:hover,
.life__card:hover,
.team__card:hover,
.who__card:hover,
.how__card:hover,
.why-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 20px 34px -20px rgba(0, 0, 0, 0.7);
}

/* The outlined cards brighten their edge as well. */
.who__card:hover,
.how__card:hover,
.why-card:hover { border-color: var(--c-border-lt); }

.theme-light .phase__toggle:hover { background: rgba(255, 255, 255, 0.09); }

/* On the white EPC page a black shadow is too heavy; soften it. */
.theme-light .svc-card:hover,
.theme-light .phase:hover { box-shadow: 0 20px 34px -22px rgba(0, 0, 0, 0.45); }

/* ---- Image zoom on photo cards --------------------------------------------
   Driven through --img-zoom rather than a direct `scale()` so it composes
   with the parallax transform on the Careers photos.
   ---------------------------------------------------------------------------- */
.service-card__bg,
.svc-card img,
.proj img,
.life__card .photo-frame img,
.team__card .photo-frame img {
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Images that are NOT parallax targets need the scale wired up here; the
   parallax rule already includes --img-zoom for the ones that are. */
.service-card__bg,
.svc-card img,
.proj img,
.team__card .photo-frame img {
  transform: scale(var(--img-zoom, 1));
}

.service-card:hover .service-card__bg,
.svc-card:hover img,
.proj:hover img,
.life__card:hover .photo-frame img,
.team__card:hover .photo-frame img { --img-zoom: 1.06; }

/* Icon cards nudge their glyph instead of zooming a photo. */
.who__card img,
.how__card img { transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1); }

.who__card:hover img,
.how__card:hover img { transform: translateX(4px); }

/* FAQ rows are boxes too, in behaviour if not in outline. */
.faq__item summary { transition: color 0.25s ease, padding-left 0.3s ease; }
.faq__item:hover summary { padding-left: 0.375rem; }
.faq__chev { transition: transform 0.22s ease, opacity 0.25s ease; opacity: 0.7; }
.faq__item:hover .faq__chev { opacity: 1; }

.service-card.is-poked,
.svc-card.is-poked,
.phase.is-poked,
.proj.is-poked,
.life__card.is-poked,
.team__card.is-poked,
.who__card.is-poked,
.how__card.is-poked,
.why-card.is-poked { animation: card-poke 0.45s cubic-bezier(0.22, 1, 0.36, 1); }

@keyframes card-poke {
  0%   { transform: translateY(0) scale(1); }
  40%  { transform: translateY(-10px) scale(1.015); }
  100% { transform: translateY(0) scale(1); }
}

/* ---- Marquee ---------------------------------------------------------------
   main.js clones the track's children once so the loop is seamless, then
   drives transform from a rAF loop rather than a CSS keyframe — that's what
   lets the arrows nudge the same position the idle drift is advancing,
   instead of fighting a separate, fixed animation.
   ---------------------------------------------------------------------------- */
.marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
}

.marquee__track {
  display: flex;
  gap: 2rem;
  width: max-content;
  will-change: transform;
}

/* ---- Marquee arrows ----------------------------------------------------------
   Siblings of .marquee, not children: .marquee clips and fades its edges,
   which would clip or fade the buttons too if they lived inside it.
   ------------------------------------------------------------------------------ */
.marquee-arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  width: 3rem;
  height: 3rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--c-border-lt);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  color: var(--c-white);
  cursor: pointer;
  transform: translateY(-50%);
  transition: background-color 0.2s ease, transform 0.2s ease;
}

.marquee-arrow:hover,
.marquee-arrow:focus-visible { background: rgba(255, 255, 255, 0.16); }

.marquee-arrow:active { transform: translateY(-50%) scale(0.92); }

.marquee-arrow svg { width: 1.25rem; height: 1.25rem; }

.marquee-arrow--prev { left: clamp(0.75rem, 3vw, 2.5rem); }
.marquee-arrow--prev svg { transform: scaleX(-1); }
.marquee-arrow--next { right: clamp(0.75rem, 3vw, 2.5rem); }

/* ==========================================================================
   17. HOMEPAGE HERO — full screen, ready for background video
   ========================================================================== */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;      /* avoids the mobile URL-bar jump */
  overflow: hidden;
  justify-content: center;
  padding-block: var(--header-h) clamp(4rem, 10vw, 8rem);
}

/* Media plate. Drop a <video> or <img> in and it fills the hero; the element
   is empty-safe, so the black background shows until an asset exists. */
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--c-hero);
}

.hero__media > video,
.hero__media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: var(--hero-video-opacity);
}

/* Scrim so hero type stays legible over any future footage. */
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, var(--hero-scrim-top)) 0%,
    rgba(0, 0, 0, var(--hero-scrim-mid)) 45%,
    rgba(5, 9, 21, var(--hero-scrim-bottom)) 100%
  );
}

.hero__content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-xs);
}

/* ==========================================================================
   18. RHYTHM
   One vertical scale for every band on the site, so sections stop carrying
   their own ad-hoc padding and the grid reads evenly.
   ========================================================================== */
:root {
  --band:    clamp(3rem, 7vw, 6.5rem);   /* standard section padding        */
  --band-sm: clamp(2rem, 4vw, 3.5rem);   /* tighter, for stacked siblings   */
}

.statement,
.certified,
.services,
.recognition,
.platform,
.who,
.how,
.cta,
.life,
.looking,
.team,
.touch,
.faqs,
.epc-intro,
.epc-section,
/* The Software rebuild (2026-08-25) shipped with its own 86.4px padding and
   sat 14.4px tighter than every other page. Folded back in on 2026-08-26. */
.rq-platform,
.rq-roadmap,
.rq-gensetiq,
.rq-faqs { padding-block: var(--band); }

.epc-banner,
.iq-hero { padding-block: var(--band-sm); }

/* Consecutive bands would otherwise double up their padding. `.certified` is
   deliberately NOT in this list: "Fully SEDA Certified." is a new section and
   needs clear air above it, not a collapsed gap. */
.who + .how,
.life + .looking,
.looking + .team,
.epc-section + .epc-section { padding-top: 0; }

/* The FAQ band closes every page, so whatever precedes it already owns the
   space above. Its own top padding stacked on top of that and left a gap
   roughly twice every other section break. */
* + .faqs { padding-top: 0; }

/* .rq-faqs is now the closing band on every page, so it inherits the same
   problem `.faqs` had: its own top padding stacking on the preceding section's
   bottom padding and leaving a gap twice every other section break. */
* + .rq-faqs { padding-top: 0; }

/* Extra separation above the SEDA band, on top of its own padding. */
.statement + .certified { padding-top: clamp(2rem, 4vw, 3.5rem); }

/* ==========================================================================
   20. WHY-CHOOSE ICON PLACEHOLDERS
   Empty containers sized and positioned for the icons that are still to come.
   Drop an <img>/<svg> inside and it will centre itself.
   ========================================================================== */
.why-card__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border-radius: var(--r-xl);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--c-white-50);
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.why-card:hover .why-card__icon {
  background: rgba(209, 177, 81, 0.16);   /* brand gold, low alpha */
  border-color: rgba(209, 177, 81, 0.45);
}

.why-card__icon img,
.why-card__icon svg { width: 28px; height: 28px; }

/* Placeholder glyph, shown only while the container is empty. */
.why-card__icon:empty::after {
  content: '';
  width: 22px;
  height: 22px;
  border: 1.5px dashed currentColor;
  border-radius: var(--r-sm);
  opacity: 0.55;
}

/* ==========================================================================
   21. LEGAL PAGES (Terms of Use, Privacy Policy)
   ========================================================================== */
.legal {
  padding-block: clamp(3rem, 8vw, 7rem) var(--band);
  max-width: 78ch;
}

.legal h1 {
  font-size: var(--fs-title);
  font-weight: 500;
  letter-spacing: var(--tr-hero);
}

.legal__meta {
  margin-top: 0.75rem;
  font-size: var(--fs-meta);
  color: var(--c-white-50);
}

.legal__draft {
  margin-top: var(--sp-sm);
  padding: 1rem 1.25rem;
  background: var(--c-card);
  border: 1px solid var(--c-border-lt);
  border-left: 3px solid var(--c-brand);
  border-radius: var(--r-md);
  font-size: var(--fs-meta);
  color: var(--c-white-70);
}

.legal h2 {
  margin-top: clamp(2rem, 4vw, 3rem);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--tr-base);
}

.legal p,
.legal li {
  margin-top: 0.9rem;
  font-size: var(--fs-body);
  line-height: var(--lh-prose);
  color: var(--c-white-70);
}

.legal ul { margin: 0; padding-left: 1.25rem; }

.legal a { text-decoration: underline; text-underline-offset: 0.2em; }

/* ==========================================================================
   22. REDUCED MOTION
   One switch that disables the whole motion system.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .js [data-parallax] { transform: none !important; }
  .js .word-reveal .word { opacity: 1 !important; transform: none !important; }
  .js .rise-in,
  .js .drop-seq { opacity: 1 !important; translate: none !important; }
  .service-card:hover,
  .svc-card:hover,
  .phase:hover,
  .proj:hover,
  .life__card:hover,
  .team__card:hover,
  .who__card:hover,
  .how__card:hover,
  .why-card:hover { transform: none; box-shadow: none; }

  .service-card:hover .service-card__bg,
  .svc-card:hover img,
  .proj:hover img,
  .life__card:hover .photo-frame img,
  .team__card:hover .photo-frame img { --img-zoom: 1; }

  .faq__item:hover summary { padding-left: 0; }

  .lightbox__dialog { transition: none; }

  /* The phase accordions still open and close, just without the slide. */
  .phase__body { transition: none !important; }
  .phase__chev { transition: none !important; }
}

/* ==========================================================================
   23. MAP (contact page)
   Click-to-load placeholder, then a 16:9 embed in its place.
   ========================================================================== */
.map { padding-block: 0 var(--band); }

.map__holder {
  position: relative;
  /* width must be pinned: with only aspect-ratio set, the min-height below
     drives the width instead of the height and the box overflows on narrow
     screens. */
  width: 100%;
  aspect-ratio: 16 / 7;
  min-height: 260px;
  overflow: hidden;
  border-radius: var(--r-card);
  background:
    radial-gradient(120% 120% at 50% 0%, rgba(209, 177, 81, 0.08), transparent 60%),
    var(--c-card);
  border: 1px solid var(--c-border);
}

.map__holder iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.map__load {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: center;
  padding: var(--sp-sm);
}

.map__pin {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 0.25rem;
  border-radius: var(--r-full);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--c-border-lt);
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.3s ease;
}

.map__pin img { width: 20px; }

.map__load:hover .map__pin {
  transform: translateY(-4px);
  border-color: rgba(209, 177, 81, 0.5);
}

.map__label {
  font-size: var(--fs-body);
  font-weight: 400;
  letter-spacing: var(--tr-base);
}

.map__hint {
  font-size: var(--fs-meta);
  color: var(--c-white-50);
}

.map__link {
  margin-top: var(--sp-xs);
  font-size: var(--fs-meta);
}

.map__link a {
  color: var(--c-white-70);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.map__link a:hover { color: var(--c-white); }

/* ==========================================================================
   24. RESPONSIVE REFINEMENTS
   The fluid type and gutters handle most of the range; these are the cases
   a clamp() cannot express.
   ========================================================================== */

/* ---- Touch targets -------------------------------------------------------
   Standalone links sit at their text height (14-16px), which is far below a
   usable tap target. Inline links inside a sentence are deliberately left
   alone: enlarging them would break the line box, and WCAG 2.5.8 exempts
   links in flowing prose for exactly that reason.
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .footer-nav a,
  .touch__item a,
  .map__link a,
  .cta__links a,
  .epc-cta a,
  .looking__cta,
  .nav a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* The padding now provides the separation, so the list gap comes down. */
  .footer-nav ul { gap: 0.125rem; }

  /* Wrapping addresses need the block to grow, not the line to centre. */
  .touch__item a { align-items: flex-start; }
}

/* ---- Short viewports (landscape phones) ----------------------------------
   A 100vh hero plus a header-height top padding does not fit in 375-420px of
   height: the hero grew to 1.5x the screen and pushed the scroll cue over
   the subtitle. Here the type is sized against height rather than width,
   and the cue is dropped.
   -------------------------------------------------------------------------- */
@media (orientation: landscape) and (max-height: 620px) {
  .hero {
    padding-block: calc(var(--header-h) + 0.5rem) 1.75rem;
  }

  .hero h1 {
    font-size: clamp(1.35rem, 7.5vh, 2.475rem);
    line-height: 1.1;
  }

  .hero p { font-size: clamp(0.8438rem, 3.4vh, 1.125rem); }

  /* Reclaim height from the tall sections too. */
  :root { --band: clamp(2rem, 5vh, 3.5rem); }
}

/* ---- Very small phones (320-359px) --------------------------------------- */
@media (max-width: 359px) {
  /* Two footer columns is one too many below 360px. */
  .footer-nav { flex-direction: column; gap: var(--sp-xs); }
  .footer-nav ul { flex: 1 1 auto; }

  /* Long unbroken strings (emails, URLs) would otherwise force a scrollbar. */
  .touch__item a,
  .legal a { overflow-wrap: anywhere; }
}

/* ---- Tablet portrait ------------------------------------------------------
   768-900px is wide enough for two columns but not three, which the
   auto-fit grids get wrong on their own.
   -------------------------------------------------------------------------- */
@media (min-width: 700px) and (max-width: 900px) {
  .life__grid,
  .svc-grid,
  .why-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .who__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---- Ultra-wide ----------------------------------------------------------
   The canvas is capped at 1440px, so beyond about 1800px the page becomes a
   narrow column in a wide field. Let the gutter grow instead.
   -------------------------------------------------------------------------- */
@media (min-width: 1800px) {
  :root { --maxw: 1600px; }
}

/* ---- Print --------------------------------------------------------------- */
@media print {
  .site-header,
  .hero__media,
  .nav-toggle,
  .marquee,
  .marquee-arrow,
  .map { display: none !important; }

  body { background: #fff; color: #000; }
  .page-body, .page-body--full, .page-light { background: #fff !important; }
  .legal p, .legal li, .legal h2 { color: #000; }
  a { text-decoration: underline; }
}
