/* =========================================================
   Adapsus — Landing Page
   Direction: near-black navy ground, deep teal + brushed
   brass/gold ribbon language, warm off-white type.
   Sora (display) / Manrope (body).
   ========================================================= */

:root {
  --navy-950: #05080b;
  --navy-900: #090e14;
  --navy-850: #0d141b;
  --navy-800: #121a23;
  --navy-line: rgba(242, 236, 224, 0.08);

  --teal-700: #144840;
  --teal-600: #1c6f62;
  --teal-500: #29897a;
  --teal-300: #86cabb;

  --gold-700: #7a5a26;
  --gold-600: #b8863c;
  --gold-500: #cf9f52;
  --gold-300: #ecc985;

  --ink: #f4eee2;
  --ink-dim: rgba(244, 238, 226, 0.72);
  --ink-faint: rgba(244, 238, 226, 0.62);
  --ink-line: rgba(244, 238, 226, 0.14);

  --font-display: "Sora", "Segoe UI", system-ui, sans-serif;
  --font-body: "Manrope", "Segoe UI", system-ui, sans-serif;

  /* Type ramp — every font-size in this file resolves to one of these steps. */
  --fs-label: 0.85rem;
  --fs-control: 0.98rem;
  --fs-body: 1.0625rem;
  --fs-lead: 1.15rem;
  --fs-title: 1.3rem;
  --fs-display-sm: clamp(1.9rem, 3.4vw, 2.7rem);
  --fs-display-lg: clamp(2.6rem, 5vw, 4.4rem);

  --measure: 68ch;
  --wrap: 1180px;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2.5rem;
  --space-5: 4rem;
  --space-6: 6.5rem;
  --space-7: 9rem;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}
body {
  margin: 0;
  background: var(--navy-950);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
/* ---------- Page-wide background ----------
   One continuous gradient for the whole scroll height, replacing the
   per-section flat fills that used to make each section read as its own
   opaque block (see "Section shells" below). Implemented as a fixed
   full-viewport layer rather than a background on body/html so it never
   depends on document height for its own sizing (no banding/repeating at
   long scroll heights) and repaints nothing when it moves — js/main.js's
   initPageGradient() sizes this element's own box to the real document
   height once (so the gradient's percentage stops below map to fixed
   points down the actual page) and then only ever moves it with
   `transform: translateY()`, a compositor-only property, in lockstep
   with scroll. z-index sits behind both the ribbon canvas (0) and every
   section (1), so it's the true backdrop for the whole page. Teal near
   the top easing into brass further down, continuously present the
   whole way rather than two brief tinted bands with a flat near-black
   plateau between them (an earlier pass did that and read as "still
   just black" for most of the page's length). */
#page-gradient {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  z-index: -1;
  pointer-events: none;
  will-change: transform;
  background: linear-gradient(
    180deg,
    var(--navy-950) 0%,
    rgba(28, 111, 98, 0.24) 10%,
    rgba(20, 72, 64, 0.16) 28%,
    rgba(20, 72, 64, 0.1) 45%,
    rgba(122, 90, 38, 0.12) 62%,
    rgba(184, 134, 60, 0.24) 85%,
    var(--navy-950) 100%
  );
}

img { max-width: 100%; display: block; }
h1, h2, h3 { font-family: var(--font-display); color: var(--ink); margin: 0; letter-spacing: -0.02em; font-weight: 600; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
ol, ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; }

.wrap {
  width: min(100% - 3rem, var(--wrap));
  margin-inline: auto;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--gold-500);
  color: var(--navy-950);
  padding: 0.75em 1.25em;
  z-index: 100;
  font-weight: 600;
}
.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

:focus-visible {
  outline: 2px solid var(--gold-300);
  outline-offset: 3px;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 0.95em 1.9em;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-control);
  letter-spacing: -0.01em;
  border-radius: 3px;
  transition: transform 0.35s var(--ease-out), box-shadow 0.35s var(--ease-out), background 0.35s var(--ease-out), border-color 0.35s var(--ease-out);
  white-space: nowrap;
}
.btn-primary {
  background: linear-gradient(155deg, var(--gold-300) 0%, var(--gold-500) 45%, var(--gold-600) 100%);
  color: #201400;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.35) inset, 0 14px 30px -14px rgba(207, 159, 82, 0.55);
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 20px 36px -14px rgba(207, 159, 82, 0.7);
}
.btn-primary:active { transform: translateY(0); }
.btn-compact { padding: 0.65em 1.4em; font-size: var(--fs-label); }
.btn-large { padding: 1.15em 2.6em; font-size: var(--fs-body); }
.btn-ghost {
  border: 1px solid var(--ink-line);
  color: var(--ink-dim);
}
.btn-ghost:hover {
  border-color: var(--teal-300);
  color: var(--ink);
  background: rgba(134, 202, 187, 0.06);
}

/* ---------- Header ---------- */
/* .site-header is the sticky positioning shell only — transparent, full
   width, pinned at true top:0 so js/ribbon-scene.js's computeRatios() can
   keep reading its offsetHeight as "total space this bar occupies" for
   the flight-hero's --header-h compensation (see that file's comment).
   The actual floating pill — width, rounding, tint, blur, border — all
   live on .header-row instead, which is what visually reads as the
   navbar. padding-top here is the "margin from the viewport edge" the
   pill floats at, folded into .site-header's own box specifically so
   offsetHeight includes it automatically, with nothing to keep in sync
   by hand. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  padding-top: var(--space-3);
}
.header-row {
  width: min(100% - 3rem, var(--wrap));
  margin-inline: auto;
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  column-gap: var(--space-3);
  padding: 0.85rem var(--space-3);
  border-radius: 20px;
  border: 1px solid color-mix(in srgb, var(--teal-300) 18%, transparent);
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--teal-700) 55%, transparent) 0%,
    color-mix(in srgb, var(--navy-900) 85%, transparent) 55%,
    color-mix(in srgb, var(--gold-700) 28%, transparent) 100%
  );
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  box-shadow: 0 20px 40px -22px rgba(0, 0, 0, 0.55);
}
.brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}
.brand-mark {
  display: block;
  width: 42px;
  height: 40px;
  background-image: url("../Adapsus Logo.png");
  background-repeat: no-repeat;
  background-size: 101px 101px;
  background-position: -30px -21px;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.4));
}
.brand-word {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.3rem;
  letter-spacing: 0.02em;
}
.site-nav {
  display: flex;
  justify-content: center;
  gap: var(--space-3);
}
.site-nav a {
  font-size: 0.85rem;
  color: var(--ink-dim);
  padding-block: 0.3rem;
  border-bottom: 1px solid transparent;
  transition: color 0.25s var(--ease-out), border-color 0.25s var(--ease-out);
}
.site-nav a:hover { color: var(--ink); border-color: var(--teal-300); }

/* Hamburger toggle — hidden by default, only ever shown below the same
   780px breakpoint that hides .site-nav, so exactly one of "inline nav
   links + CTA" or "hamburger" is visible at a time, never both. */
.hamburger {
  display: none;
  width: 2.5rem;
  height: 2.5rem;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ink-line);
  border-radius: 3px;
  color: var(--ink);
  transition: border-color 0.25s var(--ease-out);
}
.hamburger:hover, .hamburger:focus-visible { border-color: var(--teal-300); }
.hamburger-icon {
  position: relative;
  width: 18px;
  height: 12px;
}
.hamburger-icon-line {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
  transition: transform 0.3s var(--ease-out), opacity 0.3s var(--ease-out), top 0.3s var(--ease-out);
}
.hamburger-icon-line:nth-child(1) { top: 0; }
.hamburger-icon-line:nth-child(2) { top: 5px; }
.hamburger-icon-line:nth-child(3) { top: 10px; }
/* Morphs into an X once the menu is open — same three bars, no swapped
   icon/asset, so there's nothing to flash or pop between states. */
.hamburger.is-open .hamburger-icon-line:nth-child(1) { top: 5px; transform: rotate(45deg); }
.hamburger.is-open .hamburger-icon-line:nth-child(2) { opacity: 0; }
.hamburger.is-open .hamburger-icon-line:nth-child(3) { top: 5px; transform: rotate(-45deg); }

@media (max-width: 780px) {
  .site-nav, .header-row > .btn-compact { display: none; }
  .hamburger { display: inline-flex; }
}

/* ---------- Mobile menu ----------
   Full-viewport overlay, same dark/blurred treatment as the workflow
   modal (.modal-overlay) for a consistent "this is a temporary layer
   above the page" language — nav links stacked large enough to tap
   comfortably, with the same primary CTA repeated at the bottom. */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 250;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  background: rgba(5, 8, 11, 0.96);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}
.mobile-menu[hidden] { display: none; }
.mobile-menu.is-open { opacity: 1; }
.mobile-menu-nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}
.mobile-menu-nav a {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-title);
  color: var(--ink-dim);
  transition: color 0.25s var(--ease-out);
}
.mobile-menu-nav a:hover, .mobile-menu-nav a:focus-visible { color: var(--ink); }
/* Locked alongside .mobile-menu's own open state in js/page.js — the
   page behind the overlay shouldn't scroll while it's up. Same pattern
   as .modal-open on work.html's workflow modal. */
body.nav-open { overflow: hidden; }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  overflow: hidden;
  padding-block: var(--space-7) var(--space-6);
  /* No opaque base color here (unlike every other section): the fixed
     ribbon canvas needs to show through. body's navy-950 is the backdrop. */
  background:
    radial-gradient(60% 55% at 78% 30%, rgba(28, 111, 98, 0.22), transparent 65%),
    radial-gradient(45% 40% at 85% 75%, rgba(184, 134, 60, 0.14), transparent 70%);
}
.hero-scene {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* The canvas is a persistent, page-fixed layer by default (the "full"
   capability tier): it escapes .hero-scene's local box entirely and
   travels with scroll rather than sitting inside the hero. The "light"
   tier (see Capability tiers below) reverts it to the original
   hero-boxed placement. */
#ribbon-canvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.1s var(--ease-out);
}
#ribbon-canvas.is-ready { opacity: 1; }

.ribbon-fallback {
  position: absolute;
  top: 50%;
  right: -4%;
  width: min(46vw, 560px);
  transform: translateY(-50%);
  opacity: 0.9;
}
.ribbon-fallback-teal { fill: var(--teal-600); }
.ribbon-fallback-gold { fill: var(--gold-500); }
.hero-scene:has(#ribbon-canvas.is-ready) .ribbon-fallback { display: none; }

/* ---------- Capability tiers ----------
   "full"   (default markup above): fixed full-page canvas, scroll-driven
            waypoint travel + cursor tilt, set up in js/ribbon-scene.js.
   "light"  (coarse pointer / narrow viewport / low core count): canvas
            reverts to the original hero-boxed placement, one gentle
            auto-rotating appearance, no travel, no cursor tracking.
   "static" (no WebGL / prefers-reduced-motion): no canvas at all — the
            flat .ribbon-fallback SVG above is what renders; main.js never
            calls into ribbon-scene.js on this tier. */
body[data-ribbon-tier="light"] #ribbon-canvas {
  position: absolute;
  inset: auto;
  top: 50%;
  right: -6%;
  width: min(58vw, 780px);
  height: min(58vw, 780px);
  transform: translateY(-50%);
}

/* ---------- Ribbon windows ----------
   Slim, deliberately transparent dividers between opaque sections further
   down the page. PAUSED for Stage 1 of the path-based scroll experience
   (see js/ribbon-scene.js's initWaypointTravel) — hidden on every tier for
   now so they don't leave unexplained blank scroll gaps; revive or remove
   once a later stage decides what replaces them. */
.ribbon-window {
  height: clamp(10rem, 14vh, 14rem);
  display: none;
}

/* ---------- Flight sections (Stage 1: hero; Stage 2: +problem) ----------
   Added by JS only once the "full" tier's scene actually starts —
   progressive enhancement: without it, a section stays at its normal
   height with everything statically visible. Shared by every section the
   camera path visits; each section's content wrapper gets this same
   sticky/centered treatment, with JS driving opacity/transform/filter
   every frame from the (already-damped) scroll-derived waypoint value. */
.flight-section {
  min-height: 220vh;
}
/* .hero's own padding-block (used for the non-flight/light/static layout)
   would otherwise offset .hero-content's sticky box from the very top of
   the section — at rest that leaves it not-yet-stuck, sitting low in an
   oversized centered box and reading as bottom-aligned instead of
   centered on the viewport the ribbon itself is framed against.
   min-height is also shorter than the shared 220vh, and shorter still
   than an earlier 150vh pass: measuring the actual rendered page showed
   .problem-grid's own natural (pre-scroll, in-flow) document position is
   what set the floor on how soon its content could ever become visible —
   at 150vh, its sticky box didn't even start entering the viewport until
   well past the point js/ribbon-scene.js was already fading its opacity
   in, so the fade-in was running on an off-screen element for a real
   stretch of scroll. 110vh still reads as a real camera move (not
   scroll-jacked or instant) while letting Problem's own text begin
   entering view almost immediately once the hero's fades out. See also
   .problem-grid.flight-content's height override below, which shrinks
   the other half of that same gap (how far into its own sticky box the
   vertically-centered content sits). */
.hero.flight-section {
  padding-block: 0;
  min-height: 110vh;
}
.flight-content {
  position: sticky;
  top: 0;
  height: 100vh;
  /* No CSS transition here: js/ribbon-scene.js already writes opacity/
     transform/filter every animation frame from an already-damped scroll
     value — a CSS transition on top would double-smooth and lag behind. */
  will-change: opacity, transform, filter;
}
/* Vertical centering is added per element, not forced on the shared rule
   above — .hero-content is a plain block (needs display:flex to center
   its own children), .problem-grid is already display:grid (forcing flex
   on it would silently drop its two-column layout; align-content is all
   it needs to center within the sticky 100vh box). */
.hero-content.flight-content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 62%;
  /* This sticky box is exactly 100vh tall and centers its own content
     within itself (justify-content:center above) — but at rest (before
     any scroll) its static in-flow position starts below .site-header,
     not at true viewport y0, since the header is sticky rather than
     removed from flow. That leftover offset was pushing the centered
     content down by exactly the header's height. --header-h is measured
     live in ribbon-scene.js's computeRatios() (fonts/layout can shift it
     slightly) and pulling the box up by that amount is what makes "centered
     in this box" and "centered in the actual 100vh viewport" the same
     thing again. */
  margin-top: calc(-1 * var(--header-h, 76px));
}
.problem-grid.flight-content {
  align-content: center;
  /* Shorter than the shared 100vh: the sticky box's own height is what a
     vertically-centered .problem-grid has to scroll half of before its
     actual heading/paragraph (sitting at the box's center) enters the
     viewport at all — measured directly, that was ~450px of "opacity is
     rising but the text is still physically off-screen" on top of the
     hero-height gap above. 60vh keeps the same centered composition once
     settled, just over a shorter runway. */
  height: 60vh;
}
/* The whole .problem-grid becomes one sticky, camera-faded unit in this
   state — its own local "heading pinned while the body scrolls past"
   sticky (below, in the Problem section rules) would fight the parent
   being sticky too, so it's turned off here. */
.problem.flight-section .problem-lede h2 {
  position: static;
  top: auto;
}

.hero-content { position: relative; max-width: 62%; }
.hero-headline {
  font-size: clamp(2.6rem, 5vw, 4.4rem);
  line-height: 1.04;
  max-width: 15ch;
}
.accent-dot { color: var(--gold-500); }
.hero-sub {
  margin-top: var(--space-3);
  font-size: 1.15rem;
  line-height: 1.6;
  color: var(--ink-dim);
  max-width: 46ch;
}
.hero-actions {
  margin-top: var(--space-4);
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

@media (max-width: 900px) {
  .hero-content { max-width: 100%; }
  .hero { padding-block: var(--space-6) var(--space-5); }
  #ribbon-canvas, .ribbon-fallback { opacity: 0.28; right: -20%; }
}

/* No hero mark at all on mobile ("static" tier — see detectTier() in
   js/main.js, which routes touch/narrow devices here specifically so no
   WebGL scene loads): the headline carries the hero on its own at this
   width, and the small header brand mark is left as the only logo
   moment. Desktop is untouched — every other tier still gets the live
   canvas or, for desktop prefers-reduced-motion, the full-size
   .ribbon-fallback SVG the rule above still governs. */
@media (max-width: 900px) {
  body[data-ribbon-tier="static"] .hero-scene { display: none; }
}

/* ---------- Logo marquee ----------
   Pure CSS, no JS and nothing tied to the WebGL system: a doubled flex
   track animated with `transform`, paused with `:hover` — the cheapest
   version of this pattern available, and one the compositor can run
   happily alongside the scroll-driven ribbon scene higher up the page. */
.logo-marquee {
  padding-block: var(--space-5);
}
.logo-marquee-label {
  margin-bottom: var(--space-4);
  color: var(--ink-faint);
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
}
.logo-marquee-viewport {
  overflow: hidden;
  /* Fades the strip out at both edges rather than hard-cropping a logo
     mid-shape as it enters/exits the visible row. */
  -webkit-mask-image: linear-gradient(90deg, transparent, black 6rem, black calc(100% - 6rem), transparent);
  mask-image: linear-gradient(90deg, transparent, black 6rem, black calc(100% - 6rem), transparent);
}
.logo-marquee-track {
  display: flex;
  align-items: center;
  /* Content-sized, not stretched to the viewport's width — the 50%
     translate below is a percentage of this element's own width, so it
     has to equal exactly twice one group's width for the math to work
     (see .logo-marquee-logo's margin, which is what makes it exact). */
  width: max-content;
  animation: logo-marquee-scroll 34s linear infinite;
}
.logo-marquee:hover .logo-marquee-track {
  animation-play-state: paused;
}
.logo-marquee-group {
  /* Transparent to layout — every logo below is a direct flex item of
     .logo-marquee-track regardless of which group div wraps it, so nothing
     about spacing depends on how the two groups happen to be split. This
     group element exists only for the duplicate copy's aria-hidden. */
  display: contents;
}
/* Full original color, no card behind them — logos sit directly on the
   page background. */
.logo-marquee-logo {
  height: 32px;
  width: auto;
  max-width: 130px;
  object-fit: contain;
  /* Every logo (including the last one in each group) carries its own
     trailing gap via margin, rather than a shared `gap` on the track —
     that's what makes each 10-logo group exactly half the track's total
     width, so translateX(-50%) lands the seam pixel-exact with no jump. */
  margin-right: var(--space-6);
}

@keyframes logo-marquee-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .logo-marquee-track {
    animation: none;
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
  }
  .logo-marquee-logo { margin-bottom: var(--space-3); }
  .logo-marquee-viewport {
    overflow: visible;
    /* The edge fade only makes sense for a single scrolling row — once
       this wraps to several static rows it would incorrectly fade the
       start/end of every row, not just the strip's true left/right
       edges. */
    -webkit-mask-image: none;
    mask-image: none;
  }
  /* Only the real (non-duplicate) group is shown as a static row —
     without the animation there's no loop to seam, so the second copy
     would just be a visible repeat rather than serving any purpose. */
  .logo-marquee-group[aria-hidden="true"] { display: none; }
}

/* ---------- Reveal-on-scroll ---------- */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out);
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- Section shells ---------- */
/* Every section (and the footer) sits above the fixed ribbon canvas and
   #page-gradient (below) — z-index:1 keeps that stacking order, but
   background is deliberately transparent on every section now: one
   shared #page-gradient layer is the page's only background, so a
   section boundary is never a visible seam. This used to instead rely on
   each section having its own *opaque* background specifically to hide
   the ribbon canvas outside the hero/problem flight range (see git
   history/prior comment here) — that occlusion job now belongs to the
   canvas itself: js/ribbon-scene.js clears to transparent (alpha 0, not
   just alpha:true) and fades the ribbon's own material opacity to 0 well
   before the flight range ends, so there's nothing left for a section to
   occlude by the time you've scrolled past it. */
section, .site-footer { position: relative; z-index: 1; background: transparent; }
.section-head {
  max-width: 46ch;
  margin-bottom: var(--space-5);
}
.section-head h2 {
  font-size: clamp(1.9rem, 3.4vw, 2.7rem);
  line-height: 1.12;
}
.section-head p {
  margin-top: var(--space-2);
  color: var(--ink-dim);
  font-size: 1.15rem;
}

/* ---------- Problem ---------- */
.problem {
  padding-block: var(--space-6);
}
.problem.flight-section {
  /* Same reasoning as .hero.flight-section's own padding-block:0 — the
     non-flight padding-block otherwise offsets .problem-grid's sticky box
     downward from the section's actual top, so its computed waypoint
     (and the scroll position where it visually settles) lands well after
     where it needs to, reopening a blank stretch this rule exists to
     close. */
  padding-block: 0;
  /* Shorter than the shared 220vh. Note this does NOT move the computed
     Problem waypoint or the fade-in timing (measured directly: .problem-
     grid.flight-content sits flush at the top of this section regardless
     of its min-height, since nothing here pushes it down) — the fade-in
     gap is closed in js/ribbon-scene.js's problemContentOpacity() instead.
     What this height controls is the trailing dead scroll space after the
     text finishes holding/fading out; kept short so that stretch doesn't
     overstay once the reader's already moved on. */
  min-height: 110vh;
}
.problem-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: var(--space-5);
}
.problem-lede h2 {
  font-size: clamp(1.9rem, 3.4vw, 2.7rem);
  line-height: 1.14;
  position: sticky;
  top: 6.5rem;
}
.problem-body { max-width: var(--measure); }
.problem-body p {
  color: var(--ink-dim);
  font-size: 1.15rem;
}
.problem-body p + p { margin-top: 1.25em; }
.problem-close {
  color: var(--ink) !important;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.15rem !important;
  padding-top: var(--space-2);
  border-top: 1px solid var(--gold-700);
  margin-top: 1.6em !important;
}

@media (max-width: 860px) {
  .problem-grid { grid-template-columns: 1fr; gap: var(--space-3); }
  .problem-lede h2 { position: static; }
}

/* ---------- Solutions ---------- */
.solutions { padding-block: var(--space-6); }
.solutions .section-head { max-width: 60ch; }
.solution-grid {
  margin-top: var(--space-5);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}
/* Each card is its own panel — a step lighter than the page's near-black
   (the same "Elevated Void" tone used for section bands), with a thin
   accent-tinted border that brightens on hover/focus as the one primary
   reaction (lift + brighter border + icon tilt; see .icon-svg and
   js/main.js's initIconTilt()). The reveal-on-scroll entrance (opacity +
   transform, set up by initReveal()) and the hover reaction are kept on
   separate CSS properties on purpose — entrance uses `transform`, hover
   lift uses the independent `translate` property — so a card mid-entrance
   and a card being hovered never fight over the same transition. */
.solution-card {
  position: relative;
  padding: var(--space-4);
  background: var(--navy-850);
  border: 1px solid rgba(41, 137, 122, 0.28);
  border-radius: 3px;
  overflow: hidden;
  /* This duration (and the zero delay below) is what governs leaving
     hover — a little slower for a smoother exit. Entering hover uses
     its own, faster transition-duration override on the :hover/
     :focus-within rules below, so the lift/border/shadow all still
     kick off together, just quicker going in than coming out. See
     js/page.js's initReveal() for why "zero delay" also requires
     clearing an inline style, not just omitting one here — a leftover
     inline transition-delay from the scroll-reveal stagger used to add
     up to 210ms of extra lag before hover even started animating. */
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out),
    translate 220ms var(--ease-out), border-color 220ms var(--ease-out), box-shadow 220ms var(--ease-out);
  transition-delay: 0s;
}
.solution-card.is-gold { border-color: rgba(207, 159, 82, 0.28); }
.solution-card.is-teal:hover, .solution-card.is-teal:focus-within,
.solution-card.is-gold:hover, .solution-card.is-gold:focus-within {
  /* Matches the base rule's transition-property list positionally
     (opacity, transform, translate, border-color, box-shadow) — only
     the duration is overridden, and only for the three hover-driven
     properties; opacity/transform keep their entrance-animation timing,
     which is moot here since hover never touches those two. */
  transition-duration: 0.9s, 0.9s, 180ms, 180ms, 180ms;
  transition-delay: 0s;
}
.solution-card.is-teal:hover, .solution-card.is-teal:focus-within {
  translate: 0 -6px;
  border-color: rgba(134, 202, 187, 0.75);
  box-shadow: 0 24px 48px -24px rgba(41, 137, 122, 0.5);
}
.solution-card.is-gold:hover, .solution-card.is-gold:focus-within {
  translate: 0 -6px;
  border-color: rgba(236, 201, 133, 0.75);
  box-shadow: 0 24px 48px -24px rgba(207, 159, 82, 0.5);
}
/* Same low-opacity diagonal ribbon-color streak the old row list used
   behind each glyph — carried over as the background accent behind each
   card's icon, tying it back to the hero's material. */
.solution-card::before {
  content: "";
  position: absolute;
  left: -1.5rem;
  top: var(--space-4);
  width: 8rem;
  height: 2.4rem;
  transform: skewX(-22deg);
  opacity: 0.14;
  pointer-events: none;
}
.solution-card.is-teal::before { background: linear-gradient(90deg, transparent, var(--teal-500), transparent); }
.solution-card.is-gold::before { background: linear-gradient(90deg, transparent, var(--gold-500), transparent); }

.solution-icon {
  position: relative;
  width: 80px;
  height: 80px;
  margin-bottom: var(--space-3);
}
.solution-icon.is-teal { filter: drop-shadow(0 10px 20px rgba(28, 111, 98, 0.45)); }
.solution-icon.is-gold { filter: drop-shadow(0 10px 20px rgba(184, 134, 60, 0.45)); }

/* Each icon is a distinct straight-edged SVG silhouette (see index.html) —
   faceted, not rounded, per the Shapes system — styled with a shared
   two-tone base/highlight/shade vocabulary rather than a baked-in gradient,
   so the same six markup shapes work in either accent. Subtle tilt toward
   the pointer, scoped to that card's own hover state, is written per-frame
   by js/main.js's initIconTilt() as the --tilt-x/--tilt-y custom
   properties; it never snaps and eases back to rest the instant the
   pointer leaves the card. */
.icon-svg {
  display: block;
  width: 100%;
  height: 100%;
  transform: perspective(600px) rotateX(var(--tilt-y, 0deg)) rotateY(var(--tilt-x, 0deg));
  transition: transform 0.4s var(--ease-out);
  will-change: transform;
}
.solution-icon.is-teal .icon-base { fill: var(--teal-500); }
.solution-icon.is-teal .icon-highlight { fill: var(--teal-300); }
.solution-icon.is-teal .icon-shade { fill: var(--teal-700); }
.solution-icon.is-teal .icon-link { stroke: var(--teal-300); }
.solution-icon.is-gold .icon-base { fill: var(--gold-500); }
.solution-icon.is-gold .icon-highlight { fill: var(--gold-300); }
.solution-icon.is-gold .icon-shade { fill: var(--gold-700); }
.solution-icon.is-gold .icon-link { stroke: var(--gold-300); }
.icon-crease { fill: var(--navy-950); }
.icon-crease-line { fill: none; stroke: var(--navy-950); stroke-width: 2; }
.icon-link { fill: none; stroke-width: 2.5; }

.solution-card h3 {
  font-size: 1.3rem;
  margin-bottom: 0.5rem;
}
.solution-card p {
  color: var(--ink-dim);
  max-width: 42ch;
}

@media (max-width: 900px) {
  .solution-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .solution-grid { grid-template-columns: 1fr; gap: var(--space-4); }
  .solution-icon { width: 64px; height: 64px; }
}

/* ---------- Why Adapsus ----------
   Editorial stacked list, not a card grid — deliberately reads
   differently from .solution-card above it (no boxes, no background
   panels, no borders around each item) and borrows its divider
   language from the FAQ accordion below it instead (var(--navy-line)
   hairlines) rather than from the card grid, so it sits visually
   between the two: closer to "How it works" own quiet numbered-list
   rhythm than to a showcase. Also distinct from the Founder section
   further down (photo + prose, calm and personal) — this one leads
   with a big number and a short bold statement per row. */
.why { padding-block: var(--space-6); }
.why-list {
  margin-top: var(--space-5);
  border-top: 1px solid var(--navy-line);
}
.why-row {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: var(--space-4);
  align-items: start;
  padding-block: var(--space-5);
  border-bottom: 1px solid var(--navy-line);
}
/* The one row that's also a link ("See it before you commit" ->
   work.html) needs some signal it's clickable, without reintroducing
   the card grid's border/lift language — a same-family color shift on
   its own number is quieter and stays consistent with this section's
   plain-list read. */
.why-row-link:hover .why-row-num, .why-row-link:focus-visible .why-row-num {
  color: var(--teal-300);
}
.why-row.is-gold.why-row-link:hover .why-row-num, .why-row.is-gold.why-row-link:focus-visible .why-row-num {
  color: var(--gold-300);
}
.why-row-marker {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.why-row-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-display-lg);
  line-height: 1;
  transition: color 220ms var(--ease-out);
}
.why-row.is-teal .why-row-num { color: var(--teal-500); }
.why-row.is-gold .why-row-num { color: var(--gold-600); }
/* The only accent left once the per-item icon glyph is gone — a short
   line, not a shape, so the number stays the whole visual moment. */
.why-row-accent {
  display: block;
  width: 2.75rem;
  height: 2px;
}
.why-row.is-teal .why-row-accent { background: var(--teal-500); }
.why-row.is-gold .why-row-accent { background: var(--gold-600); }
.why-row-body h3 {
  font-size: var(--fs-display-sm);
  line-height: 1.15;
  max-width: 22ch;
}
.why-row-body p {
  margin-top: var(--space-3);
  color: var(--ink-dim);
  max-width: var(--measure);
}

@media (max-width: 640px) {
  .why-row { grid-template-columns: 1fr; gap: var(--space-2); }
  .why-row-marker { flex-direction: row; align-items: center; }
}

/* ---------- How it works ---------- */
.how {
  padding-block: var(--space-6);
}
.how-grid {
  display: grid;
  grid-template-columns: 0.95fr 1.3fr;
  gap: var(--space-6);
}
.how-lede h2 {
  font-size: clamp(1.9rem, 3.4vw, 2.7rem);
  line-height: 1.14;
}
.how-lede p {
  margin-top: var(--space-3);
  color: var(--ink-dim);
  max-width: 44ch;
}
.how-steps {
  display: grid;
  gap: var(--space-4);
  border-left: 1px solid var(--gold-700);
  padding-left: var(--space-4);
}
.how-step-num {
  display: block;
  font-family: var(--font-display);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--gold-300);
  margin-bottom: 0.4rem;
}
.how-step h3 { font-size: 1.3rem; margin-bottom: 0.4rem; }
.how-step p { color: var(--ink-dim); max-width: 52ch; }

@media (max-width: 860px) {
  .how-grid { grid-template-columns: 1fr; gap: var(--space-4); }
}

/* Minimal one-line teaser into work.html, sitting under the 3-step
   summary above — deliberately just a link, not a card or content
   preview: the page it points to is where the actual walkthrough lives. */
.how-teaser {
  grid-column: 1 / -1;
  margin-top: var(--space-2);
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  color: var(--teal-300);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-control);
  transition: gap 0.25s var(--ease-out), color 0.25s var(--ease-out);
}
.how-teaser:hover { gap: 0.75em; color: var(--ink); }
.how-teaser-arrow { transition: transform 0.25s var(--ease-out); }
.how-teaser:hover .how-teaser-arrow { transform: translateX(3px); }

/* ---------- Founder ----------
   Deliberately understated: no 3D object, no ribbon accent competing
   with the photo — just the photo and a couple of sentences, on the
   same transparent section shell as everything else. */
.founder {
  padding-block: var(--space-6);
}
.founder-grid {
  display: grid;
  grid-template-columns: minmax(180px, 240px) 1fr;
  gap: var(--space-5);
  align-items: center;
}
/* The photo's crop is a softly-rounded square rather than this file's
   usual sharp 3px corner (see DESIGN.md's Shapes system) — an explicit,
   scoped exception for a headshot, where a harder edge reads as a UI
   tile rather than a portrait. Nothing else adopts this radius. */
.founder-photo-wrap {
  position: relative;
  max-width: 240px;
  aspect-ratio: 4 / 5;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 24px 48px -20px rgba(0, 0, 0, 0.6);
}
.founder-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The source photo's background is a dark neutral gray, close to but
   not quite the page's near-black navy — this inner vignette darkens
   its edges toward navy-950 so the crop's boundary reads as a soft fade
   rather than a hard-edged rectangle sitting on top of the page. */
.founder-photo-wrap::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 2.5rem 0.75rem var(--navy-950);
  pointer-events: none;
}
.founder-body h2 {
  font-size: var(--fs-display-sm);
  line-height: 1.14;
}
.founder-body p {
  margin-top: var(--space-3);
  color: var(--ink-dim);
  font-size: var(--fs-lead);
  max-width: 60ch;
}
.founder-linkedin {
  margin-top: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  color: var(--teal-300);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--fs-control);
  transition: gap 0.25s var(--ease-out), color 0.25s var(--ease-out);
}
.founder-linkedin:hover { gap: 0.75em; color: var(--ink); }
.founder-linkedin-arrow { transition: transform 0.25s var(--ease-out); }
.founder-linkedin:hover .founder-linkedin-arrow { transform: translateX(3px); }

@media (max-width: 780px) {
  .founder-grid { grid-template-columns: 1fr; justify-items: start; }
  .founder-photo-wrap { max-width: 180px; }
}

/* ---------- Site nav: current page ----------
   Used on work.html's nav (this same class would apply equally on
   index.html for a matching link, but nothing there is ever "current"
   in that sense since it's a single scrolling page). */
.site-nav a[aria-current="page"] {
  color: var(--ink);
  border-color: var(--gold-300);
}

/* ---------- Work page ---------- */
.work-hero {
  padding-block: var(--space-7) var(--space-5);
}
.work-hero-inner {
  max-width: 56ch;
}
.work-hero-inner h1 {
  font-size: var(--fs-display-lg);
  line-height: 1.04;
}
.work-hero-inner p {
  margin-top: var(--space-3);
  color: var(--ink-dim);
  font-size: var(--fs-lead);
}

.workflows {
  padding-block: 0 var(--space-7);
}

/* ---------- Workflow card grid ----------
   Index only — name + one-line description, no step detail (that lives
   in each workflow's hidden <template> and only ever renders inside the
   modal, see js/work.js). 3 columns at desktop width, matching the
   solution grid's own column count, so the two "what we build" and "what
   we've built" indexes read as siblings; scales to any card count since
   nothing here assumes exactly 3. */
.workflow-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}
.workflow-card {
  position: relative;
  padding: var(--space-4);
  background: var(--navy-850);
  border: 1px solid rgba(41, 137, 122, 0.28);
  border-radius: 3px;
  text-align: left;
  cursor: pointer;
  transition: translate 260ms var(--ease-out), border-color 260ms var(--ease-out), box-shadow 260ms var(--ease-out);
}
.workflow-card:hover, .workflow-card:focus-visible {
  translate: 0 -6px;
  border-color: rgba(134, 202, 187, 0.75);
  box-shadow: 0 24px 48px -24px rgba(41, 137, 122, 0.5);
}
/* Small faceted diamond in the ribbon's own two-tone material (a gradient
   rather than a flat fill) rather than a literal icon per workflow — this
   is an index of workflows, not six distinct service categories the way
   .solution-icon's glyphs are, so one shared mark is the honest signal. */
.workflow-card-icon {
  display: block;
  width: 2.5rem;
  height: 2.5rem;
  margin-bottom: var(--space-3);
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  background: linear-gradient(155deg, var(--teal-300), var(--gold-300));
}
.workflow-card-name {
  font-size: var(--fs-title);
  margin-bottom: 0.5rem;
}
.workflow-card-desc {
  color: var(--ink-dim);
  font-size: var(--fs-body);
  max-width: 42ch;
}

@media (max-width: 900px) {
  .workflow-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .workflow-grid { grid-template-columns: 1fr; }
}

/* ---------- Workflow modal ---------- */
/* Background stays put but stops scrolling while the modal has focus —
   set/cleared alongside .modal-overlay's own open state in js/work.js. */
body.modal-open { overflow: hidden; }
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgba(5, 8, 11, 0.78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}
.modal-overlay[hidden] { display: none; }
.modal-overlay.is-open { opacity: 1; }
.modal {
  position: relative;
  width: 100%;
  max-width: 720px;
  max-height: 90vh;
  overflow-y: auto;
  padding: var(--space-5);
  background: var(--navy-900);
  border: 1px solid var(--ink-line);
  border-radius: 3px;
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.65);
  transform: translateY(14px) scale(0.98);
  transition: transform 0.3s var(--ease-out);
}
.modal-overlay.is-open .modal { transform: none; }
.modal-close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  width: 2.25rem;
  height: 2.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-faint);
  border: 1px solid var(--ink-line);
  border-radius: 3px;
  transition: color 0.25s var(--ease-out), border-color 0.25s var(--ease-out);
}
.modal-close:hover, .modal-close:focus-visible {
  color: var(--gold-300);
  border-color: var(--gold-300);
}
.modal-close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: square;
}

/* ---------- Workflow step diagram (rendered inside the modal) ---------- */
.workflow-head {
  max-width: 60ch;
  padding-right: var(--space-6);
}
.workflow-head h2 {
  font-size: var(--fs-display-sm);
  line-height: 1.14;
}
.workflow-head p {
  margin-top: var(--space-2);
  color: var(--ink-dim);
  font-size: var(--fs-lead);
}

/* A vertical step sequence rather than a horizontal row — stays legible
   at every one of the site's existing breakpoints without a separate
   mobile layout, and matches the page's normal-scroll, documentation-like
   read (see .how-steps for the landing page's own numbered-list
   precedent). Steps and connectors are separate list items (rather than
   one continuous background line behind everything) specifically so each
   connector can animate its own "draw" independently as the modal's open
   sequence reaches it — see .workflow-step-connector-line and
   js/work.js's playSequence(). */
.workflow-steps {
  margin-top: var(--space-5);
  display: flex;
  flex-direction: column;
}
.workflow-step {
  position: relative;
  display: grid;
  grid-template-columns: 2.75rem 1fr;
  gap: var(--space-3);
  align-items: start;
  /* Reveal state — js/work.js adds .is-in on this step's turn in the
     open sequence (or immediately, for every step at once, under
     prefers-reduced-motion). transform/opacity only, nothing that
     triggers layout. */
  opacity: 0;
  transform: translateY(14px) scale(0.94);
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
}
.workflow-step.is-in {
  opacity: 1;
  transform: none;
}
/* Faceted diamond marker, not a circular badge — per DESIGN.md's Shapes
   system (straight-edged silhouettes in place of generic circular/
   rounded-square tiles), the same rule .solution-icon's glyphs follow. */
.workflow-step-marker {
  position: relative;
  z-index: 1;
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-label);
  color: var(--navy-950);
}
.workflow-step.is-teal .workflow-step-marker { background: var(--teal-300); }
.workflow-step.is-gold .workflow-step-marker { background: var(--gold-300); }
.workflow-step-body h3 { font-size: var(--fs-title); margin-bottom: 0.35rem; }
.workflow-step-body p { color: var(--ink-dim); max-width: 58ch; }

/* The connecting "thread" between consecutive markers — a thin gradient
   line, echoing the ribbon's own teal-to-gold material rather than a
   plain gray divider. Sits in its own row (marker-column width, body
   column empty) so it lines up under the markers above and below it.
   scaleY(0) -> scaleY(1) from a fixed top origin is the "drawing"
   animation: a transform, not a height/layout change. */
.workflow-step-connector {
  height: var(--space-4);
  display: grid;
  grid-template-columns: 2.75rem 1fr;
}
.workflow-step-connector-line {
  justify-self: center;
  width: 1px;
  height: 100%;
  background: linear-gradient(180deg, var(--teal-500), var(--gold-500));
  opacity: 0.55;
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.35s var(--ease-out);
}
.workflow-step-connector.is-drawn .workflow-step-connector-line {
  transform: scaleY(1);
}

@media (max-width: 560px) {
  .modal { padding: var(--space-4); }
  .workflow-head { padding-right: var(--space-5); }
  .workflow-step, .workflow-step-connector { grid-template-columns: 2.25rem 1fr; gap: var(--space-2); }
  .workflow-step-marker { width: 2.25rem; height: 2.25rem; }
}

/* ---------- FAQ ---------- */
.faq { padding-block: var(--space-6); }
.faq-wrap { max-width: 860px; }
.faq-list { border-top: 1px solid var(--navy-line); }
.faq-item {
  border-bottom: 1px solid var(--navy-line);
}
.faq-item summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: 1.35rem;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 500;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-toggle {
  position: relative;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.faq-toggle::before, .faq-toggle::after {
  content: "";
  position: absolute;
  background: var(--gold-400, var(--gold-500));
  border-radius: 3px;
  transition: transform 0.35s var(--ease-out);
}
.faq-toggle::before { top: 8px; left: 0; width: 18px; height: 2px; }
.faq-toggle::after { top: 0; left: 8px; width: 2px; height: 18px; }
.faq-item[open] .faq-toggle::after { transform: rotate(90deg); }
.faq-answer { padding-bottom: 1.5rem; }
.faq-answer p {
  color: var(--ink-dim);
  max-width: 62ch;
}
.faq-answer p + p { margin-top: 1em; }

/* ---------- Closing CTA ---------- */
.cta-close {
  padding-block: var(--space-6);
  /* Kept as a soft local accent, not a flat backing fill — it's a radial
     glow that fades to fully transparent by 70%, so it adds warmth around
     the CTA without reintroducing a section-wide seam against #page-gradient. */
  background: radial-gradient(70% 100% at 50% 100%, rgba(184, 134, 60, 0.16), transparent 70%);
  text-align: center;
}
.cta-close-inner {
  max-width: 640px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}
.cta-close h2 {
  font-size: clamp(1.9rem, 3.4vw, 2.7rem);
  line-height: 1.14;
}
.cta-close p { color: var(--ink-dim); font-size: 1.15rem; }

/* ---------- Footer ---------- */
/* A faint lift above the page's near-black, scoped to this section only
   (every other section stays deliberately transparent, per the Section
   shells rule above) — enough that the footer reads as its own closing
   band rather than the page gradient just fading out under the text. */
/* Same contained/rounded language as .header-row now uses, just not
   sticky or blurred — this sits at the bottom of the page, not over
   scrolling content, so it doesn't need to stay legible against a moving
   background the way the navbar does. Width/rounding sit on .site-footer
   itself (matching how .header-row owns its own pill), with overflow
   hidden so the ::before divider's straight top edge clips cleanly into
   the rounded corners instead of poking past them. */
.site-footer {
  position: relative;
  overflow: hidden;
  width: min(100% - 3rem, var(--wrap));
  margin-inline: auto;
  margin-block: var(--space-5) var(--space-5);
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-radius: 20px;
  border: 1px solid var(--navy-line);
  background:
    linear-gradient(180deg, rgba(244, 238, 226, 0.035), rgba(244, 238, 226, 0.015) 60%, transparent),
    linear-gradient(
      160deg,
      color-mix(in srgb, var(--teal-700) 12%, transparent) 0%,
      var(--navy-900) 55%,
      color-mix(in srgb, var(--gold-700) 8%, transparent) 100%
    );
}
/* Thin top divider in the ribbon's own teal-to-brass material, fading
   out at both ends — marks the boundary between the brand row and the
   rest of the footer content. Same "thin line, not a flat border" logic
   as the workflow-step connectors. */
.site-footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--teal-600) 20%, var(--gold-600) 80%, transparent);
  opacity: 0.35;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.3fr 1fr 1fr;
  gap: var(--space-5);
  align-items: start;
}
.footer-brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.footer-logo { width: 56px; height: 56px; opacity: 0.9; }
.footer-tagline { color: var(--ink-faint); font-size: 0.85rem; max-width: 32ch; }

/* Small uppercase headers over the nav and contact columns — gives each
   its own labeled context instead of links just floating with none. */
.footer-col-label {
  margin-bottom: var(--space-3);
  color: var(--ink-faint);
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Two columns rather than one tall stack — five links at one-per-row ran
   the column noticeably taller than the brand/contact columns beside it;
   wrapping into a compact 2-column block keeps it closer to their
   height. */
.footer-nav {
  display: grid;
  grid-template-columns: repeat(2, auto);
  column-gap: var(--space-4);
  row-gap: var(--space-2);
}
.footer-nav a {
  font-size: 0.85rem;
  color: var(--ink-faint);
  transition: color 0.25s var(--ease-out);
}
.footer-nav a:hover { color: var(--teal-300); }

.footer-contact {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}
.footer-email {
  font-size: 0.85rem;
  color: var(--ink-faint);
  transition: color 0.25s var(--ease-out);
}
.footer-email:hover { color: var(--gold-300); }
.footer-icon-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* Shared circular-button shape for the two footer icon links (LinkedIn,
   back-to-top) — a bordered circle rather than a bare glyph, so both
   read as clickable buttons, not floating icons. Circular is a scoped
   exception to this file's usual sharp 3px corners (see DESIGN.md's
   Shapes system), reserved for small round icon-only affordances like
   this rather than anything content-bearing. */
.footer-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: 1px solid var(--ink-line);
  transition: color 0.25s var(--ease-out), border-color 0.25s var(--ease-out),
    background-color 0.25s var(--ease-out), transform 0.25s var(--ease-out);
}
.footer-social {
  color: var(--teal-300);
  border-color: rgba(134, 202, 187, 0.3);
}
.footer-social:hover, .footer-social:focus-visible {
  color: var(--ink);
  border-color: var(--teal-300);
  background: rgba(134, 202, 187, 0.1);
}
.footer-social-icon {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
}
.footer-social-icon .footer-social-mark { fill: currentColor; stroke: none; }
/* Same ribbon-arrow motif as the brand mark, redrawn as a plain straight-
   edged glyph (faceted, no rounded caps) rather than text: this is the
   one link on the page whose destination ("go up") the arrow itself
   already says, so a label would be redundant, not clearer. */
.back-to-top {
  color: var(--gold-500);
  border-color: rgba(207, 159, 82, 0.3);
}
.back-to-top:hover, .back-to-top:focus-visible {
  color: var(--gold-300);
  border-color: var(--gold-300);
  background: rgba(207, 159, 82, 0.1);
  transform: translateY(-2px);
}
.back-to-top-icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: square;
  stroke-linejoin: miter;
}
@media (max-width: 780px) {
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .footer-grid { grid-template-columns: 1fr; gap: var(--space-5); }
}

/* ---------- Loading transition ----------
   Full-viewport cover shown for the "full" and "light" tiers only (never
   "static" — nothing is loading if no scene is coming) so the ribbon
   never pops in mid-render. main.js removes this element once the scene
   is ready or a hard-cap timeout elapses. */
#loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--navy-950);
  opacity: 1;
  transition: opacity 0.5s var(--ease-out);
}
.loader-mark {
  width: 3.5rem;
  height: 3.5rem;
  opacity: 0.85;
  animation: loader-pulse 1.4s var(--ease-out) infinite;
}
.loader-mark-teal { fill: var(--teal-500); }
.loader-mark-gold { fill: var(--gold-500); }
@keyframes loader-pulse {
  0%, 100% { transform: scale(0.94); opacity: 0.6; }
  50% { transform: scale(1); opacity: 0.95; }
}
body.is-loaded #loader { opacity: 0; pointer-events: none; }
body.is-loading { overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
  .loader-mark { animation: none; }
}

/* ---------- Custom cursor + particle trail ----------
   Both elements are created by js/cursor.js, only on fine-pointer/
   any-hover devices (touch never sees them at all) — this class is what
   actually hides the native cursor, and only gets added once that JS
   feature-detect has already passed, so a device that fails it (or a
   page where the script errors) keeps the real cursor instead of losing
   it to a stray "cursor:none" with nothing rendered to replace it. */
body.has-custom-cursor,
body.has-custom-cursor * {
  cursor: none !important;
}
.custom-cursor {
  position: fixed;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  /* Above .modal-overlay (z-index: 300): without this, the cursor was
     compositing underneath the modal's opaque backdrop the moment one
     opened, leaving nothing visible to click the close button with. */
  z-index: 310;
  /* No transition on transform here: js/cursor.js already writes it
     every animation frame from an already-damped position — see
     ribbon-scene.js for the same rule and the same reasoning. The actual
     dot/ring is drawn on ::before instead, sized/positioned with a
     one-time static margin (not transitioned) and grown purely via
     transform:scale() on hover — transform/opacity/color only, nothing
     that triggers layout, unlike animating width/height/margin would. */
}
.custom-cursor::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: var(--teal-300);
  border: 1px solid transparent;
  opacity: 0;
  transform: scale(1);
  transition: transform 0.2s var(--ease-out), background-color 0.2s var(--ease-out),
    border-color 0.2s var(--ease-out), opacity 0.2s var(--ease-out);
}
.custom-cursor.is-visible::before { opacity: 1; }
/* Hover reaction: dot -> ring, teal -> brass — the "this is clickable"
   signal called for, using the same two accent hues as everywhere else
   rather than a new color. */
.custom-cursor.is-hovering::before {
  background: transparent;
  border-color: var(--gold-300);
  transform: scale(2.6);
}
/* ---------- Chat widget ---------- */
/* Self-contained overlay built by js/chat.js and appended to <body> on
   every page — no markup here, just the visual language. Sits below the
   workflow modal (z-index 300) and custom cursor (309/310) so those still
   win if ever open at the same time, but above everything else. */
.chat-widget {
  position: fixed;
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: 220;
}
.chat-toggle {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: linear-gradient(155deg, var(--teal-500) 0%, var(--teal-700) 100%);
  color: var(--ink);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset, 0 16px 32px -14px rgba(20, 72, 64, 0.65);
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}
.chat-toggle:hover, .chat-toggle:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.18) inset, 0 20px 38px -14px rgba(20, 72, 64, 0.75);
}
.chat-toggle-icon { position: absolute; transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out); }
.chat-toggle-icon-close { opacity: 0; transform: scale(0.6); }
.chat-toggle.is-open .chat-toggle-icon-open { opacity: 0; transform: scale(0.6); }
.chat-toggle.is-open .chat-toggle-icon-close { opacity: 1; transform: scale(1); }

.chat-window {
  position: absolute;
  right: 0;
  bottom: calc(3.5rem + var(--space-2));
  width: min(380px, calc(100vw - 2 * var(--space-3)));
  height: min(600px, 70vh);
  display: flex;
  flex-direction: column;
  background: rgba(13, 20, 27, 0.92);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid var(--navy-line);
  border-radius: 3px;
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.65);
  transform-origin: bottom right;
  opacity: 0;
  transform: scale(0.94) translateY(8px);
  transition: opacity 0.22s var(--ease-out), transform 0.22s var(--ease-out);
}
.chat-window[hidden] { display: none; }
.chat-window.is-open { opacity: 1; transform: scale(1) translateY(0); }

.chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--navy-line);
  flex-shrink: 0;
}
.chat-header-info { display: flex; align-items: center; gap: 0.55rem; }
.chat-header-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--teal-300);
  box-shadow: 0 0 0 3px rgba(134, 202, 187, 0.18);
}
.chat-header-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-control);
  margin: 0;
}
.chat-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  color: var(--ink-faint);
  transition: color 0.2s var(--ease-out), background-color 0.2s var(--ease-out);
}
.chat-close:hover, .chat-close:focus-visible { color: var(--ink); background: rgba(244, 238, 226, 0.06); }

.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.chat-msg { display: flex; }
.chat-msg-user { justify-content: flex-end; }
.chat-msg-assistant { justify-content: flex-start; }
.chat-msg-bubble {
  max-width: 85%;
  margin: 0;
  padding: 0.65em 0.9em;
  border-radius: 3px;
  font-size: var(--fs-control);
  line-height: 1.55;
  white-space: pre-wrap;
}
.chat-msg-user .chat-msg-bubble {
  background: var(--teal-700);
  color: var(--ink);
}
.chat-msg-assistant .chat-msg-bubble {
  background: rgba(244, 238, 226, 0.05);
  border: 1px solid var(--navy-line);
  color: var(--ink-dim);
}
.chat-cta-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.55em 1.1em;
  border: 1px solid rgba(207, 159, 82, 0.4);
  border-radius: 3px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-label);
  color: var(--gold-300);
  transition: border-color 0.2s var(--ease-out), background-color 0.2s var(--ease-out), color 0.2s var(--ease-out);
}
.chat-cta-link:hover, .chat-cta-link:focus-visible {
  border-color: var(--gold-300);
  background: rgba(207, 159, 82, 0.08);
  color: var(--gold-300);
}

/* Three-dot "typing" indicator — a plain CSS stagger, no extra library. */
.chat-typing {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0 var(--space-3) var(--space-2);
  flex-shrink: 0;
}
.chat-typing[hidden] { display: none; }
.chat-typing span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink-faint);
  animation: chat-typing-bounce 1.1s var(--ease-out) infinite;
}
.chat-typing span:nth-child(2) { animation-delay: 0.15s; }
.chat-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes chat-typing-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
  30% { transform: translateY(-4px); opacity: 1; }
}

.chat-input-row {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--navy-line);
  flex-shrink: 0;
}
.chat-input {
  flex: 1;
  resize: none;
  max-height: 120px;
  padding: 0.55em 0.75em;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--ink-line);
  border-radius: 3px;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-control);
  line-height: 1.4;
  transition: border-color 0.2s var(--ease-out);
}
.chat-input:focus-visible { border-color: var(--teal-300); }
.chat-input::placeholder { color: var(--ink-faint); }
.chat-send {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.35rem;
  height: 2.35rem;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--teal-600);
  color: var(--ink);
  transition: background-color 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.chat-send:hover, .chat-send:focus-visible { background: var(--teal-500); transform: translateY(-1px); }

@media (max-width: 560px) {
  .chat-widget { right: var(--space-2); bottom: var(--space-2); }
  .chat-window {
    width: calc(100vw - 2 * var(--space-2));
    height: min(520px, calc(100vh - 6.5rem));
  }
}

.cursor-trail-canvas {
  position: fixed;
  inset: 0;
  pointer-events: none;
  /* One below .custom-cursor, same reasoning as its z-index above: the
     trail needs to stay visible over the modal too, not just the dot. */
  z-index: 309;
}
