/* ==========================================================================
   Outshine Global Solutions — design system
   Tokens sourced from outshine/context/brand-system/confirmed-brand-decisions.md
   and outshine/context/business-info.md (approved palette + Sora/Inter type).
   Layout skeleton inherited from the earlier effortel.com clone pass; colors,
   type, and copy below are Outshine's own.
   ========================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700;800&display=swap');

:root {
  /* Client-supplied "colors palette" (white/off-white/grey/near-black/black),
     used heavily per the client's own instruction, with the prior warm
     brown/gold identity now reduced to sparing accent moments rather than
     the dominant look. Every color below is still consumed exclusively
     through these custom properties everywhere in this file, so this
     block is the single place the whole site's palette is actually set.
     --color-bg-dark and --color-bg-black are deliberately two distinct
     dark tones (not one flattened value): --color-bg-dark (#141414) is
     the everyday dark section/header/footer color, --color-bg-black
     (pure #000000) is reserved for the deepest moments (the homepage
     hero), mirroring the subtle depth the site already had and matching
     the palette reference's own hero example. */
  --color-bg-dark: #141414;        /* near-black — everyday dark section background */
  --color-bg-black: #000000;       /* true black — reserved for the hero's deepest moment */
  --color-bg-steel: #f5f5f5;       /* off-white — mid-tone band */
  --color-bg-pale: #ffffff;        /* pure white — palest band */
  --color-card-dark: #232323;      /* soft grey lift off #141414, for cards on dark */
  --color-card-steel: #ececec;     /* deeper off-white tint, for cards on the light band */
  --color-accent: #f2a51a;         /* signature golden orange — now a sparing accent, not the dominant color */
  --color-accent-pale: #ffd98a;    /* pale gold, secondary accent */
  --color-accent-on-light: #96610c; /* darkened gold — passes 4.5:1 on white/steel, unlike --color-accent's 2.06:1 there */
  --color-secondary-muted: #141414;
  --color-text-on-dark: #ffffff;
  --color-text-muted-on-dark: #606060;
  --color-text-on-light: #141414;
  --color-text-muted-on-light: #606060;   /* palette's mid-grey, used for muted/secondary text everywhere */
  --color-border: #606060;

  /* Icon-chip rotation: grey/white neutrals from the new palette, with a
     single gold chip kept as the one deliberate glimpse of brand color
     in an otherwise monochrome rotation. */
  --color-icon-chip-1: #f5f5f5;
  --color-icon-chip-2: #f2a51a;
  --color-icon-chip-3: #606060;
  --color-icon-chip-4: #ececec;

  /* Approved typography: Sora for headings, Inter for body and UI chrome */
  --font-display: 'Sora', sans-serif;   /* headlines */
  --font-body: 'Inter', sans-serif;     /* paragraph copy */
  --font-ui: 'Inter', sans-serif;       /* nav/buttons/eyebrows — uppercase treatment, no mono face */

  --radius-btn: 9px;
  --radius-card: 22px;
  --radius-sm: 10px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;
  --space-32: 128px;

  --max-width: 1320px;
  --transition-fast: 180ms ease;
}

/* ---- Page transitions (main.js) ----
   .page-transition-ready is added by an inline-order script before
   first paint, holding the page at opacity 0 the instant CSS applies;
   .page-transition-in (added on DOMContentLoaded/pageshow) fades it
   to visible. .page-transition-out plays on an outgoing internal link
   click, just before the real navigation fires. If JS never runs (disabled,
   blocked, or errors before this point), .page-transition-ready is never
   added and the page renders at its normal opacity: 1 default, so a
   broken script fails open to a visible page, never a stuck-blank one. */
html.page-transition-ready { opacity: 0; }
html.page-transition-ready.page-transition-in {
  opacity: 1;
  transition: opacity 320ms ease;
}
html.page-transition-ready.page-transition-out {
  opacity: 0;
  transition: opacity 260ms ease;
}
/* The floating dock's own hidden-by-default state is normally set by
   main.js on DOMContentLoaded, which runs after first paint (the
   script sits at the end of <body>). That left a real, visible flash
   on every page-to-page navigation: the new page paints with the dock
   already showing, then a beat later main.js catches up and hides it,
   right in the middle of the ~320ms fade-in this same feature added.
   Scoped to html.page-transition-ready (the class the inline pre-paint
   script in <head> adds only once JS has actually run) rather than a
   bare .floating-dock rule, so a no-JS visitor still gets the dock
   visible by default as a fallback nav, exactly as before — this only
   pre-hides it for visitors whose JS is already confirmed running,
   the same visitors who would otherwise see the flash.
   Also requires :not(.dock-js-ready): main.js adds that class to the
   dock the moment its own visibility logic takes over (immediately on
   DOMContentLoaded, before the first scroll check even runs — see
   main.js). Without that guard this rule's specificity (two classes +
   one element) beat .floating-dock.is-hidden's (two classes) outright,
   so once a visitor scrolled past the reveal threshold and main.js
   removed .is-hidden, THIS rule kept winning anyway and the dock could
   never actually show — it looked "broken/missing" for every visitor
   with JS enabled, on every page, permanently. .dock-js-ready flips
   control over to main.js's own .is-hidden toggling for the rest of
   the page's life, exactly once, right after the flash-prone window
   this rule exists to cover has passed. */
html.page-transition-ready .floating-dock:not(.dock-js-ready) { opacity: 0; transform: translateX(-50%) translateY(16px) scale(0.92); pointer-events: none; }

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--color-text-on-dark);
  background: var(--color-bg-dark);
  -webkit-font-smoothing: antialiased;
}

/* Custom cursor dot replaces the OS pointer on fine-pointer devices only;
   touch/coarse-pointer devices keep native behavior untouched. Applied
   to every element (via the universal selector), not just body/a/button:
   scoping it to a handful of tag names left any other clickable element
   (e.g. the custom-select dropdown's <div role="option"> rows) showing
   the native OS arrow on top of the custom sparkle dot at the same time,
   which read as two overlapping cursors. */
@media (hover: hover) and (pointer: fine) {
  body.has-custom-cursor, body.has-custom-cursor * {
    cursor: none;
  }
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; margin: 0; padding: 0; }
button { font-family: inherit; cursor: pointer; }
h1, h2, h3, h4 { margin: 0; font-family: var(--font-display); font-weight: 800; letter-spacing: -0.02em; }
p { margin: 0; }

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--space-6);
}

/* ---- Section band backgrounds ----
   Corrected: the real site has ONE global dark background (measured #22282a on
   .main-wrapper), not several distinct dark tones. .section-navy is kept as an
   alias of .section-dark (same color) purely so existing page markup that used
   the class still renders correctly, rather than editing 15 already-built pages. */
.section { padding: var(--space-24) 0; }
/* Every dark section carries a dark vignette (deepening toward the
   edges) plus a genuinely small trace of gold/violet — deliberately
   NOT the hero's own richer recipe. An earlier pass gave every dark
   section the hero's full gradient strength, which read as "heavily
   brown" rather than "dark with a hint of brand color" once actually
   reviewed on real screenshots (about.html's Vision/Mission band in
   particular was closer to a warm brown wash than a near-black
   section). The two colored glows here are cut hard (0.025/0.02, a
   third of the earlier 0.07/0.05) specifically so the hero — which
   keeps its own separate, richer treatment via --color-bg-black plus
   its own .aurora-layer at higher effective strength — reads as the
   one deliberately dramatic moment on the site, with every other dark
   section clearly more restrained by comparison, not competing with
   it. Still built on --color-bg-dark (not --color-bg-black): see
   .hero-and-trusted below for the hero's own darker base. */
.section-dark, .section-navy {
  background:
    radial-gradient(ellipse 900px 700px at 50% 40%, rgba(242, 165, 26, 0.025) 0%, transparent 60%),
    radial-gradient(ellipse 1100px 800px at 50% 75%, rgba(150, 70, 170, 0.02) 0%, transparent 65%),
    radial-gradient(ellipse 1400px 1000px at 50% 45%, transparent 0%, rgba(0, 0, 0, 0.45) 100%),
    var(--color-bg-dark);
  color: var(--color-text-on-dark);
}
.section-steel { background: var(--color-bg-steel); color: var(--color-text-on-light); }
.section-pale { background: var(--color-bg-pale); color: var(--color-text-on-light); }
/* Aurora removed from these two non-hero dark sections (about.html's
   "Our experience", projects.html's "Additional Engagements"), matching
   the same experiment already applied to index.html's non-hero dark
   sections: flat solid --color-bg-dark, no gradient/vignette/aurora.
   Every hero on every page (including index.html's) keeps its aurora
   untouched. */
.about-experience-section, .projects-engagements-section {
  background: var(--color-bg-dark);
}
/* Site-wide: every eyebrow (the bracketed [ LABEL ] tag above a section
   heading) reads gold and bold regardless of section background.
   Started as an index.html-only experiment, then confirmed for every
   page. Pure --color-accent measures only 2.06:1 on white (fails the
   4.5:1 minimum for this text size) so light sections get the
   darkened --color-accent-on-light instead, which clears 5.2:1 there;
   dark sections keep the full-brightness gold since it already passes
   10:1 on black. */
.section-steel .eyebrow, .section-pale .eyebrow { color: var(--color-accent-on-light); }
.section-dark .eyebrow, .section-navy .eyebrow { color: var(--color-accent); }

/* ---- Eyebrow label ----
   Measured: Et Mono, uppercase, negative letter-spacing, wrapped in literal
   square brackets (not parentheses as first assumed). */
.eyebrow {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin-bottom: var(--space-4);
}
.eyebrow::before { content: "[ "; }
.eyebrow::after { content: " ]"; }

/* ---- Headings ---- */
.h1 { font-size: clamp(32px, 5vw, 60px); line-height: 1.05; }
.h2 { font-size: clamp(28px, 4vw, 44px); line-height: 1.1; }
.h3 { font-size: 21px; font-weight: 700; }
/* Two real usages exist (about.html, projects.html) and both put
   the class directly on the <p> itself, which the descendant-selector
   rules below (added for a wrapper-with-child-p shape) never match —
   the paragraph fell back to full-brightness body text instead of the
   intended muted color, with no top margin. p.section-intro covers
   the actual markup shape without touching either page's HTML. */
.section-intro { max-width: 640px; }
.section-intro p, p.section-intro { color: var(--color-text-muted-on-dark); margin-top: var(--space-4); }
.section-steel .section-intro p, .section-pale .section-intro p,
.section-steel p.section-intro, .section-pale p.section-intro { color: var(--color-text-muted-on-light); }

/* ---- Buttons ----
   Measured anatomy: a rounded-rect chip (~9px radius, NOT a pill) with an
   uppercase mono label on the left and a smaller, differently-shaded
   rounded-square arrow segment inset on the right — not a single flat pill. */
.btn-primary, .btn-secondary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: 4px 4px 4px 20px;
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  border-radius: var(--radius-btn);
  border: 1px solid transparent;
  transition: background var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast);
}
.btn-primary {
  background: var(--color-accent);
  color: var(--color-secondary-muted);
}
.btn-primary .btn-arrow { background: rgba(0, 0, 0, 0.18); color: var(--color-secondary-muted); }
.btn-primary:hover { opacity: 0.88; }
.btn-secondary {
  background: var(--color-card-dark);
  color: var(--color-text-on-dark);
  border-color: var(--color-border);
}
.btn-secondary .btn-arrow { background: rgba(0, 0, 0, 0.25); color: var(--color-text-on-dark); }
.btn-secondary:hover { border-color: var(--color-accent); }
.btn-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: calc(var(--radius-btn) - 2px);
  flex-shrink: 0;
}

/* ---- Header ----
   Transparent AND overlaid (position: absolute, not relative/static):
   the header used to carry its own flat, hardcoded background color
   (rgba(34,40,42,...), a leftover from a much earlier pass, not even
   one of the current palette tokens), which always read as a separate
   bar sitting on top of the page rather than part of it.
   Making the header transparent alone was not enough to fix that,
   though — it still sat in normal document flow above the first
   section, so its own box was really showing whatever `body` painted,
   not the section behind it. A first attempt matched body's fallback
   color to the section's own gradient recipe, but that gradient's
   `at 50% 40%` position is resolved against body's own box, which
   spans the ENTIRE page height (thousands of pixels) — so the "glow
   center" landed far down the page, nowhere near the header, and the
   header still looked flat. Confirmed by directly sampling rendered
   pixel colors at the header/section boundary: a hard jump from flat
   grey to warm gradient exactly at the header's own height, on every
   page.
   The actual fix is structural: the header is taken out of normal
   flow and placed absolutely at the very top, so it overlaps the
   first section instead of pushing it down — the section's own
   background (correctly sized to ITS OWN box, not body's) now paints
   underneath the header too. Each first dark section adds top padding
   equal to the header's own height (see .hero-and-trusted, .hero,
   .hero--full below) so real content still clears the header visually
   despite the section starting underneath it. */
.site-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: transparent;
}
/* services.html only: the page's own first section is light
   (.section-pale), so the header needs dark text/logo instead of the
   light-on-dark treatment every other page's dark hero calls for. */
body.site-header--on-light .main-nav a.nav-link { color: var(--color-text-muted-on-light); }
body.site-header--on-light .main-nav a.nav-link:hover { color: var(--color-text-on-light); }
body.site-header--on-light .nav-toggle { color: var(--color-text-on-light); }
body.site-header--on-light .site-header .logo-img { content: url('assets/outshine-logo-light-bg-web.png'); }
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-4);
  padding-bottom: var(--space-4);
}
/* On dark surfaces (header, footer) this uses a derived cream-wordmark
   lockup (outshine/brand/outshine-derived-primary-dark-bg-cream-wordmark.png)
   built from the approved transparent master with only the black
   wordmark recolored, sun untouched — see approved-logo-usage.md's own
   warning against placing the black wordmark on a dark background.
   Not an approved client master; kept separate from the outshine-approved-*
   files it derives from. */
.logo {
  display: inline-flex;
  align-items: center;
}
.logo-img { height: 34px; width: auto; display: block; }
.main-nav { display: flex; align-items: center; gap: var(--space-8); }
.main-nav ul { display: flex; gap: var(--space-6); }
.main-nav a.nav-link {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  color: var(--color-text-muted-on-dark);
  position: relative;
}
.main-nav a.nav-link:hover { color: var(--color-text-on-dark); }
.nav-cta { display: flex; align-items: center; gap: var(--space-4); }
.nav-toggle {
  display: none;
  background: none;
  border: none;
  color: var(--color-text-on-dark);
  font-size: 24px;
}

/* ---- Mega-menu dropdowns ----
   Measured: a full-width panel of dark cards drops from the header, one
   distinct layout per menu (Products = preview widget + icon-grid card,
   Solutions = two side-by-side product cards, Resources = link list +
   featured-article cards). */
.has-dropdown { position: relative; }
.mega-panel {
  position: absolute;
  top: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
  display: none;
  gap: var(--space-3);
  background: transparent;
  z-index: 90;
}
.has-dropdown:hover .mega-panel,
.has-dropdown:focus-within .mega-panel { display: flex; }
.mega-card {
  background: var(--color-card-dark);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--radius-card);
  padding: var(--space-6);
  box-shadow: 0 20px 48px rgba(0,0,0,0.4);
}

.mega-links-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.mega-link-card {
  background: rgba(255,255,255,0.03);
  border-radius: var(--radius-btn);
  padding: var(--space-4);
  display: block;
}
.mega-link-card:hover { background: rgba(255,255,255,0.07); }
.mega-link-card .icon { width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.06); display: flex; align-items: center; justify-content: center; margin-bottom: var(--space-3); color: var(--color-accent); }
.mega-link-card .icon svg { width: 16px; height: 16px; }
.mega-link-card .title { font-size: 14px; font-weight: 700; color: var(--color-text-on-dark); }

/* Solutions mega-menu */
.mega-solutions { width: 560px; }
.mega-solutions-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.mega-solution-card { min-height: 220px; display: flex; flex-direction: column; }
.mega-solution-card h4 { font-size: 16px; margin-bottom: 4px; }
.mega-solution-card p { font-size: 13px; color: var(--color-text-muted-on-dark); }
.mega-solution-visual { margin-top: auto; padding-top: var(--space-6); display: flex; justify-content: center; align-items: center; font-size: 28px; opacity: 0.8; }

/* Resources mega-menu */
.mega-resources { width: 640px; }
.mega-resources-inner { display: grid; grid-template-columns: 200px 1fr; gap: var(--space-3); }
.mega-resources .resource-links { display: flex; flex-direction: column; gap: 2px; }
.mega-resources .resource-links a {
  font-family: var(--font-ui);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-text-on-dark);
}
.mega-resources .resource-links a:hover { background: rgba(255,255,255,0.06); }
.mega-resources .resource-links a.is-disabled { color: var(--color-text-muted-on-dark); opacity: 0.5; pointer-events: none; }
.mega-resources .featured-label { font-family: var(--font-ui); font-size: 11px; text-transform: uppercase; color: var(--color-text-muted-on-dark); margin-bottom: var(--space-3); }
.featured-article { display: flex; gap: var(--space-3); margin-bottom: var(--space-3); }
.featured-article:last-child { margin-bottom: 0; }
.featured-article .thumb { width: 64px; height: 64px; border-radius: var(--radius-sm); flex-shrink: 0; }
.featured-article .tag-pill { margin-bottom: 4px; }
.featured-article .title { font-size: 13px; font-weight: 700; color: var(--color-text-on-dark); }

/* ---- Floating dock nav ----
   Measured: a second, always-present nav rendered as a dark pill docked near
   the bottom of the viewport (not scroll-triggered — it's simply always
   there, floating above page content, alongside the sticky top header). */
.floating-dock {
  position: fixed;
  left: 50%;
  /* 24px baseline, plus whatever the device's own home-indicator /
     gesture-bar safe area needs on top of that (0 on devices without
     one, since env() falls back to 0 when the constant isn't
     supported/needed). Without this the dock sat right in the iOS
     home-indicator's swipe zone on notched phones. */
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  /* Reveal mechanism is opacity + transform (scale/translate), not
     clip-path. An earlier version used a clip-path wipe for a more
     distinctive open/close look, but clip-path animations are not as
     reliably cheap to composite as opacity/transform across browsers —
     combined with this element re-triggering fairly often (any scroll
     crossing near its trigger point, see main.js's debounce), that
     extra cost was a real, reported source of visible jank/stutter
     during scrolling. opacity and transform are the two properties
     virtually guaranteed to run on the compositor thread with no
     paint step, which removes the jank at its source rather than only
     tuning timing/debounce around it. */
  transform-origin: bottom center;
  opacity: 1;
  transform: translateX(-50%) translateY(0) scale(1);
  z-index: 150;
  pointer-events: auto;
  /* Shortened from an earlier 1000ms: at that length, scrolling back
     and forth across the trigger point (completely normal while
     reading a page, not an edge case) interrupted and restarted the
     animation on every crossing, each restart snapping direction
     mid-flight — read as the dock "breaking"/stuttering while
     scrolling. 420ms is fast enough that even a rapid re-trigger
     finishes (or is very close to finished) before the next one can
     land, and main.js's own DOCK_TOGGLE_DEBOUNCE now also holds off
     on flipping state at all until the visitor's scroll position has
     settled briefly, so a quick oscillation across the boundary no
     longer causes back-to-back restarts in the first place. */
  transition: opacity 420ms cubic-bezier(0.4, 0, 0.2, 1), transform 420ms cubic-bezier(0.4, 0, 0.2, 1);
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--color-card-dark);
  border-radius: var(--radius-card);
  padding: 6px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.4);
}
/* Staggered items: the return-to-top button, then each of the 4 dock
   links (nested inside .dock-links, not direct children of
   .floating-dock — .dock-top and .dock-links itself are the only true
   direct children) fade/rise in with their own delay once .is-hidden
   is removed, echoing Effortel's own stagger: { each: 0.05 } on the
   floating nav's children, timed inside the parent's 1s reveal rather
   than after it.
   Every item gets its own base rule here (an earlier version relied
   on .floating-dock > * for this, which never reaches the nested
   .dock-link anchors at all — they inherited no fade rule on close and
   stayed stuck fully visible while everything else disappeared,
   which is what read as the close "breaking").
   The stagger delay is opening-only (see the :not(.is-hidden) rules):
   applying the same delay to closing left later items still fully
   visible and un-delayed while the parent's clip-path had already
   started collapsing around them. Closing fades everything out
   together, immediately, no delay. */
.floating-dock .dock-top,
.floating-dock .dock-link {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 200ms ease, transform 200ms ease;
}
.floating-dock.is-hidden .dock-top,
.floating-dock.is-hidden .dock-link {
  opacity: 0;
  transform: translateY(8px);
  transition-delay: 0ms;
}
.floating-dock:not(.is-hidden) .dock-top { transition: opacity 220ms ease, transform 220ms ease; transition-delay: 0ms; }
.floating-dock:not(.is-hidden) .dock-link:nth-child(1) { transition: opacity 220ms ease, transform 220ms ease; transition-delay: 30ms; }
.floating-dock:not(.is-hidden) .dock-link:nth-child(2) { transition: opacity 220ms ease, transform 220ms ease; transition-delay: 60ms; }
.floating-dock:not(.is-hidden) .dock-link:nth-child(3) { transition: opacity 220ms ease, transform 220ms ease; transition-delay: 90ms; }
.floating-dock:not(.is-hidden) .dock-link:nth-child(4) { transition: opacity 220ms ease, transform 220ms ease; transition-delay: 120ms; }
.floating-dock .dock-top {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 50%;
  background: rgba(255,255,255,0.06);
  color: var(--color-text-on-dark);
  flex-shrink: 0;
}
.floating-dock .dock-links {
  display: flex;
  align-items: center;
  gap: 4px;
}
.floating-dock .dock-link {
  font-family: var(--font-ui);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  color: var(--color-text-muted-on-dark);
  padding: 8px 14px;
  border-radius: var(--radius-btn);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.floating-dock .dock-link:hover { color: var(--color-text-on-dark); background: rgba(255,255,255,0.06); }
.floating-dock .dock-cta {
  background: var(--color-accent);
  color: var(--color-secondary-muted);
  font-weight: 500;
}
.floating-dock .dock-cta:hover { color: var(--color-secondary-muted); opacity: 0.88; }
@media (max-width: 900px) {
  .floating-dock .dock-links a:not(.dock-cta) { display: none; }
}
/* Added by JS only once it can track scroll position — starts hidden so
   it doesn't flash on load, then rises into place (fade + scale-up
   settle, its own children staggering in) once the trigger point is
   reached. No-JS visitors never get .is-hidden added, so the dock
   stays visible and usable as a fallback nav rather than disappearing
   outright.
   Closing runs slightly faster than opening (380ms vs. 420ms) and
   without the staggered child delays above — those delays made sense
   unfolding open, but on close they left later children still fully
   visible, un-delayed, while the parent had already started collapsing
   around them, which is what read as the close breaking instead of
   collapsing smoothly. */
.floating-dock.is-hidden {
  opacity: 0;
  transform: translateX(-50%) translateY(16px) scale(0.92);
  pointer-events: none;
  transition-duration: 380ms;
}
@media (prefers-reduced-motion: reduce) {
  .floating-dock, .floating-dock > * { transition: opacity 200ms ease !important; transform: none !important; }
  .floating-dock.is-hidden { opacity: 0; transform: translateX(-50%) !important; }
}

/* ---- Scroll progress scrubber ----
   A thin track across the very top of the viewport that fills as the
   visitor scrolls, with a draggable thumb (percentage readout) that
   can also be grabbed directly to jump to any scroll position. Desktop
   only (see main.js): on touch devices the thumb would fight native
   scroll gestures, so the whole control is hidden there and native
   scrolling is left alone. */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  /* Hoverable hit area is much taller than the visible 3px track
     (matching Effortel's own 2em hover zone around a thin bar) so the
     thumb can actually be discovered and grabbed without pixel-perfect
     aim on a 3px-tall strip. */
  height: 20px;
  z-index: 300;
  pointer-events: auto;
}
.scroll-progress-track {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: rgba(0,0,0,0.08);
  pointer-events: none;
}
/* The gold fill line stays visible throughout scrolling (like
   Effortel's own always-on .flex-inner); only the draggable pill with
   its percentage readout is hover-gated below. Positioned by the same
   JS that positions the thumb, as its own element rather than a
   pseudo-element of the thumb, so hiding the thumb on mouseout doesn't
   also hide the fill it used to be attached to. */
.scroll-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  height: 3px;
  width: 0;
  background: var(--color-accent);
  pointer-events: none;
}
.scroll-progress-thumb {
  position: absolute;
  top: 0;
  left: 0;
  transform: translate(0, 0);
  display: flex;
  align-items: center;
  gap: 1px;
  height: 26px;
  margin-top: -12px;
  padding: 0 10px;
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-secondary-muted);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  box-shadow: 0 6px 16px rgba(0,0,0,0.25);
  pointer-events: auto;
  cursor: grab;
  opacity: 0;
  transition: opacity 200ms ease;
}
/* Hover anywhere on the track (or the thumb itself, or while actively
   dragging) reveals the thumb + percentage, matching Effortel's own
   .progress-wrap:hover behavior; it's invisible the rest of the time
   so the readout doesn't compete with the page for attention. */
.scroll-progress:hover .scroll-progress-thumb,
.scroll-progress-thumb:hover,
.scroll-progress.is-dragging .scroll-progress-thumb {
  opacity: 1;
}
.scroll-progress-thumb:active { cursor: grabbing; }
.scroll-progress-percent { opacity: 0.75; }

/* Desktop-only: matches the equivalent gate in main.js so no dead,
   unusable control ships to touch visitors. */
@media (max-width: 1023px), (hover: none), (pointer: coarse) {
  .scroll-progress { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-progress-thumb { transition: none; }
}

/* ---- Hero + Trusted-by: one merged section ----
   .hero-and-trusted is the actual section-level element (background, overall
   clipping); .hero and .logo-marquee inside it are plain content blocks with
   no background/divider of their own, so nothing marks a boundary between
   them and they read as one continuous piece.
   The vignette/gradient recipe itself now lives once, on the shared
   .section-dark/.section-navy rule above, so every dark section gets the
   same richness. This override exists only to swap the final color stop
   from the everyday --color-bg-dark to the deeper --color-bg-black — the
   one section on the site meant to read as its darkest moment — without
   duplicating the whole gradient stack a second time. */
.hero-and-trusted {
  position: relative;
  overflow: hidden;
  background-color: var(--color-bg-black);
}
/* This section's own aurora layer specifically (not the shared
   .aurora-layer base rule used on about/projects/contact) toned down
   to a subtle trace rather than a colorful shifting blend, matching
   the same "keep the palette, quiet the motion/color" request above. */
.hero-and-trusted > .aurora-layer { opacity: 0.35; }
.hero { padding: var(--space-32) 0 var(--space-8); text-align: center; position: relative; }
.hero .eyebrow { display: inline-flex; }
.hero p.lede { max-width: 640px; margin: var(--space-12) auto 0; color: var(--color-text-muted-on-dark); font-size: 18px; }
.hero .hero-actions { display: flex; gap: var(--space-4); justify-content: center; margin-top: var(--space-8); }

/* Full-viewport hero (projects.html, contact.html): the brown hero fills the
   visitor's first view entirely, so the cream section beneath it is
   not visible until they scroll, rather than both bands being
   partially visible on load. Scoped to this modifier rather than the
   base .hero rule so about.html/contact.html's shorter heroes are
   unaffected. min-height (not height) so a long headline/lede on a
   short viewport can still push the section taller instead of
   clipping content. */
.hero--full { min-height: 100vh; display: flex; align-items: center; padding-top: var(--space-16); padding-bottom: var(--space-16); }

/* Interactive wireframe globe (own hand-rolled canvas projection, drag to rotate) */
.hero-globe-wrap {
  position: absolute;
  left: 50%;
  bottom: -180px;
  transform: translateX(-50%);
  width: 520px;
  height: 520px;
  pointer-events: none;
  z-index: 0;
}
.hero-globe-wrap canvas { pointer-events: auto; }
/* touch-action: pan-y (not none): the globe only rotates off horizontal
   drag distance (see globe.js's pointerMove, rotY driven by dx). Letting
   vertical touch gestures pass through to native scroll means a visitor
   whose thumb lands on the globe while scrolling the page isn't trapped
   into rotating it instead — measured live, a plain `none` here silently
   ate every scroll swipe that started on the hero's globe artwork. */
.hero-globe { width: 100%; height: 100%; display: block; touch-action: pan-y; user-select: none; }
/* pointer-events:none lets clicks/hovers fall through to the globe canvas
   in the transparent padding around the hero text; the text and buttons
   inside opt back in individually so they stay fully interactive. */
.hero .container { position: relative; z-index: 1; pointer-events: none; }
.hero .container > * { pointer-events: auto; }
@media (max-width: 700px) {
  .hero-globe-wrap { width: 340px; height: 340px; bottom: -120px; }
}

/* ---- Logo marquee ---- */
.logo-marquee { position: relative; padding: var(--space-4) 0 var(--space-8); overflow-x: clip; }
.logo-marquee .track {
  display: flex;
  gap: var(--space-16);
  width: max-content;
  animation: marquee 40s linear infinite;
}
.logo-marquee .track span {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  color: var(--color-text-muted-on-dark);
  white-space: nowrap;
}
.logo-marquee .track img {
  height: 40px;
  width: auto;
  display: block;
}
/* Logos supplied on a solid white background get the same badge treatment
   as the Outshine wordmark, so they don't show a visible white box on the
   dark marquee band. */
.logo-marquee .track .client-badge {
  background: var(--color-bg-pale);
  border-radius: var(--radius-sm);
  padding: 6px 12px;
  display: inline-flex;
  align-items: center;
}
.logo-marquee .track .client-badge img { height: 32px; }
@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .logo-marquee .track { animation: none; }
}

/* ---- Grids ---- */
.icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-6);
  margin-top: var(--space-12);
}
.icon-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.icon-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.icon-grid.cols-6 { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 900px) {
  .icon-grid.cols-3, .icon-grid.cols-4, .icon-grid.cols-6 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
  .icon-grid, .icon-grid.cols-3, .icon-grid.cols-4, .icon-grid.cols-6 { grid-template-columns: 1fr; }
}

/* ---- Card ---- */
.card {
  background: var(--color-card-dark);
  border-radius: var(--radius-card);
  padding: var(--space-6);
  border: 1px solid rgba(255,255,255,0.06);
}
.section-steel .card, .section-pale .card { background: var(--color-card-steel); border-color: rgba(0,0,0,0.06); }
.card .icon {
  width: 40px;
  height: 40px;
  margin-bottom: var(--space-4);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 11px;
  background: var(--color-icon-chip-1);
  color: var(--color-secondary-muted);
}
.card .icon svg { width: 19px; height: 19px; }
/* Measured pastel icon-chip rotation (cycles through the 4 real colors) */
.icon-grid .card:nth-child(4n+1) .icon { background: var(--color-icon-chip-1); }
.icon-grid .card:nth-child(4n+2) .icon { background: var(--color-icon-chip-2); }
.icon-grid .card:nth-child(4n+3) .icon { background: var(--color-icon-chip-3); }
.icon-grid .card:nth-child(4n+4) .icon { background: var(--color-icon-chip-4); }
.card h3 { margin-bottom: var(--space-2); }
.card p { color: var(--color-text-muted-on-dark); font-size: 15px; }
.section-steel .card p, .section-pale .card p { color: var(--color-text-muted-on-light); }
.card a.learn-more { display: inline-block; margin-top: var(--space-3); color: var(--color-accent); font-size: 14px; font-weight: 600; }

/* ---- Status chip ----
   Visible, permanent disclosure-status label for a case-study card.
   Replaces a hover-only data-cursor tooltip that carried the same
   information invisibly to touch devices and screen readers: this is
   real text in the DOM, always rendered, so the confidentiality tier
   reads the same regardless of input method. Three tiers, one shared
   shape, differing only in tint so the level (not just the presence)
   of disclosure is visible at a glance. */
.status-chip {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 4px 10px;
  border-radius: var(--radius-btn);
  margin-bottom: var(--space-3);
}
/* Text-safe gold on a light card background is --color-secondary-muted
   (dark brown), matching .section-steel/.section-pale .eyebrow just
   above — raw --color-accent gold-on-cream fails contrast, the same
   reason that rule exists. --nda originally used
   --color-text-muted-on-light (correct at ~5.7:1 directly on the card,
   but only 3.91:1 once composited over its own light rgba(0.14) chip
   fill, an AA fail on the most confidential card specifically) — uses
   the same dark --color-secondary-muted as the other two variants
   instead, which clears AA comfortably against any of the three tints. */
.status-chip--nda { background: rgba(110,100,94,0.18); color: var(--color-secondary-muted); }
.status-chip--progress { background: rgba(242,165,26,0.16); color: var(--color-secondary-muted); }
.status-chip--summary { background: rgba(242,165,26,0.26); color: var(--color-secondary-muted); }

.case-study-card { display: flex; flex-direction: column; }
.case-study-accordion-item {
  margin-top: var(--space-4);
  padding: var(--space-2) 0 0;
  border-bottom: none;
  color: var(--color-secondary-muted);
  font-size: 13px;
  font-weight: 600;
}
.case-study-accordion-item .label { flex: 1; }
.case-study-accordion-item .chevron { color: inherit; }

/* ---- Tag cloud ----
   A lighter-weight alternative to the .card grid for a flat list of
   client names with no per-item detail to show (unlike case-study
   cards, there's nothing to expand into) — pills fill the section's
   width instead of leaving it mostly empty next to one sentence. Lives
   on .section-navy (dark), so raw --color-accent gold is the correct
   text-safe color here, matching .section-dark/.section-navy .eyebrow.
   Scoped as .engagement-list .tag-pill--warm (not bare
   .tag-pill--warm): the plain .tag-pill rule declared later in this
   file has equal specificity otherwise, so background/font-size/
   padding were being silently overridden by it, leaving only color
   correct by coincidence. The extra ancestor selector outranks that
   rule without reordering the stylesheet. */
.engagement-list { display: flex; flex-wrap: wrap; gap: var(--space-6); }
.engagement-item { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.engagement-item-detail { font-size: 13px; color: var(--color-text-muted-on-dark); max-width: 220px; }
.engagement-list .tag-pill--warm { background: rgba(242,165,26,0.14); color: var(--color-accent); font-size: 14px; padding: 8px 16px; }
.section-steel .engagement-list .tag-pill--warm, .section-pale .engagement-list .tag-pill--warm { color: var(--color-secondary-muted); }

/* ---- Homepage "About us" section ----
   Flipped from dark to section-pale (white) so index.html's main sections
   strictly alternate dark/white/dark/off-white starting from the hero,
   instead of stacking two dark sections back to back. Every child rule
   below that referenced an on-dark text token now uses the on-light
   equivalent; the photo cards use --color-card-steel (the light-band
   card tint) instead of --color-card-dark. */
.about-home-grid {
  display: grid;
  grid-template-columns: minmax(280px, 380px) 1fr;
  gap: var(--space-16);
  align-items: start;
}
.about-home-copy .lede { font-size: 18px; color: var(--color-text-muted-on-light); margin-bottom: var(--space-4); }
.about-home-copy p { color: var(--color-text-muted-on-light); line-height: 1.7; }
.about-home-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
  margin-top: var(--space-12);
}
.about-home-stat { display: flex; flex-direction: column; gap: 4px; }
.about-home-stat-value {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  color: var(--color-accent);
}
.about-home-stat-label { font-size: 13px; color: var(--color-text-muted-on-light); }

.about-home-photos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-auto-rows: 1fr;
  gap: var(--space-6);
  align-items: stretch;
}
/* First photo spans both columns (the reference's wide handshake card);
   the other two sit side by side beneath it. */
.about-home-photo--wide { grid-column: 1 / -1; }
.about-home-photo {
  position: relative;
  margin: 0;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--color-card-steel);
  /* Each card is a column so the caption sits in its own flexible
     block below the (fixed-height) image, rather than the caption's
     own content height deciding the whole card's height inconsistently
     between a 1-line and a 2-line caption in the same row. */
  display: flex;
  flex-direction: column;
  height: 100%;
}
.about-home-photo img { width: 100%; height: 220px; object-fit: cover; display: block; flex-shrink: 0; }
.about-home-photo--wide img { height: 180px; }
/* Bottom padding matches the sides (not shorter, as it was): with
   overflow:hidden plus a rounded bottom corner on the card, a caption
   that wraps to two lines and has little-to-no bottom clearance reads
   as clipped by that curve. flex:1 lets this block grow to fill
   whatever height the grid row settles on (see grid-auto-rows: 1fr and
   align-items: stretch above), instead of every card being only as
   tall as its own shortest caption. */
.about-home-photo figcaption {
  flex: 1;
  padding: var(--space-6);
}
.about-home-photo h3 { font-size: 16px; margin-bottom: var(--space-2); color: var(--color-text-on-light); }
.about-home-photo p { font-size: 13px; color: var(--color-text-muted-on-light); line-height: 1.6; }

@media (max-width: 900px) {
  .about-home-grid { grid-template-columns: 1fr; }
  .about-home-photos { grid-template-columns: 1fr; }
  .about-home-photo--wide { grid-column: auto; }
  .about-home-stats { grid-template-columns: 1fr 1fr; }
}

/* ---- Homepage "Our services" section ----
   A vertical category list on the left; clicking (or, on desktop,
   hovering) an item swaps which detail panel shows on the right. Panels
   stay in the DOM together (not re-rendered per click) so the image
   underneath each one is already loaded before it's ever revealed.
   Section flipped from section-pale to section-dark (see the About-us
   comment above) so index.html's sections alternate dark/white/dark/
   steel; every rule below that referenced an on-light token or a
   light-band card tint now uses the on-dark equivalent instead. */
.services-home-grid {
  display: grid;
  grid-template-columns: minmax(220px, 280px) 1fr;
  gap: var(--space-12);
  align-items: start;
}
.services-home-list { display: flex; flex-direction: column; }
.services-home-item {
  text-align: left;
  padding: var(--space-4) 0;
  border: none;
  border-bottom: 1px solid rgba(255,255,255,0.1);
  background: none;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
  color: var(--color-text-muted-on-dark);
  cursor: pointer;
  transition: color 180ms ease;
}
.services-home-item:first-child { padding-top: 0; }
.services-home-item.is-active {
  color: var(--color-text-on-dark);
  border-bottom-color: var(--color-text-on-dark);
}
.services-home-item:hover { color: var(--color-text-on-dark); }

.services-home-panel-wrap { position: relative; }
.services-home-panel {
  display: none;
  background: var(--color-card-dark);
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: 0 20px 48px rgba(0,0,0,0.35);
}
.services-home-panel.is-active { display: block; animation: reveal-in 400ms ease both; }
.services-home-panel img { width: 100%; height: 260px; object-fit: cover; display: block; }
.services-home-panel-body { padding: var(--space-8); }
.services-home-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  border-bottom: 1px solid rgba(255,255,255,0.1);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-4);
}
.services-home-panel-head h3 { font-size: 22px; }
.services-home-panel-link {
  flex-shrink: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-on-dark);
  text-decoration: underline;
  white-space: nowrap;
}
.services-home-panel-body > p { color: var(--color-text-muted-on-dark); line-height: 1.7; margin-bottom: var(--space-6); }
.services-home-panel-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-on-dark);
  margin-bottom: var(--space-3);
}
.services-home-checklist {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4) var(--space-6);
  background: var(--color-bg-black);
  border-radius: var(--radius-sm);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
}
.services-home-checklist li {
  position: relative;
  padding-left: var(--space-4);
  font-size: 13px;
  line-height: 1.6;
  color: var(--color-text-on-dark);
}
.services-home-checklist li::before {
  content: '\2713';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--color-accent);
  font-size: 12px;
}
.services-home-cta { display: inline-flex; }

@media (max-width: 900px) {
  .services-home-grid { grid-template-columns: 1fr; }
  .services-home-checklist { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .services-home-panel.is-active { animation: none; }
}

/* Gradient art header, shared sizing across every card, one distinct
   animated abstract pattern per service so each card's top section reads as
   that service's own "mood" rather than a repeated decorative block. */
.service-card-art {
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  height: 38%;
  border-radius: var(--radius-btn);
  margin-bottom: var(--space-4);
  background: linear-gradient(150deg, var(--color-accent) 0%, var(--color-accent-pale) 55%, var(--color-card-steel) 100%);
}

/* Business Systems and Automation: interlocking rotating rings, evoking
   connected/automated processes. */
.service-card-art--systems {
  background: linear-gradient(150deg, var(--color-icon-chip-2) 0%, var(--color-icon-chip-1) 100%);
}
.service-card-art--systems::before,
.service-card-art--systems::after {
  content: "";
  position: absolute;
  border: 2.5px solid rgba(255,255,255,0.55);
  border-radius: 50%;
}
.service-card-art--systems::before { width: 64px; height: 64px; top: 20%; left: 18%; animation: sc-spin 7s linear infinite; }
.service-card-art--systems::after { width: 46px; height: 46px; bottom: 12%; right: 16%; border-color: rgba(255,255,255,0.4); animation: sc-spin-rev 5s linear infinite; }

/* Digital Products: stacked overlapping screens/cards drifting gently,
   evoking interfaces and products in motion. */
.service-card-art--products {
  background: linear-gradient(150deg, var(--color-icon-chip-4) 0%, var(--color-card-steel) 100%);
}
.service-card-art--products::before,
.service-card-art--products::after {
  content: "";
  position: absolute;
  width: 58px;
  height: 40px;
  border-radius: 8px;
  background: rgba(255,255,255,0.5);
  animation: sc-drift 4.5s ease-in-out infinite;
}
.service-card-art--products::before { top: 22%; left: 20%; }
.service-card-art--products::after { top: 42%; left: 40%; background: rgba(255,255,255,0.75); animation-delay: 0.6s; }

/* Infrastructure and Security: a soft pulsing shield/lock silhouette,
   evoking protection and stability. */
.service-card-art--infrastructure {
  background: linear-gradient(150deg, var(--color-secondary-muted) 0%, var(--color-icon-chip-2) 130%);
}
.service-card-art--infrastructure::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 70px;
  height: 80px;
  transform: translate(-50%, -50%);
  background: rgba(255,255,255,0.55);
  clip-path: polygon(50% 0%, 100% 20%, 100% 55%, 50% 100%, 0% 55%, 0% 20%);
  animation: sc-pulse 2.6s ease-in-out infinite;
}

/* Brand and Digital Growth: a rising trio of bars/sparks, evoking growth and
   creative output. */
.service-card-art--brand {
  background: linear-gradient(150deg, var(--color-icon-chip-1) 0%, var(--color-icon-chip-4) 100%);
}
.service-card-art--brand::before,
.service-card-art--brand::after,
.service-card-art--brand .service-card-art-bar {
  content: "";
  position: absolute;
  bottom: 16%;
  width: 14px;
  border-radius: 4px;
  background: rgba(255,255,255,0.6);
  animation: sc-rise 2.2s ease-in-out infinite;
}
.service-card-art--brand::before { left: 28%; height: 30%; animation-delay: 0s; }
.service-card-art--brand .service-card-art-bar { left: 48%; height: 46%; animation-delay: 0.3s; background: rgba(255,255,255,0.85); }
.service-card-art--brand::after { left: 68%; height: 22%; animation-delay: 0.6s; }

@keyframes sc-spin { to { transform: rotate(360deg); } }
@keyframes sc-spin-rev { to { transform: rotate(-360deg); } }
@keyframes sc-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}
@keyframes sc-pulse {
  0%, 100% { opacity: 0.55; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 0.85; transform: translate(-50%, -50%) scale(1.06); }
}
@keyframes sc-rise {
  0%, 100% { transform: scaleY(1); transform-origin: bottom; }
  50% { transform: scaleY(1.18); transform-origin: bottom; }
}
@media (prefers-reduced-motion: reduce) {
  .service-card-art::before, .service-card-art::after, .service-card-art-bar { animation: none !important; }
}
/* ---- Services page: one-page detail rows ----
   Three-column row per service (art / copy / accordion list), reusing
   the same gradient-art treatment built for the homepage's What We Do
   cards so this page doesn't introduce a second visual language for
   the same four services. */
.services-detail { padding-top: var(--space-16); }
.services-page-title { margin-bottom: var(--space-16); max-width: 720px; }
.service-row {
  display: grid;
  grid-template-columns: 220px 1fr 340px;
  gap: var(--space-8);
  align-items: start;
  padding: var(--space-12) 0;
  border-bottom: 1px solid rgba(0,0,0,0.08);
  scroll-margin-top: var(--space-12);
}
.service-row:first-child { padding-top: 0; }
.service-row:last-child { border-bottom: none; }

/* Real photos, one per service category, replacing the earlier
   animated CSS-only shapes (spinning rings, drifting cards, a pulsing
   shield, rising bars) that lived here — kept as a plain photo frame
   now, no pseudo-element sizing needed since there's no decorative
   shape left to scale. */
.service-row-art {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-card);
  position: relative;
  overflow: hidden;
}
.service-row-art img { width: 100%; height: 100%; object-fit: cover; display: block; }

.service-row-copy .eyebrow { display: inline-block; margin-bottom: var(--space-3); }
.service-row-copy h2 { margin-bottom: var(--space-4); }
.service-row-copy p { color: var(--color-text-muted-on-light); line-height: 1.7; }

.service-row-list { display: flex; flex-direction: column; }
.service-accordion-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-4) 0;
  border-bottom: 1px solid rgba(0,0,0,0.08);
  background: none;
  border-left: none;
  border-right: none;
  border-top: none;
  text-align: left;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  color: var(--color-text-on-light);
  cursor: pointer;
}
.service-row-list > *:first-child.service-accordion-item { padding-top: 0; }
.service-accordion-item .icon { flex-shrink: 0; display: flex; color: var(--color-accent); }
.service-accordion-item .icon svg { width: 17px; height: 17px; }
.service-accordion-item .label { flex: 1; }
.service-accordion-item .chevron {
  flex-shrink: 0;
  color: var(--color-accent);
  transition: transform 220ms ease;
}
.service-accordion-item[aria-expanded="true"] .chevron { transform: rotate(180deg); }

.service-accordion-panel {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 260ms ease, opacity 200ms ease;
}
.service-accordion-panel.is-open { grid-template-rows: 1fr; opacity: 1; }
.service-accordion-panel > p {
  overflow: hidden;
  margin: 0;
  padding-bottom: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--color-text-muted-on-light);
}
.service-accordion-panel.is-open > p { padding-bottom: var(--space-4); }

@media (prefers-reduced-motion: reduce) {
  .service-accordion-panel, .service-accordion-item .chevron { transition: none; }
}

@media (max-width: 1000px) {
  .service-row { grid-template-columns: 160px 1fr; grid-template-areas: "art copy" "list list"; }
  .service-row-art { grid-area: art; }
  .service-row-copy { grid-area: copy; }
  .service-row-list { grid-area: list; margin-top: var(--space-6); }
}
@media (max-width: 640px) {
  .service-row { grid-template-columns: 1fr; grid-template-areas: "art" "copy" "list"; }
  .service-row-art { max-width: 160px; }
  /* Measured at 342x35 on a 390px viewport: comfortably wide but under
     the 44px touch-target minimum in height. min-height (not more
     padding) keeps the existing type size and vertical rhythm and just
     gives the tap area room to breathe. */
  .service-accordion-item { min-height: 44px; }
}
.service-card p { color: var(--color-text-muted-on-light); font-size: 13px; line-height: 1.6; margin: 0; }
.service-card-icon-btn {
  align-self: flex-start;
  flex-shrink: 0;
  /* margin-top: auto (not space-between on the card) pushes the button
     to the bottom while leaving the card's own padding as the actual
     clearance to the edge — that clearance stays constant regardless
     of how much text sits above it, instead of varying with content
     length the way space-between's distribution would. */
  margin-top: auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--color-bg-pale);
  border: 1px solid rgba(0,0,0,0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  line-height: 1;
  color: var(--color-text-on-light);
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease, transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* The → glyph's inked shape sits above and right of its own em-box
   center (measured directly: flex align-items/justify-content center
   the line box, not the glyph's actual ink), which reads as visibly
   off-center in a small round button. .service-card-icon-btn-glyph
   wraps the character in main.js-free markup so it can be nudged onto
   true visual center without affecting the button's own hover-shift
   transform. */
.service-card-icon-btn-glyph { display: block; transform: translate(0px, -1px); }
.service-card-icon-btn:hover {
  background: var(--color-accent);
  color: var(--color-secondary-muted);
  border-color: var(--color-accent);
  transform: translateX(3px);
}

/* ---- Vision / Mission: full-bleed split screen ----
   The words themselves are the graphic: VISION / MISSION set at
   display-numeral scale fill the space that a small label + a lot of
   empty background used to leave dead. A hard vertical divider
   (a single 1px line, not two independent boxes) reinforces the
   split; statement copy sits smaller beneath each word, doing the
   explaining once the word has already done the announcing. Section
   flipped from section-navy to section-pale (about.html alternation
   fix, see the "Our experience" section below) so every rule that
   referenced an on-dark text token now uses the on-light equivalent;
   the divider line darkened to match. */
.section-tight { padding: var(--space-16) 0; }
.vm-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  position: relative;
}
.vm-split::before {
  content: '';
  position: absolute;
  top: 10%;
  bottom: 10%;
  left: 50%;
  width: 1px;
  background: rgba(0,0,0,0.12);
}
.vm-half {
  padding: var(--space-16) var(--space-8);
  display: flex;
  flex-direction: column;
}
.vm-half--vision { align-items: flex-end; text-align: right; padding-right: var(--space-16); }
.vm-half--mission { align-items: flex-start; text-align: left; padding-left: var(--space-16); }
.vm-word {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(56px, 8vw, 108px);
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--color-text-on-light);
  margin-bottom: var(--space-6);
}
.vm-half--vision .vm-word { color: var(--color-accent-on-light); }
.vm-half p {
  max-width: 360px;
  color: var(--color-text-muted-on-light);
  font-size: 17px;
  line-height: 1.7;
}

@media (max-width: 800px) {
  .vm-split { grid-template-columns: 1fr; }
  .vm-split::before { display: none; }
  .vm-half, .vm-half--vision, .vm-half--mission {
    align-items: flex-start;
    text-align: left;
    padding: var(--space-8) var(--space-6);
  }
  .vm-half--vision { border-bottom: 1px solid rgba(255,255,255,0.1); }
  .vm-word { font-size: clamp(44px, 14vw, 64px); }
  .vm-half p { max-width: none; }
}

/* ---- Numbered step ---- */
.numbered-step { display: flex; gap: var(--space-6); padding: var(--space-6) 0; border-bottom: 1px solid rgba(255,255,255,0.08); }
.numbered-step .index { font-family: var(--font-ui); color: var(--color-accent); font-size: 14px; min-width: 32px; }
.numbered-step .icon { width: 32px; height: 32px; flex-shrink: 0; }
.numbered-step .content h3 { margin-bottom: var(--space-2); }
.numbered-step .content p { color: var(--color-text-muted-on-dark); }

/* ---- Workflow zigzag (How we work) ----
   A vertical centre line with numbered/icon circles, steps alternating left
   and right of the line, connected by a short dashed segment from the line
   to each step's own heading. Own original layout, own icon glyphs, brand
   colours (not a copy of any reference's specific icon art or palette). */
.workflow { position: relative; padding: var(--space-8) 0; }
.workflow::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 32px;
  bottom: 32px;
  width: 2px;
  background: repeating-linear-gradient(to bottom, currentColor 0 6px, transparent 6px 12px);
  opacity: 0.25;
  transform: translateX(-50%);
}
.workflow-step {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  min-height: 140px;
}
.workflow-step:not(:last-child) { margin-bottom: var(--space-8); }
.workflow-step-icon {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--color-accent);
  color: var(--color-secondary-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  box-shadow: 0 8px 20px color-mix(in oklab, var(--color-accent) 45%, transparent);
}
.workflow-step-icon svg { width: 24px; height: 24px; }
.workflow-step-body {
  grid-column: 1;
  max-width: 360px;
  margin-left: auto;
  padding-right: calc(var(--space-8) + 28px);
  text-align: right;
}
.workflow-step:nth-child(even) .workflow-step-body {
  grid-column: 2;
  margin-left: 0;
  margin-right: auto;
  padding-right: 0;
  padding-left: calc(var(--space-8) + 28px);
  text-align: left;
}
.workflow-step-index { font-family: var(--font-ui); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-accent); margin-bottom: var(--space-2); display: block; }
.workflow-step-body h3 { margin-bottom: var(--space-2); }
.workflow-step-body p { color: var(--color-text-muted-on-dark); font-size: 14px; }
.section-steel .workflow-step-body p, .section-pale .workflow-step-body p { color: var(--color-text-muted-on-light); }

@media (max-width: 720px) {
  .workflow::before { left: 27px; }
  .workflow-step { grid-template-columns: 1fr; padding-left: 68px; min-height: 0; }
  .workflow-step-icon { left: 27px; top: 4px; transform: translate(-50%, 0); width: 44px; height: 44px; }
  .workflow-step-icon svg { width: 20px; height: 20px; }
  .workflow-step-body, .workflow-step:nth-child(even) .workflow-step-body {
    grid-column: 1;
    text-align: left;
    padding: 0;
  }
}

/* ---- Journey (project roadmap component, about.html only) ----
   A dark editorial panel on the page's light background: caption/
   title/subtitle top-left, a short description top-right, then an
   organic SVG path running left-to-right with one interactive marker
   per stage. Content is rendered by journey.js from a stages array —
   nothing about this CSS assumes a fixed stage count. index.html
   keeps its own separate .workflow zigzag; this is a distinct
   component, not a restyle of that one. */
.journey {
  /* Same gradient recipe as .section-dark/.section-navy and the
     footer, for the same reason: this card is a real dark surface on
     the page (about.html), not exempt from the site-wide "every dark
     surface gets this richness" rule just because it's a rounded card
     rather than a full-bleed section. */
  background:
    radial-gradient(ellipse 900px 700px at 50% 40%, rgba(242, 165, 26, 0.07) 0%, transparent 60%),
    radial-gradient(ellipse 1100px 800px at 50% 75%, rgba(150, 70, 170, 0.05) 0%, transparent 65%),
    radial-gradient(ellipse 1400px 1000px at 50% 45%, transparent 0%, rgba(0, 0, 0, 0.45) 100%),
    var(--color-bg-dark);
  color: var(--color-text-on-dark);
  border-radius: var(--radius-card);
  position: relative;
  overflow: hidden;
  padding: var(--space-12) var(--space-12) var(--space-16);
}
.journey-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-12);
  flex-wrap: wrap;
}
.journey-eyebrow {
  display: block;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-2);
}
.journey-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(32px, 4vw, 44px);
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-1);
}
.journey-subtitle {
  color: var(--color-text-muted-on-dark);
  font-size: 15px;
  margin: 0;
}
.journey-head-right { max-width: 380px; padding-top: var(--space-2); }
.journey-head-right p {
  color: var(--color-text-muted-on-dark);
  font-size: 14px;
  line-height: 1.7;
  margin: 0;
  text-align: right;
}

/* ---- Path + markers ---- */
.journey-path-wrap {
  position: relative;
  height: 200px;
  margin-top: var(--space-16);
}
.journey-path-inner { position: relative; height: 100%; width: 100%; }
.journey-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.journey-path {
  stroke: color-mix(in oklab, var(--color-text-on-dark) 22%, transparent);
  stroke-width: 1.5;
  transition: stroke 240ms ease;
}
.journey-markers { position: absolute; inset: 0; }

/* The dot itself is the point anchored to (x, y) on the path — the
   button's own center is set exactly there via translate(-50%, -50%)
   with the dot as its only in-flow content, and the pill floats
   absolutely above it. Stacking pill+dot as flex siblings (an earlier
   pass did this) centers the *button* on the path instead of the dot
   specifically, leaving the dot visibly floating below the curve
   rather than sitting on it. */
.journey-marker {
  position: absolute;
  transform: translate(-50%, -50%);
  display: block;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.journey-marker-pill {
  position: absolute;
  bottom: calc(100% + var(--space-3));
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  padding: var(--space-1) var(--space-4);
  border-radius: 999px;
  border: 1px solid color-mix(in oklab, var(--color-text-on-dark) 24%, transparent);
  color: var(--color-text-muted-on-dark);
  background: color-mix(in oklab, var(--color-bg-dark) 40%, black 10%);
  transition: color 220ms ease, border-color 220ms ease, background 220ms ease;
}
.journey-marker-dot {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  /* Filled, not hollow: an outline-only dot on a dark panel reads as
     decorative geometry, not a control. A visible (if muted) fill
     plus the hover/focus states below is what actually signals
     "this is a button" before anyone touches it. */
  background: color-mix(in oklab, var(--color-text-on-dark) 55%, transparent);
  border: 1.5px solid color-mix(in oklab, var(--color-text-on-dark) 55%, transparent);
  transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1), border-color 220ms ease, background 220ms ease, box-shadow 220ms ease;
}
/* Comfortable click/tap target beyond the tiny 10px visual dot,
   invisible and not affecting the dot's own centered position. */
.journey-marker::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 36px;
  height: 36px;
  transform: translate(-50%, -50%);
}

/* Hover: modest emphasis, no bounce — a small scale-up and a warmer
   border, matching the "premium and subtle" brief rather than a
   playful micro-interaction. */
.journey-marker:hover .journey-marker-dot,
.journey-marker:focus-visible .journey-marker-dot {
  transform: scale(1.35);
  border-color: var(--color-accent);
}
.journey-marker:hover .journey-marker-pill,
.journey-marker:focus-visible .journey-marker-pill {
  color: var(--color-text-on-dark);
  border-color: color-mix(in oklab, var(--color-text-on-dark) 40%, transparent);
}
.journey-marker:focus-visible { outline: none; }
.journey-marker:focus-visible .journey-marker-pill { box-shadow: 0 0 0 2px var(--color-accent); }

/* Active: strongest emphasis — filled dot with a soft halo ring, solid
   accent-colored pill. This is the only state that gets real color;
   everything else stays within the dark/cream/muted palette. */
.journey-marker.is-active .journey-marker-dot {
  background: var(--color-accent);
  border-color: var(--color-accent);
  transform: scale(1.5);
  box-shadow: 0 0 0 5px color-mix(in oklab, var(--color-accent) 22%, transparent);
}
.journey-marker.is-active .journey-marker-pill {
  color: var(--color-secondary-muted);
  background: var(--color-accent);
  border-color: var(--color-accent);
}

/* ---- Expanded stage detail ----
   Rendered as a continuation of the panel below the path (not a
   floating tooltip/card): same background, a thin top rule instead of
   a boxed border, so it reads as part of the roadmap rather than a
   generic dashboard widget bolted on underneath it. */
.journey-detail {
  margin-top: var(--space-12);
  border-top: 1px solid rgba(255,255,255,0.1);
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 320ms ease, opacity 260ms ease, border-color 320ms ease;
}
.journey-detail.is-open { grid-template-rows: 1fr; opacity: 1; }
.journey-detail:not(.is-open) { border-color: transparent; }
/* padding-top lives on the inner element instead of .journey-detail
   itself: collapsing its parent to grid-template-rows:0fr already
   clips this to zero visible height with the rest of the panel, so
   there's nothing left to animate here — no margin/padding transition
   needed to avoid a layout-thrashing property animation. */
.journey-detail-inner { overflow: hidden; padding-top: var(--space-8); }
/* Content-swap entrance: .journey-detail's own grid/opacity transition
   only covers the panel's first open/close. renderDetail() replaces
   journey-detail-inner's whole subtree on every click (including
   switches between two already-open stages), so that swap needs its
   own animation or the new stage's text just snaps in instantly. A
   fresh element each render means the animation plays automatically
   without a reflow-restart hack. */
@keyframes journey-detail-enter {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}
/* both: holds the from-state through its own animation-delay (set
   inline per element in journey.js) instead of flashing visible for
   that gap, then holds the to-state after finishing. */
.journey-detail-enter { animation: journey-detail-enter 480ms ease both; }
.journey-detail-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-2);
}
.journey-detail-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  letter-spacing: -0.01em;
  display: inline-block;
  margin: 0;
}
.journey-detail-duration {
  display: inline-block;
  margin-left: var(--space-4);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--color-text-muted-on-dark);
  vertical-align: middle;
}
.journey-detail-description {
  max-width: 640px;
  margin: var(--space-4) 0 0;
  color: var(--color-text-muted-on-dark);
  font-size: 15px;
  line-height: 1.7;
}
.journey-detail-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-8);
  max-width: 640px;
  margin-top: var(--space-8);
}
.journey-detail-heading {
  display: block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted-on-dark);
  margin-bottom: var(--space-3);
}
.journey-detail-col ul { display: flex; flex-direction: column; gap: var(--space-2); }
.journey-detail-col li {
  position: relative;
  padding-left: var(--space-4);
  font-size: 14px;
  line-height: 1.5;
}
.journey-detail-col li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 6px;
  height: 1px;
  background: var(--color-accent);
}

@media (prefers-reduced-motion: reduce) {
  .journey-marker-dot, .journey-marker-pill, .journey-path, .journey-detail { transition: none !important; }
  .journey-detail-enter { animation: none !important; }
}

@media (max-width: 900px) {
  .journey { padding: var(--space-8) var(--space-6) var(--space-12); }
  .journey-head { flex-direction: column; gap: var(--space-4); }
  .journey-head-right { max-width: none; }
  .journey-head-right p { text-align: left; }
  .journey-detail-columns { grid-template-columns: 1fr; gap: var(--space-6); }
}

/* Mobile: the organic horizontal path becomes hard to read and the
   pills start colliding at narrow widths, so it switches to a
   horizontally scrollable rail instead of trying to compress the same
   layout — the journey stays legible and every stage stays reachable
   by swipe, matching the brief's explicit preference for a scrollable
   journey over silently shrinking the desktop design. */
@media (max-width: 640px) {
  .journey-path-wrap {
    height: 140px;
    overflow-x: auto;
    overflow-y: visible;
    -webkit-overflow-scrolling: touch;
  }
  /* .journey-path-inner is given a real pixel width by journey.js at
     this breakpoint (see MIN_PX_PER_STAGE), which is what actually
     makes the wrap above scrollable — this rule just stops it
     shrinking back to the flex/grid default. */
  .journey-path-inner { flex-shrink: 0; }
  .journey-marker-pill { font-size: 11px; padding: var(--space-1) var(--space-3); }
}

/* ---- Timeline (horizontal scroll rail) ---- */
.timeline { display: flex; gap: var(--space-8); overflow-x: auto; padding: var(--space-8) 0; scroll-snap-type: x proximity; }
.timeline .stop { flex: 0 0 220px; scroll-snap-align: start; }
.timeline .stop .year { font-family: var(--font-ui); color: var(--color-accent); font-size: 14px; margin-bottom: var(--space-2); }
.timeline .stop h4 { font-size: 16px; font-weight: 700; margin-bottom: var(--space-2); }
.timeline .stop p { font-size: 14px; color: var(--color-text-muted-on-dark); }

/* ---- Stat card ---- */
.stat-card { text-align: center; padding: var(--space-8) var(--space-4); }
.stat-card .stat { font-family: var(--font-display); font-size: 40px; font-weight: 800; color: var(--color-accent); }
.stat-card .label { font-size: 14px; color: var(--color-text-muted-on-dark); margin-top: var(--space-2); }
.section-steel .stat-card .label { color: var(--color-text-muted-on-light); }

/* ---- Testimonial carousel ---- */
.testimonial-carousel { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-6); margin-top: var(--space-12); }
.testimonial-card { background: var(--color-card-dark); border-radius: var(--radius-card); padding: var(--space-8); }
.testimonial-card blockquote { margin: 0 0 var(--space-6); font-size: 16px; color: var(--color-text-on-dark); }
.testimonial-card .person { display: flex; align-items: center; gap: var(--space-3); }
.testimonial-card .avatar { width: 44px; height: 44px; border-radius: 50%; background: var(--color-bg-steel); flex-shrink: 0; }
.testimonial-card .person .name { font-weight: 700; font-size: 14px; }
.testimonial-card .person .role { font-size: 13px; color: var(--color-text-muted-on-dark); }
.testimonial-card a.learn-more { margin-top: var(--space-4); }

/* ---- CTA band ---- */
.cta-band { background: var(--color-accent); color: var(--color-secondary-muted); padding: var(--space-16) 0; }
.cta-band .container { display: flex; align-items: center; justify-content: space-between; gap: var(--space-8); flex-wrap: wrap; }
.cta-band h2 { color: var(--color-secondary-muted); font-size: clamp(24px, 3vw, 34px); }
.cta-band .btn-primary { background: var(--color-secondary-muted); color: var(--color-accent); }

/* ---- Footer ---- */
/* Shares the exact gradient recipe .section-dark/.section-navy use
   (not just the flat --color-bg-dark) — the footer is a real dark
   surface on every page just like the hero/other dark sections, and
   was the most visible remaining gap in an otherwise-consistent site:
   confirmed by a full-page screenshot showing every other dark band
   with visible warmth/texture and the footer alone reading as flat,
   textureless black. .site-footer doesn't carry the .section-dark
   class itself (it has its own layout rules), so this repeats the
   recipe directly rather than adding that class, which would also
   pull in .section padding this component doesn't want. */
.site-footer {
  background:
    radial-gradient(ellipse 900px 700px at 50% 40%, rgba(242, 165, 26, 0.07) 0%, transparent 60%),
    radial-gradient(ellipse 1100px 800px at 50% 75%, rgba(150, 70, 170, 0.05) 0%, transparent 65%),
    radial-gradient(ellipse 1400px 1000px at 50% 45%, transparent 0%, rgba(0, 0, 0, 0.45) 100%),
    var(--color-bg-dark);
  padding: var(--space-24) 0 var(--space-8);
  border-top: 1px solid rgba(255,255,255,0.06);
  position: relative;
  overflow: hidden;
}
.footer-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-8); margin-bottom: var(--space-16); }
.footer-grid h4 { font-family: var(--font-body); font-size: 15px; font-weight: 700; color: var(--color-text-on-dark); margin-bottom: var(--space-4); }
.footer-grid ul li { margin-bottom: var(--space-2); }
.footer-grid a { font-family: var(--font-ui); font-size: 13px; text-transform: uppercase; letter-spacing: -0.02em; color: var(--color-text-muted-on-dark); }
.footer-grid a:hover { color: var(--color-accent); }
.footer-bottom { display: flex; align-items: center; justify-content: space-between; padding-top: var(--space-8); border-top: 1px solid rgba(255,255,255,0.08); font-size: 13px; color: var(--color-text-muted-on-dark); }
@media (max-width: 800px) {
  .footer-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ---- Forms ---- */
.contact-form { max-width: 560px; }
.form-field { margin-bottom: var(--space-4); }
.form-field label { display: block; font-size: 13px; margin-bottom: var(--space-2); color: var(--color-text-muted-on-dark); }
.section-steel .form-field label { color: var(--color-text-muted-on-light); }
.form-field input, .form-field textarea {
  width: 100%;
  padding: 12px var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: rgba(255,255,255,0.04);
  color: inherit;
  font-family: inherit;
  /* 16px, not 14px: iOS Safari auto-zooms the viewport on focus for any
     text input under 16px, which yanks the page around the instant a
     visitor taps into the form. 16px is the documented threshold that
     avoids it. */
  font-size: 16px;
}

/* ---- Custom select (service dropdown) ----
   A native <select>'s open panel is drawn by the OS, not the page, so
   its font/spacing/hover color can't be made to match the site no
   matter what CSS is applied to the closed control. This component
   keeps a real <select> in the DOM (visually hidden, still focusable
   as a fallback and the thing that actually submits with the form) and
   layers a fully-styled button + listbox on top of it, wired up in
   main.js. Built from the same tokens as every other control on this
   page (--radius-sm, --color-border, --font-ui) so the open panel
   reads as part of this site, not a generic browser widget. */
.custom-select { position: relative; }
.custom-select-native {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
}
.custom-select-trigger {
  width: 100%;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 12px var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: rgba(255,255,255,0.04);
  color: inherit;
  font-family: inherit;
  font-size: 14px;
  text-align: left;
  transition: border-color 180ms ease;
}
.custom-select-trigger:hover { border-color: var(--color-accent); }
.custom-select.is-open .custom-select-trigger { border-color: var(--color-accent); }
.custom-select-value { flex: 1; }
.custom-select-value.is-placeholder { color: color-mix(in oklab, currentColor 55%, transparent); }
.custom-select-chevron { flex-shrink: 0; color: var(--color-accent); transition: transform 200ms ease; }
.custom-select.is-open .custom-select-chevron { transform: rotate(180deg); }

/* The scroll container is a separate element from the one carrying
   border-radius: putting overflow-y directly on a rounded box makes
   the browser's native scrollbar track run straight to the corner,
   clipping across the curve instead of following it. Splitting the
   two lets the outer element own the rounded shape/border/shadow while
   the inner element owns scrolling, so the scrollbar sits inset from
   the curve instead of cutting into it. */
.custom-select-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--color-bg-pale);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 20px 48px rgba(0,0,0,0.18);
  overflow: hidden;
}
.custom-select-panel-scroll {
  max-height: 320px;
  overflow-y: auto;
  padding: var(--space-2);
  /* Thin, on-brand scrollbar instead of the OS default (grey track +
     up/down arrow buttons), matching the site's warm palette. Firefox
     via scrollbar-width/scrollbar-color, WebKit/Blink via the
     ::-webkit-scrollbar pseudo-elements below — no arrow buttons in
     either since neither API can add or remove them independently of
     the browser's own chrome. */
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}
.custom-select-panel-scroll::-webkit-scrollbar { width: 8px; }
.custom-select-panel-scroll::-webkit-scrollbar-track { background: transparent; }
.custom-select-panel-scroll::-webkit-scrollbar-thumb {
  background-color: var(--color-border);
  border-radius: 999px;
  border: 2px solid var(--color-bg-pale);
}
.custom-select-panel-scroll::-webkit-scrollbar-thumb:hover { background-color: var(--color-accent); }
.custom-select-group-label {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--color-text-on-light);
  padding: var(--space-3) var(--space-3) var(--space-1);
}
.custom-select-group-label:first-child { padding-top: var(--space-2); }
.custom-select-option {
  padding: 11px var(--space-3);
  border-radius: var(--radius-btn);
  font-size: 14px;
  color: var(--color-text-on-light);
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
}
.custom-select-option:hover,
.custom-select-option.is-active,
.custom-select-option:focus-visible {
  background: rgba(242, 165, 26, 0.14);
  color: var(--color-secondary-muted);
}
.custom-select-option:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.custom-select-option.is-selected { font-weight: 600; color: var(--color-secondary-muted); }
@media (max-width: 640px) {
  .custom-select-option { min-height: 46px; }
}

.form-note { font-size: 13px; margin-top: var(--space-3); color: var(--color-text-muted-on-dark); }
.form-note.success { color: #7be0a0; }
.form-note.error { color: #f28b82; }

/* ---- Office card ---- */
.office-card h4 { margin-bottom: var(--space-2); }
.office-card .addr { font-size: 14px; color: var(--color-text-muted-on-dark); margin-bottom: var(--space-2); }
.office-card a.email { font-size: 14px; color: var(--color-accent); }

/* ---- Breadcrumb pills ---- */
.breadcrumb-pills { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-6); }
.breadcrumb-pills span, .breadcrumb-pills a {
  font-size: 12px;
  padding: 6px 14px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted-on-dark);
}

/* ---- Share row ---- */
.share-row { display: flex; gap: var(--space-3); align-items: center; margin: var(--space-6) 0; }
.share-row a { font-size: 13px; color: var(--color-text-muted-on-dark); border: 1px solid var(--color-border); padding: 6px 12px; border-radius: var(--radius-btn); }
.share-row a:hover { color: var(--color-accent); border-color: var(--color-accent); }

/* ---- Article ---- */
.article-body { max-width: 720px; margin: 0 auto; }
.article-body h2 { font-size: 24px; margin: var(--space-8) 0 var(--space-3); }
.article-body p { margin-bottom: var(--space-4); color: var(--color-text-muted-on-dark); }
.article-body ul { margin: 0 0 var(--space-4) var(--space-6); list-style: disc; }
.article-body ul li { margin-bottom: var(--space-2); color: var(--color-text-muted-on-dark); }
.section-steel .article-body p, .section-pale .article-body p,
.section-steel .article-body ul li, .section-pale .article-body ul li {
  color: var(--color-text-muted-on-light);
}
.article-meta { display: flex; gap: var(--space-4); font-size: 13px; color: var(--color-text-muted-on-dark); margin-bottom: var(--space-6); }
.tag-pill { font-size: 12px; padding: 4px 12px; border-radius: var(--radius-btn); background: rgba(102,232,250,0.1); color: var(--color-accent); }
.newsletter-block { background: var(--color-card-dark); border-radius: var(--radius-card); padding: var(--space-8); margin-top: var(--space-16); text-align: center; }

/* ---- Empty state ---- */
.empty-state { text-align: center; padding: var(--space-12) 0; color: var(--color-text-muted-on-dark); }
.section-steel .empty-state, .section-pale .empty-state { color: var(--color-text-muted-on-light); }

/* ---- Scroll reveal ----
   Content is visible by default and stays visible no matter what JS does.
   .reveal-play (added by JS at the moment an element intersects the
   viewport) triggers a one-shot fade/slide-in animation that starts from
   its own snapshot of "already there" and ends at the normal resting
   state — it never sets a resting opacity of 0, so a slow/absent/failed
   observer simply means "no entrance animation", never "invisible content". */
@keyframes reveal-in {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}
.reveal-play { animation: reveal-in 500ms ease both; }

/* ---- Scroll engine (scroll-engine.js) ----
   Same non-destructive principle as .reveal-play above: content is visible
   by default; .se-reveal-pending (added by JS right before observing) is
   the ONLY place opacity ever drops, and .se-revealed (added on
   intersection, removed on exit so it replays on re-entry) restores it.
   A slow/absent observer never leaves an element stuck invisible, since
   the pending class itself carries the transition and the browser's
   initial paint has already happened without JS in the loop. Losing
   se-revealed while off-screen snaps back to opacity 0 with no visible
   flash, since the element isn't in the viewport when that happens. */
[data-se-reveal].se-reveal-pending,
[data-se-reveal-group] > .se-reveal-pending {
  opacity: 0;
  transform: translateY(36px) scale(0.98);
  transition: opacity 700ms cubic-bezier(0.16, 1, 0.3, 1), transform 700ms cubic-bezier(0.16, 1, 0.3, 1);
}
[data-se-reveal].se-revealed,
[data-se-reveal-group] > .se-revealed {
  opacity: 1;
  transform: none;
}

/* Industries grid: a more pronounced entrance than the site-wide default,
   since this grid has 8 cards and benefits from a clearer cascade. */
.industries-grid > .se-reveal-pending {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity 800ms ease, transform 800ms ease;
}
.industries-grid > .se-revealed {
  opacity: 1;
  transform: none;
}

/* Kinetic text: each split unit is visible by default; only once the parent
   also carries .se-reveal-pending do the units hide and stagger in via their
   own per-unit transition-delay (set inline by scroll-engine.js). */
.se-split-unit { display: inline-block; }
[data-se-reveal].se-reveal-pending .se-split-unit {
  opacity: 0;
  transform: translateY(0.4em);
  transition: opacity 500ms ease, transform 500ms ease;
}
[data-se-reveal].se-revealed .se-split-unit {
  opacity: 1;
  transform: none;
}

/* Phrase-level split (data-se-split="phrases"): each clause rises in on its
   own, with a slower, more deliberate transition than the word/char split
   above, matching the longer per-phrase delay scroll-engine.js sets inline. */
[data-se-reveal].se-reveal-pending .se-split-phrase {
  transform: translateY(0.5em);
  transition: opacity 650ms ease, transform 650ms ease;
}

/* Print / PDF / full-page capture: the reveal-on-scroll system waits for
   a real IntersectionObserver callback, which never fires for content
   below the fold when the whole page is rendered at once instead of
   being scrolled through (browser print preview, "save as PDF", and
   any headless full-page screenshot tool). Left alone, everything still
   in .se-reveal-pending at that moment prints as a blank gap. This
   override only applies to the print media type, so the on-screen
   replay-on-scroll behavior for real visitors is untouched. */
@media print {
  [data-se-reveal].se-reveal-pending,
  [data-se-reveal-group] > .se-reveal-pending,
  .industries-grid > .se-reveal-pending,
  [data-se-reveal].se-reveal-pending .se-split-unit,
  [data-se-reveal].se-reveal-pending .se-split-phrase {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

[data-se-parallax] { will-change: transform; }
[data-se-tilt], [data-se-magnet] { will-change: transform; }

/* ---- Custom cursor ----
   Two independent layers, both instantiated in main.js:
   1. A small sparkle-shaped dot that tracks the mouse with zero lag
      (position set directly on every mousemove, no easing) and morphs
      shape/color depending on what it's hovering.
   2. A separate scramble-text tooltip that trails the pointer with an
      eased offset, showing a short label from an element's data-cursor
      attribute, decoding the text with a brief character-scramble.
   Both are pointer-events:none so they never intercept clicks, and both
   are skipped entirely on touch/coarse-pointer devices (see the
   `has-custom-cursor` gate in the reset section above). */
.cursor-dot-wrap {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  pointer-events: none;
  width: 0;
  height: 0;
}
.cursor-dot {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 22px;
  height: 22px;
  opacity: 0;
  transition: opacity 200ms ease;
}
body.has-custom-cursor .cursor-dot { opacity: 1; }

/* The shape morph (sparkle <-> ring, size, color) lives on this inner
   layer and animates with transform/opacity only, so it never touches
   the position-tracking dot's own box (JS writes that transform on
   every mousemove and would otherwise fight a width/height transition). */
.cursor-dot-shape {
  width: 100%;
  height: 100%;
  background: var(--color-accent);
  clip-path: polygon(50% 0%, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0% 50%, 39% 39%);
  transform: scale(1);
  transition: clip-path 220ms ease, transform 200ms cubic-bezier(0.4, 0, 0.2, 1), background-color 220ms ease;
}

/* Hover over any link/button/control: sparkle relaxes into a plain
   solid dot (a bigger, plain-circle version of the same filled shape,
   not an outline). Previously used mix-blend-mode:difference (white
   inverted against the page) for this, which produces an off-palette
   blue/cyan cast against the gold accent (255,255,255 minus 242,165,26
   lands in blue territory) — a hollow ring was tried next but read as
   the wrong shape entirely, so this keeps the dot solid/filled and
   just recolors it per-section via the existing is-on-dark/is-on-light
   classes below, with no blend math involved. */
.cursor-dot-shape.is-interactive {
  transform: scale(1.545); /* 34px / 22px, matches the earlier fixed-size step */
  clip-path: circle(50% at 50% 50%);
}

/* Section-aware recoloring: main.js watches which section-* background
   the pointer is currently over and toggles these classes on the dot
   directly, so it stays visible against dark, steel, navy and pale
   sections alike. Applies to both the resting sparkle and the
   hovering solid dot, since neither is hollow. */
.cursor-dot-shape.is-on-dark { background: var(--color-accent); }
.cursor-dot-shape.is-on-light { background: var(--color-bg-dark); }

/* Scramble-text tooltip: a small pill that trails the pointer with an
   eased lag (see main.js quickTo-style lerp) and only appears over
   elements carrying data-cursor="label text". */
.cursor-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9998;
  pointer-events: none;
  transform: translate(-50%, -50%);
  background: var(--color-text-on-light);
  color: var(--color-text-on-dark);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: var(--space-2) var(--space-4);
  border-radius: 999px;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 180ms ease;
}
.cursor-tooltip.is-visible { opacity: 1; }

@media (hover: none), (pointer: coarse) {
  .cursor-dot-wrap, .cursor-tooltip { display: none; }
}

/* ---- Ambient scroll glow (ambient.js) ----
   A soft radial light source, fixed to the viewport, whose vertical
   position and color both track scroll progress: warmer/brighter on
   dark sections (the sun against night), softer and paler on light
   ones (the same light, now diffuse against daylight). Sits behind
   all real content, pointer-events:none throughout. Not loaded at all
   under prefers-reduced-motion (see ambient.js's early return) — a
   light source that doesn't move carries nothing worth keeping. */
.ambient-glow {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
}
/* The glow is a real, sized, blurred element (not a background
   radial-gradient) specifically so it can be moved and breathed via
   transform — a gradient's own position/size can't be animated
   cheaply since it isn't a box, it's a paint, so scaling it smoothly
   would mean recomputing pixel sizes in JS every frame instead of
   letting the compositor handle a transform. */
.ambient-glow::before {
  content: '';
  position: absolute;
  top: var(--ambient-y, 40%);
  left: var(--ambient-x, 82%);
  width: 900px;
  height: 750px;
  transform: translate(-50%, -50%) scale(var(--ambient-scale, 1));
  border-radius: 50%;
  background: radial-gradient(circle, var(--ambient-color, transparent), transparent 70%);
  filter: blur(10px);
  transition: background-color 500ms ease, top 500ms ease;
}

/* Section boundary bleed: each section that can sit next to a
   different-toned one gets an ::after tinted with the *next* section's
   background, faded in via --section-bleed as the visitor approaches
   that edge (set by ambient.js) so the transition between dark and
   light bands feels anticipated rather than cut. Deliberately one
   pseudo-element, one direction (bottom edge only): a matching
   top-edge ::before was tried so the effect would also anticipate
   scrolling up, but at the seam it stacked two independent,
   opposite-color translucent washes on top of each other — composited
   into a muddy gray band instead of a clean gradient. One gradient
   owning the seam reads clean in both scroll directions already,
   since it's driven by scroll position, not scroll direction. The
   gradient is confined to the bottom third (not a flat full-box fill)
   so the tint only ever touches the edge nearest the boundary and
   never washes out a heading or paragraph sitting higher in the
   section, even once --section-bleed reaches 1. */
.section-dark, .section-navy, .section-pale, .section-steel {
  position: relative;
}
.section-dark::after, .section-navy::after, .section-pale::after, .section-steel::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent 60%, var(--section-bleed-color, transparent) 100%);
  opacity: var(--section-bleed, 0);
}
.section-dark > *, .section-navy > *, .section-pale > *, .section-steel > * {
  position: relative;
  z-index: 1;
}
/* .wwd-decor is a direct child of .wwd-section (which carries
   .section-pale) and needs position: absolute to stay a decorative
   background layer out of document flow. The blanket rule above
   matched it too (equal specificity, declared later in this file) and
   silently downgraded it to position: relative, so it stayed in flow
   and reserved its full 380px height as real space, pushing "What we
   do" down by that much below the hero. Restoring absolute here, kept
   above z-index:0 via the element's own rule so it still sits behind
   real content. */
.wwd-decor { position: absolute; }
.wwd-aurora-section { overflow: hidden; }
/* Generic version of the same overflow-clip requirement, for any
   .section-dark/.section-navy on another page that gets an
   .aurora-layer child without also being the homepage's specific
   "why we do" section. */
.aurora-section { overflow: hidden; }

/* ---- Aurora background (gradient mesh) ----
   A slow, soft blend of the section's brown against warm white/gold,
   for the two dark sections on the homepage the brief called out (the
   hero+trusted-by band, and "Why Outshine"). One element carrying four
   radial-gradient layers; each gradient's own position and its own
   color-mix ratio animate on independent phases within one shared
   keyframe timeline, so the whole mesh reads as one liquid surface
   re-blending rather than distinct shapes traveling across a static
   backdrop. No blur filter needed — radial-gradient's own transparent
   falloff is already soft-edged.
   An earlier blob-based version (blurred divs animated via `transform`)
   was replaced: its motion was real but too subtle at a glance (heavy
   blur plus a 22-32s cycle). This version's motion is deliberately
   more visible, on a single 13s cycle.
   @property is required, not decorative: without registering these as
   typed <percentage> values, the browser does not tween them between
   keyframes at all, it snaps discretely from one keyframe's value
   straight to the next — confirmed by sampling the live computed
   background mid-animation. @property is what makes the position and
   blend both progress smoothly frame by frame.
   Layered UNDERNEATH the existing per-section machinery, not replacing
   it: .ambient-glow (position:fixed, z-index:1, a separate light
   source that tracks scroll) and each section's own ::before/::after
   boundary-bleed tint (z-index:0) both keep painting on top of this.
   z-index:0, not a negative value: a negative z-index on a child whose
   parent has position:relative but no z-index of its own can paint
   BEHIND that parent's own solid background color, not just behind
   its other children — confirmed live on .section-navy (a flat
   background: var(--color-bg-dark), no gradient to see through),
   where a z-index:-1 aurora layer was fully invisible even though its
   layout/size were correct. z-index:0 avoids that ambiguity, and
   still sits correctly behind the z-index:0 bleed pseudo-elements by
   paint order (this layer is the section's first child, painted
   before them) and behind the z-index:1 real content.
   The section's own overflow:hidden keeps this contained to its own
   box. Static under prefers-reduced-motion, like every other moving
   piece on this site: frozen mid-blend rather than removed, so the
   visual mix stays but nothing drifts. */
@property --aurora-p1x { syntax: '<percentage>'; inherits: false; initial-value: 20%; }
@property --aurora-p1y { syntax: '<percentage>'; inherits: false; initial-value: 30%; }
@property --aurora-p2x { syntax: '<percentage>'; inherits: false; initial-value: 80%; }
@property --aurora-p2y { syntax: '<percentage>'; inherits: false; initial-value: 70%; }
@property --aurora-p3x { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
@property --aurora-p3y { syntax: '<percentage>'; inherits: false; initial-value: 10%; }
@property --aurora-p4x { syntax: '<percentage>'; inherits: false; initial-value: 30%; }
@property --aurora-p4y { syntax: '<percentage>'; inherits: false; initial-value: 85%; }
@property --aurora-m1 { syntax: '<percentage>'; inherits: false; initial-value: 38%; }
@property --aurora-m2 { syntax: '<percentage>'; inherits: false; initial-value: 40%; }
@property --aurora-m3 { syntax: '<percentage>'; inherits: false; initial-value: 30%; }
@property --aurora-m4 { syntax: '<percentage>'; inherits: false; initial-value: 35%; }

.aurora-layer {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  /* Rebuilt for the client-supplied monochrome-first palette: this
     used to blend pure white and full-strength gold, which read as a
     loud warm brown/gold wash once --color-bg-pale became true white
     — and on about.html's three stacked aurora sections, busy enough
     to actively hurt text legibility over it. The two former
     --color-bg-pale blobs now mix in the palette's own mid-grey
     (--color-text-muted-on-dark, #606060) instead of white — softer,
     lower-contrast, reads as a subtle depth cue rather than a bright
     highlight, and stays at 90% of keyframe strength — grey has no
     color cast to compound with anything else, so it's the safe part
     of this recipe to keep visible.
     The two gold blobs went through three passes before landing here:
     15% (too flat/plain compared to the hero — confirmed by sampling
     pixel colors at the seam between hero and the next dark section),
     then 35% (fixed the flatness but, combined with the base
     .section-dark gradient's own gold glow, added up to "heavily
     brown" rather than "dark with a hint of gold" once reviewed on
     real full-page screenshots — the client's own words). Settled at
     10%: enough to register as a deliberate warm accent alongside the
     grey depth cue, not enough to tint the section's overall color.
     The hero's own `.hero-and-trusted > .aurora-layer` override below
     separately dials the whole layer down further still, since the
     hero is the one section meant to read as visibly richer than
     every other dark section, not the baseline they're all measured
     against. */
  background-image:
    radial-gradient(circle at var(--aurora-p1x) var(--aurora-p1y), color-mix(in oklab, var(--color-text-muted-on-dark) calc(var(--aurora-m1) * 0.9), transparent) 0%, transparent 55%),
    radial-gradient(circle at var(--aurora-p2x) var(--aurora-p2y), color-mix(in oklab, var(--color-accent) calc(var(--aurora-m2) * 0.1), transparent) 0%, transparent 55%),
    radial-gradient(circle at var(--aurora-p3x) var(--aurora-p3y), color-mix(in oklab, var(--color-text-muted-on-dark) calc(var(--aurora-m3) * 0.9), transparent) 0%, transparent 50%),
    radial-gradient(circle at var(--aurora-p4x) var(--aurora-p4y), color-mix(in oklab, var(--color-accent) calc(var(--aurora-m4) * 0.1), transparent) 0%, transparent 55%);
  animation: aurora-mesh-shift 13s ease-in-out infinite;
  pointer-events: none;
}
/* Restated next to the class it belongs to (not relying on source
   order alone against the .section-* > * rule elsewhere in this
   file): .aurora-layer is a direct child of .section-dark/.section-navy
   in both usages, and that rule sets position:relative/z-index:1 on
   every direct child — the same bug already found once on .wwd-decor.
   The extra ancestor+class selector here outranks that universal
   selector regardless of which one is declared later. */
.section-dark > .aurora-layer,
.section-navy > .aurora-layer {
  position: absolute;
  z-index: 0;
}
@keyframes aurora-mesh-shift {
  0%   { --aurora-p1x: 20%; --aurora-p1y: 30%; --aurora-p2x: 80%; --aurora-p2y: 70%; --aurora-p3x: 50%; --aurora-p3y: 10%; --aurora-p4x: 30%; --aurora-p4y: 85%; --aurora-m1: 38%; --aurora-m2: 40%; --aurora-m3: 30%; --aurora-m4: 35%; }
  25%  { --aurora-p1x: 45%; --aurora-p1y: 15%; --aurora-p2x: 60%; --aurora-p2y: 85%; --aurora-p3x: 75%; --aurora-p3y: 30%; --aurora-p4x: 15%; --aurora-p4y: 60%; --aurora-m1: 28%; --aurora-m2: 50%; --aurora-m3: 24%; --aurora-m4: 45%; }
  50%  { --aurora-p1x: 70%; --aurora-p1y: 40%; --aurora-p2x: 30%; --aurora-p2y: 60%; --aurora-p3x: 40%; --aurora-p3y: 75%; --aurora-p4x: 60%; --aurora-p4y: 20%; --aurora-m1: 33%; --aurora-m2: 35%; --aurora-m3: 32%; --aurora-m4: 38%; }
  75%  { --aurora-p1x: 35%; --aurora-p1y: 65%; --aurora-p2x: 65%; --aurora-p2y: 25%; --aurora-p3x: 20%; --aurora-p3y: 50%; --aurora-p4x: 80%; --aurora-p4y: 70%; --aurora-m1: 25%; --aurora-m2: 48%; --aurora-m3: 27%; --aurora-m4: 42%; }
  100% { --aurora-p1x: 20%; --aurora-p1y: 30%; --aurora-p2x: 80%; --aurora-p2y: 70%; --aurora-p3x: 50%; --aurora-p3y: 10%; --aurora-p4x: 30%; --aurora-p4y: 85%; --aurora-m1: 38%; --aurora-m2: 40%; --aurora-m3: 30%; --aurora-m4: 35%; }
}
@media (prefers-reduced-motion: reduce) {
  .aurora-layer { animation: none !important; }
}

/* ---- Utility ---- */
.text-center { text-align: center; }
.mt-8 { margin-top: var(--space-8); }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-8); }
@media (max-width: 800px) { .grid-2 { grid-template-columns: 1fr; } }

/* ---- Responsive nav ----
   Below 900px the inline nav list becomes a dropdown panel and the
   hamburger (.nav-toggle) takes over; main.js toggles a `.open` class
   on .main-nav when it's tapped.
   The panel stays in the DOM/layout at all times (never display:none)
   specifically so open/close can animate: a `display` toggle happens
   in a single frame with nothing to transition, which is why this had
   no animation before. Closed state instead uses opacity/visibility/a
   small upward translate plus pointer-events:none (so the invisible,
   collapsed panel can't be tabbed into or intercept taps), and .open
   swaps those to the visible/settled values, both changes covered by
   one transition. Matches the same fade+rise language already used
   for scroll-reveal elsewhere on the site, just applied to a nav panel
   instead of scroll-triggered content. */
@media (max-width: 900px) {
  .nav-cta .btn-secondary { display: none; }
  .nav-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
  }
  .main-nav ul {
    display: flex;
    flex-direction: column;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    gap: 0;
    margin: 0;
    padding: var(--space-4) var(--space-6);
    background: var(--color-bg-dark);
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    box-shadow: 0 20px 32px rgba(0, 0, 0, 0.35);
    opacity: 0;
    transform: translateY(-8px);
    visibility: hidden;
    pointer-events: none;
    transition: opacity 220ms ease, transform 220ms ease, visibility 0ms linear 220ms;
  }
  .main-nav.open ul {
    opacity: 1;
    transform: translateY(0);
    visibility: visible;
    pointer-events: auto;
    transition: opacity 220ms ease, transform 220ms ease, visibility 0ms linear;
  }
  .site-header { position: relative; }
  .main-nav ul li { width: 100%; }
  .main-nav ul a.nav-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: 15px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  .main-nav ul li:last-child a.nav-link { border-bottom: none; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .main-nav ul, .main-nav.open ul { transition: none; }
}
