/* ===== Reset ===== */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html {
  scroll-behavior: smooth;
  /* `scroll-padding-top` keeps anchor jumps (#work, #services, #contact,
     and the JS scrollIntoView) from parking the target's headline under
     the fixed nav-pill. Matches the pill's top:24px + ~56px height +
     a touch of breathing room. */
  scroll-padding-top: 96px;
  /* Set bg on <html> too so the area outside body (scrollbar gutter,
     overscroll on mobile) takes the theme color cleanly. */
  background: var(--bg);
  transition: background var(--dur-base) var(--ease-out);
}
body {
  background: var(--bg);
  color: var(--fg);
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  position: relative;
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button { font: inherit; background: none; border: none; color: inherit; cursor: pointer; }
ul { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, h4, h5, h6, p { margin: 0; font-weight: inherit; font-size: inherit; }

:root {
  /* ----- Theme tokens (defaults = DARK theme) -----
     Override on `[data-theme="light"]` below. Anything visual that
     should flip with the theme goes through one of these vars. */
  --bg: #1c1c1c;
  --fg: #ebebeb;
  --accent: #ff4a08;
  --accent-soft: #ff9966;        /* peachy orange — secondary button accent */
  --white: #fff;

  --hairline: rgba(235, 235, 235, 0.17);
  --surface-1: rgba(255, 255, 255, 0.02);   /* card / faint chrome */
  --surface-2: rgba(255, 255, 255, 0.06);   /* card border */
  --pill-bg: rgba(0, 0, 0, 0.55);            /* nav-pill glass */
  --pill-border: rgba(255, 255, 255, 0.06);
  --pill-shadow:
    0 10px 40px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --bg-glow-warm-1: rgba(255, 74, 8, 0.08);
  --bg-glow-warm-2: rgba(255, 74, 8, 0.05);
  --hero-decoration-opacity: 0.18;
  /* Width of the soft fade-out band at each edge of the marquee.
     The marquee uses `mask-image` (not a colored gradient overlay),
     so this controls how far inset the opaque-to-transparent ramp
     starts. Themed-independent: a mask cuts true transparency, so
     no color has to match the background. */
  --marquee-fade-width: clamp(80px, 12vw, 200px);
  --cursor-border: rgba(255, 255, 255, 0.85);
  --scroll-tick-track: rgba(235, 235, 235, 0.6);

  /* Nav link + contact-pill tokens. The Contact CTA in the nav is a
     filled pill; in dark mode it's a white pill with dark text, in
     light mode it inverts to dark-pill + light-text so it stays as
     the strong call-to-action. */
  --nav-link-color: var(--fg);
  --contact-bg: #fff;
  --contact-bg-hover: #e8e8e8;
  --contact-fg: #000;
  --contact-glow: rgba(255, 255, 255, 0.18);

  /* Case-row hover state — the headline brightens. In dark mode that
     means going from off-white to pure white; in light mode from
     mid-dark to pure black. */
  --case-headline-hover: #ffffff;

  /* Non-themed tokens */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Fluid scale — designed at 1440px viewport */
  --size-eyebrow: clamp(14px, 1.25vw, 20px);
  --size-body: clamp(15px, 1.25vw, 20px);
  --size-title-sm: clamp(22px, 2.2vw, 36px);
  --size-title-md: clamp(32px, 4.5vw, 64px);
  --size-display: clamp(40px, 6.4vw, 96px);
  --size-tagline: clamp(20px, 2.4vw, 36px);

  /* Spacing scale — fluid (vh-based), used for vertical gaps and
     section padding that should breathe with viewport height. The
     three vw-based clamps (btn-row gap, hero-inner gap, main-grid
     gap) stay literal at their use sites — vw and vh react to
     different axes, so consolidating them into vh tokens would
     change responsive behavior in a non-obvious way. Px constants
     for chrome (buttons, nav-pill, etc.) also stay literal —
     they're intentionally fixed at all viewports.
     Override via `spacing_scale` in settings.md. */
  --space-xs:        clamp(16px, 2vh, 28px);
  --space-sm:        clamp(24px, 3vh, 48px);
  --space-md:        clamp(32px, 5vh, 64px);
  --space-lg:        clamp(40px, 6vh, 80px);
  --space-section-y: clamp(64px, 10vh, 120px);

  /* Duration scale — covers the four most-used transition/animation
     durations (76 of ~104 declarations across the file). `.45s`
     stays literal at its 4 use sites — three are tuned fades, one
     is the case-tooltip scale paired with --ease-spring where a
     longer duration would feel laggy. Loops + one-shots
     (800ms, 2.4s, 5s, 12s, 24s, 40s, 80s) also stay literal,
     each is a specific intent.
     Override via `duration_scale` in settings.md. */
  --dur-fast:   0.2s;
  --dur-base:   0.4s;
  --dur-medium: 0.55s;
  --dur-slow:   0.6s;
}

/* ----- LIGHT theme overrides -----
   Activated by `<html data-theme="light">`. Set by the toggle button +
   the bootstrap inline script in index.html (which also restores the
   user's saved preference or falls back to prefers-color-scheme). */
[data-theme="light"] {
  --bg: #faf8f4;                 /* warm off-white, complements the orange */
  --fg: #1c1c1c;                 /* dark text */
  /* accent + accent-soft stay the same — orange works in either theme */

  --hairline: rgba(28, 28, 28, 0.12);
  --surface-1: rgba(28, 28, 28, 0.03);
  --surface-2: rgba(28, 28, 28, 0.08);
  --pill-bg: rgba(255, 255, 255, 0.72);
  --pill-border: rgba(28, 28, 28, 0.08);
  --pill-shadow:
    0 10px 40px rgba(28, 28, 28, 0.12),
    inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --bg-glow-warm-1: rgba(255, 74, 8, 0.12);
  --bg-glow-warm-2: rgba(255, 74, 8, 0.08);
  --hero-decoration-opacity: 0.12;
  /* `--marquee-fade-width` is themed-independent — see :root. */
  --cursor-border: rgba(28, 28, 28, 0.65);
  --scroll-tick-track: rgba(28, 28, 28, 0.55);

  --nav-link-color: #1c1c1c;
  --contact-bg: #1c1c1c;
  --contact-bg-hover: #2a2a2a;
  --contact-fg: #faf8f4;
  --contact-glow: rgba(28, 28, 28, 0.18);

  --case-headline-hover: #000000;
}

/* ===== Theme toggle =====
   A small frosted disc that swaps `[data-theme]` on the <html> root.
   `position: fixed` to the BOTTOM-right of the viewport so it stays
   accessible while scrolling without competing with the nav-pill at
   the top. Same disc styling and click handler at every breakpoint —
   the mobile override below just shrinks the size a touch. */
.theme-toggle {
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  /* Solid-ish frosted dot for high visibility against either theme.
     In dark mode it's a clearly lit glass disc; in light mode it
     inverts to a warm white disc with a darker border. */
  background: rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(20px) saturate(120%);
  -webkit-backdrop-filter: blur(20px) saturate(120%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  box-shadow:
    0 10px 30px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
  color: var(--fg);
  z-index: 200;     /* above nav-pill (100) and cursor (9999 has its own
                       blend-mode pass; this 200 keeps the toggle clickable
                       and stacked above the rest of the page chrome). */
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  pointer-events: auto;
  transition:
    transform var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out);
}
[data-theme="light"] .theme-toggle {
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(28, 28, 28, 0.18);
  box-shadow:
    0 10px 30px rgba(28, 28, 28, 0.18),
    inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.theme-toggle:hover { transform: scale(1.06); }
.theme-toggle:active { transform: scale(0.96); }
.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.theme-toggle-icon {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Centered absolutely so both icons stack on each other (we crossfade
     via opacity + scale). `translate(-50%, -50%)` is preserved in every
     transform variant below — the rotation/scale compose with it. */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  transform-origin: center center;
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
/* Convention: the icon shown represents the action — sun = "go light",
   moon = "go dark". So in DARK mode we show the SUN icon (click to go
   light), and in LIGHT mode we show the MOON icon (click to go dark). */
.theme-toggle .icon-moon { opacity: 0; transform: translate(-50%, -50%) rotate(-30deg) scale(0.7); }
.theme-toggle .icon-sun  { opacity: 1; transform: translate(-50%, -50%) rotate(0)      scale(1);   }
[data-theme="light"] .theme-toggle .icon-moon { opacity: 1; transform: translate(-50%, -50%) rotate(0)     scale(1);   }
[data-theme="light"] .theme-toggle .icon-sun  { opacity: 0; transform: translate(-50%, -50%) rotate(30deg) scale(0.7); }

/* ===== Language switcher =====
   A small two-segment EN/ES pill, floating to the left of the theme
   toggle. Same frosted-glass chrome as the theme toggle. Visibility is
   controlled by `i18n.show_switcher` (applyI18n sets display); the
   active segment is highlighted. On mobile the theme toggle docks into
   the nav-pill, so this sits alone at the bottom-right. */
.lang-toggle {
  position: fixed;
  bottom: 24px;
  right: 80px;             /* left of the 44px theme toggle (24 + 44 + 12) */
  z-index: 200;
  display: inline-flex;
  align-items: stretch;
  height: 44px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(20px) saturate(120%);
  -webkit-backdrop-filter: blur(20px) saturate(120%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  box-shadow:
    0 10px 30px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
[data-theme="light"] .lang-toggle {
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(28, 28, 28, 0.18);
  box-shadow:
    0 10px 30px rgba(28, 28, 28, 0.18),
    inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.lang-toggle button {
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--fg);
  padding: 0 13px;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.lang-toggle button.is-active {
  opacity: 1;
  background: rgba(255, 255, 255, 0.14);
}
[data-theme="light"] .lang-toggle button.is-active {
  background: rgba(28, 28, 28, 0.08);
}
.lang-toggle button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* The in-menu variant: not floating. It lives inside `.nav-overlay`
   (display:none on desktop, so it only ever shows on mobile) and sits
   centered below the menu links. */
.lang-toggle-inline {
  position: static;
  bottom: auto;
  right: auto;
  margin-top: clamp(28px, 5vh, 48px);
}
@media (max-width: 700px) {
  /* On mobile the floating switcher is replaced by the one inside the
     mobile menu (.lang-toggle-inline). */
  .lang-toggle-floating { display: none; }
}

/* ===== Subtle ambient backdrop ===== */
.bg-glow {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(60% 50% at 15% 20%, var(--bg-glow-warm-1), transparent 60%),
    radial-gradient(50% 50% at 85% 80%, var(--bg-glow-warm-2), transparent 60%);
  transition: background var(--dur-base) var(--ease-out);
}

main { position: relative; z-index: 1; }

/* ===== Header / Nav ===== */
.site-header {
  position: fixed;
  top: 24px;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  justify-content: center;
  padding: 0 24px;
  pointer-events: none;
}
.nav-pill {
  pointer-events: auto;
  width: 100%;
  max-width: 926px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 8px 8px 16px;
  background: var(--pill-bg);
  backdrop-filter: blur(35px) saturate(120%);
  -webkit-backdrop-filter: blur(35px) saturate(120%);
  border-radius: 16px;
  border: 1px solid var(--pill-border);
  box-shadow: var(--pill-shadow);
  transition:
    background var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}
.nav-tag {
  font-size: clamp(13px, 1.1vw, 16px);
  font-weight: 400;
  letter-spacing: -0.16px;
  color: var(--fg);
  white-space: nowrap;
}
.nav-logo img {
  width: 90px;
  height: 28px;
  transition:
    transform var(--dur-base) var(--ease-out),
    filter var(--dur-base) var(--ease-out);
}
.nav-logo:hover img { transform: scale(1.06); }
/* The SVG ships with a light-gray fill (#EBEBEB) for the dark theme.
   In light theme, `brightness(0)` zeroes the lightness of every pixel
   while preserving alpha — the white logo becomes pure black on the
   warm-white background, no asset swap needed. */
[data-theme="light"] .nav-logo img { filter: brightness(0); }
.nav-links {
  display: flex;
  align-items: center;
  gap: 24px;
}
/* Hamburger button — hidden by default so it doesn't take up
   `justify-content: space-between` slot in the desktop nav-pill (which
   would push the inline links inward and leave a gap on the right).
   Becomes `display: flex` inside the 700px mobile breakpoint. */
.nav-toggle { display: none; }

/* Mobile menu overlay — hidden on desktop. Becomes the full-screen
   menu inside the 700px mobile breakpoint. Lives at the body level
   (sibling of .site-header) so the pill always stacks above it. */
.nav-overlay { display: none; }
.nav-links a {
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.08px;
  color: var(--nav-link-color);
  transition: opacity var(--dur-fast) ease, color var(--dur-base) var(--ease-out);
}
.nav-links a:hover { opacity: .65; }
.btn-contact {
  background: var(--contact-bg);
  color: var(--contact-fg) !important;
  padding: 8px 14px;
  border-radius: 10px;
  font-weight: 500;
  transition:
    background var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out),
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) ease;
}
.btn-contact:hover {
  background: var(--contact-bg-hover);
  opacity: 1 !important;
  transform: translateY(-1px);
  box-shadow: 0 6px 20px var(--contact-glow);
}

/* ----- Nav contact button with a variant applied (e.g. bv-270 primary).
   settings.js applies the primary `.bv-NNN` class to .btn-contact so the
   nav CTA matches the hero CTA. The variant base rules want larger
   padding/radius/font — fine on the hero, but in the nav we want to
   keep the compact pill proportions. Specificity (0,3,0) here beats
   the `.btn.bv-NNN` (0,2,0) base in buttons.css. */
.btn-contact.btn[class*="bv-"] {
  padding: 8px 14px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.14px;
}

/* ===== Buttons — Liquid Glass =====
   Each button is built from three stacked layers:
     1. ::before  → a colored, blurred blob that drifts behind the glass
                    while the button is hovered, and grows to fill the whole
                    button while the button is pressed.
     2. ::after   → a frosted-glass overlay (backdrop-filter: blur) that
                    sits ABOVE the blob and refracts both the blob and the
                    page behind the button.
     3. .btn-label → the text, z-index 2, always crisp on top.

   The blob's color matches the button's border (--btn-color), so primary
   and secondary buttons are internally color-consistent. The two variants
   use related-but-distinct oranges (--accent and --accent-soft).

   No transforms or position changes on the button itself — the button's
   box is always static. All animation lives inside it. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  border-radius: 4px;
  font-size: clamp(13px, 1vw, 14px);
  font-weight: 600;
  letter-spacing: -0.14px;
  line-height: 1;
  background: rgba(255, 255, 255, 0.02);
  color: var(--fg);
  border: 1px solid var(--btn-color, var(--accent));
  overflow: hidden;
  cursor: pointer;
  box-shadow: 0 10px 32px -10px var(--btn-glow, rgba(255, 74, 8, 0.3));
  transition:
    box-shadow   .45s var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

/* Layer 1 — the colored blob behind the glass.
   Heavy filter: blur dissolves the blob into a soft cloud of color rather
   than a defined shape. The animation morphs both the position and the
   four-corner border-radius so the cloud continuously warps like a
   viscous liquid.

   NOTE: every `.btn:not([class*="bv-"])` scope below means "only when no
   variant class from buttons.css is applied." When the user picks a V01–V188
   variant via settings.js, the .bv-NN class wins and these defaults step
   aside — preventing pseudo-element conflicts between the two systems. */
.btn:not([class*="bv-"])::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 90%;
  height: 90%;
  border-radius: 50%;
  background: var(--btn-color, var(--accent));
  filter: blur(22px);
  transform: translate(-50%, -50%);
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity       .45s var(--ease-out),
    width         var(--dur-medium) var(--ease-out),
    height        var(--dur-medium) var(--ease-out),
    border-radius var(--dur-medium) var(--ease-out),
    filter        var(--dur-medium) var(--ease-out);
}

/* Layer 2 — frosted glass overlay. Blurs everything behind it (the blob
   + the page beyond the button). Subtle inset highlight + bottom shadow
   for a tactile glass edge. */
.btn:not([class*="bv-"])::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: rgba(255, 255, 255, 0.04);
  backdrop-filter: blur(14px) saturate(1.4);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  box-shadow:
    inset 0 1px 0  rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18);
  border-radius: inherit;
}

/* Layer 3 — the label, always above both. */
.btn-label {
  position: relative;
  z-index: 2;
}

/* Hover — blob fades in and drifts. */
.btn:not([class*="bv-"]):hover {
  box-shadow: 0 14px 40px -8px var(--btn-glow, rgba(255, 74, 8, 0.5));
}
.btn:not([class*="bv-"]):hover::before {
  opacity: 0.92;
  animation: btn-shape-drift 12s ease-in-out infinite;
}

/* Pressed — the blob expands to fill the whole button. Filter blur is
   reduced and border-radius locks back to the button's shape so the fill
   reads as "lit-up glass" rather than the diffuse cloud of the hover. */
.btn:not([class*="bv-"]):active::before {
  opacity: 1;
  width: 115%;
  height: 115%;
  border-radius: 4px;
  filter: blur(10px);
  animation: none;
  transform: translate(-50%, -50%);
}

/* Variants — each defines its own --btn-color (border + blob match) and
   --btn-glow (the outer drop shadow). Both are warm oranges so the
   primary/secondary pairing reads as a family. */
.btn-filled {
  --btn-color: var(--accent);                       /* #ff4a08 — saturated orange */
  --btn-glow:  rgba(255, 74, 8, 0.4);
}
.btn-outline {
  --btn-color: var(--accent-soft);                  /* #ff9966 — peachy orange */
  --btn-glow:  rgba(255, 153, 102, 0.28);
}

.btn-row { display: flex; gap: 20px; flex-wrap: wrap; }

/* ===== Button style swap (driven by settings.js) =====
   Picking a button style in settings.md applies the corresponding `.bv-NN`
   class from buttons.css to .btn-filled / .btn-outline. The production
   default rules above use `:not([class*="bv-"])` so they only apply when
   NO variant class is present — meaning the buttons.css variant takes
   over cleanly when a style is chosen. */

/* The drift animation morphs translate, scale, rotate, AND border-radius
   through irregular four-corner values so the blurred cloud continuously
   warps into different organic shapes — no two frames have the same
   silhouette. 12s for a viscous, slow-changing-liquid pace. */
@keyframes btn-shape-drift {
  0% {
    transform: translate(-50%, -50%) scale(1) rotate(0deg);
    border-radius: 50% 50% 50% 50%;
  }
  14% {
    transform: translate(-28%, -65%) scale(1.15) rotate(50deg);
    border-radius: 38% 62% 65% 35% / 55% 40% 60% 45%;
  }
  28% {
    transform: translate(-70%, -45%) scale(0.95) rotate(110deg);
    border-radius: 65% 35% 40% 60% / 40% 60% 55% 45%;
  }
  42% {
    transform: translate(-32%, -55%) scale(1.1) rotate(170deg);
    border-radius: 55% 45% 30% 70% / 65% 50% 50% 35%;
  }
  57% {
    transform: translate(-62%, -28%) scale(1.0) rotate(220deg);
    border-radius: 35% 65% 55% 45% / 45% 55% 45% 55%;
  }
  71% {
    transform: translate(-48%, -75%) scale(1.2) rotate(280deg);
    border-radius: 60% 40% 45% 55% / 35% 60% 40% 65%;
  }
  85% {
    transform: translate(-42%, -40%) scale(1.05) rotate(330deg);
    border-radius: 45% 55% 60% 40% / 50% 50% 60% 40%;
  }
  100% {
    transform: translate(-50%, -50%) scale(1) rotate(360deg);
    border-radius: 50% 50% 50% 50%;
  }
}

/* ===== Sections — full viewport ===== */
.section {
  min-height: 100vh;
  min-height: 100svh;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(80px, 8vh, 140px) clamp(24px, 4vw, 64px);
  position: relative;
}

/* ===== Hero ===== */
.hero {
  align-items: center;
  text-align: left;
  overflow: hidden;
}
.hero-decoration {
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(140vh, 120vw, 1400px);
  height: min(140vh, 120vw, 1400px);
  max-width: none;
  transform: translate(-50%, -50%);
  transform-origin: center center;
  /* Two animations: an 80s spin that never stops, and a one-shot 800ms
     fade from 0 → resting opacity right after first paint. `backwards`
     fill keeps the `from` (opacity 0) in effect before the animation
     starts. The resting opacity is theme-aware via --hero-decoration-opacity
     — duplicated as a literal in the keyframe `to` because animation
     end-state can't read a custom property at runtime in all browsers.
     We also apply the var to the static `opacity` property so the
     light-theme value is in effect once the animation completes. */
  opacity: var(--hero-decoration-opacity);
  pointer-events: none;
  z-index: 0;
  animation:
    spin-slow 80s linear infinite,
    hero-decoration-in .8s var(--ease-out) backwards;
  filter: drop-shadow(0 0 60px rgba(255, 74, 8, 0.1));
  transition: opacity var(--dur-base) var(--ease-out);
}
@keyframes spin-slow {
  to { transform: translate(-50%, -50%) rotate(360deg); }
}
@keyframes hero-decoration-in {
  from { opacity: 0; }
  to   { opacity: var(--hero-decoration-opacity); }
}
.hero > .hero-inner { position: relative; z-index: 1; }
.hero > .scroll-hint { z-index: 1; }
.hero-inner {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  gap: clamp(24px, 3vw, 56px);
  align-items: flex-start;
}
.hero-icon {
  width: clamp(48px, 5vw, 80px);
  height: clamp(48px, 5vw, 80px);
  flex-shrink: 0;
}
.hero-icon img {
  width: 100%;
  height: 100%;
  animation: gentle-spin 24s linear infinite;
}
@keyframes gentle-spin {
  to { transform: rotate(360deg); }
}
.hero-content {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  flex: 1;
  min-width: 0;
}
.hero-title {
  font-size: var(--size-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--fg);
}
.hero-title span { display: block; white-space: nowrap; }
.hero-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: clamp(24px, 4vw, 80px);
  flex-wrap: wrap;
}
.hero-sub {
  max-width: 420px;
  font-size: var(--size-body);
  font-weight: 400;
  color: var(--fg);
  line-height: 1.5;
  opacity: 0.85;
}

/* Scroll hint
   Custom fade-in (rather than [data-reveal]) so the translateX(-50%)
   centering isn't clobbered by data-reveal's translateY rule. Delayed
   580ms so it lands at the end of the hero entrance cadence. */
.scroll-hint {
  position: absolute;
  bottom: 32px;
  left: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0.5;
  color: var(--fg);
  transform: translateX(-50%);
  animation: scroll-hint-in var(--dur-slow) var(--ease-out) var(--dur-slow) both;
}
@keyframes scroll-hint-in {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(14px);
  }
  to {
    opacity: 0.5;
    transform: translateX(-50%) translateY(0);
  }
}
.scroll-hint-line {
  width: 1px;
  height: 40px;
  background: linear-gradient(to bottom, var(--scroll-tick-track), transparent);
  position: relative;
  overflow: hidden;
}
.scroll-hint-line::after {
  content: "";
  position: absolute;
  top: -50%;
  left: 0;
  width: 100%;
  height: 50%;
  background: linear-gradient(to bottom, transparent, var(--accent));
  animation: scroll-tick 2.4s var(--ease-out) infinite;
}
@keyframes scroll-tick {
  0% { transform: translateY(0); }
  100% { transform: translateY(300%); }
}

/* ===== Eyebrow ===== */
.eyebrow {
  font-size: var(--size-eyebrow);
  font-weight: 400;
  color: var(--fg);
  letter-spacing: 0;
  opacity: 0.7;
}
.eyebrow-center { text-align: center; align-self: center; }

/* ===== What we do + Trusted by (combined) ===== */
.what-trusted {
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}
.what-block {
  width: 100%;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  flex: 1 1 auto;
  min-height: 0;
}
.trusted-block {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  flex-shrink: 0;
}
.what {
  align-items: center;
  gap: var(--space-sm);
}
.what > .eyebrow,
.what-block > .eyebrow { width: 100%; max-width: 1200px; }
.what-stage {
  position: relative;
  width: 100%;
  max-width: 1200px;
  height: min(58vh, 640px);
  flex: 1 1 auto;
  overflow: hidden;
  border-radius: 4px;
}
.what-bg {
  position: absolute;
  width: clamp(720px, 70vw, 1100px);
  aspect-ratio: 710 / 946;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) rotate(-90deg);
  transform-origin: center center;
  opacity: 0.95;
}
.what-content {
  position: absolute;
  left: clamp(24px, 6%, 96px);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-width: min(748px, 70%);
  z-index: 2;
}
.what-title {
  font-size: var(--size-title-sm);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--fg);
}
.what-bottom {
  display: flex;
  align-items: center;
  gap: clamp(20px, 3vw, 40px);
  flex-wrap: wrap;
}
.what-sub {
  max-width: 394px;
  font-size: var(--size-body);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.4;
  color: var(--fg);
  opacity: 0.85;
}

/* ===== Trusted by ===== */
.trusted {
  align-items: stretch;
  justify-content: center;
  gap: var(--space-md);
}
.trusted .eyebrow,
.trusted-block .eyebrow { align-self: center; }
.marquee {
  position: relative;
  width: 100%;
  height: clamp(72px, 10vh, 110px);
  overflow: hidden;
  /* Edge fade-out via CSS mask, not a colored gradient overlay.
     This was previously done with two absolutely-positioned
     `.marquee-fade-left/right` divs filled with
     `linear-gradient(var(--marquee-fade), transparent)` — but that
     bakes in a SOLID color (the page bg) at the edges. The site has
     ambient warm `bg-glow` blobs behind every section, so the
     baked-in bg color never matched the real pixels behind the
     fade rectangle — a faint colored band was always visible at
     the page edges, especially noticeable on theme-toggle or as
     the user scrolls past a glow.
     A mask cuts TRUE transparency into the marquee itself, so the
     ambient pixels show through perfectly regardless of theme,
     glow position, or backdrop. `--marquee-fade-width` controls
     how far inset the ramp starts. */
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 var(--marquee-fade-width),
    #000 calc(100% - var(--marquee-fade-width)),
    transparent 100%
  );
          mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 var(--marquee-fade-width),
    #000 calc(100% - var(--marquee-fade-width)),
    transparent 100%
  );
}
.marquee-track {
  display: flex;
  /* No `gap` on the track itself — each group carries its own trailing
     gap via `padding-right`, so the keyframe can land on an exact
     `-33.333%` seam regardless of viewport width.
     `--marquee-speed` is settable via `trusted_by.speed` in settings.md
     (applied by `applyTrustedBy` as a `<style>` block). Fallback `40s`
     matches the historical hard-coded value. */
  width: max-content;
  height: 100%;
  align-items: center;
  animation: scroll var(--marquee-speed, 40s) linear infinite;
}
.marquee-group {
  display: flex;
  align-items: center;
  /* Inter-logo gap AND trailing tile gap come from the same token so
     the marquee tessellates evenly. Override via `trusted_by.gap` in
     settings.md; fallback matches the previous hard-coded value. */
  gap: var(--marquee-gap, clamp(32px, 4vw, 64px));
  /* Trailing gap is part of the group's width so each group is a
     self-contained "tile" that tessellates seamlessly when repeated. */
  padding-right: var(--marquee-gap, clamp(32px, 4vw, 64px));
  flex-shrink: 0;
  height: 100%;
}
.logo-slot {
  flex-shrink: 0;
  width: clamp(140px, 14vw, 220px);
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.logo-slot img {
  /* SVG logos ship with `fill="#E8E8E8"` (near-white) so they read
     correctly on the dark theme at the rest opacity. Light theme
     gets a `filter: brightness(0)` flip so they become black —
     same pattern the nav-logo uses.
     Per-logo size: `--logo-scale` is set inline on the parent
     `.logo-slot` by `applyTrustedBy` (default 1). Multiplies the
     base `max-height` of 70% so a logo at scale 1.2 reads 20%
     bigger; 0.8 reads 20% smaller. Useful for normalising
     visually heavy / light brand marks against one another. */
  max-width: 100%;
  max-height: calc(70% * var(--logo-scale, 1));
  object-fit: contain;
  opacity: 0.55;
  transition:
    opacity var(--dur-base) ease,
    filter var(--dur-base) ease,
    transform var(--dur-base) var(--ease-out);
}
.logo-slot:hover img {
  opacity: 1;
  transform: scale(1.05);
}
[data-theme="light"] .logo-slot img {
  filter: brightness(0);
}
@keyframes scroll {
  /* Three groups, each width = (6 logos + 6 gaps including padding-right).
     -33.333% of the track is exactly one group, so when this loops the
     second/third groups slide into the position the first/second held —
     a perfectly seamless infinite scroll. */
  0%   { transform: translateX(0); }
  100% { transform: translateX(-33.3333%); }
}

/* ===== How we work =====
   A warm, grid-backed process band. Kept light/warm in BOTH themes
   (intentional brand band — matches the reference). All the knobs
   below default to these values but are overridden at runtime by
   `applyHowWeWork` in settings.js from the `how_we_work` block in
   settings.md. The card-glass spec is the Figma "Shared Map Card":
   rgba(217,217,217,.20) + 4px blur + 10px radius.
   CANDIDATE TOKENS — these `--hww-*` vars are section-scoped for now;
   promote to a shared `--card-glass-*` set if reused elsewhere. */
.howwework {
  --hww-section-bg: #f7f2eb;
  --hww-grid-color: #cfcfcf;
  --hww-grid-width: 1px;
  --hww-grid-size: 72px;
  --hww-blob-blur: 80px;
  --hww-blob-speed: 3;            /* unitless multiplier, read by script.js */
  --hww-card-rgb: 217, 217, 217;
  --hww-card-alpha: 0.20;
  --hww-card-blur: 4px;
  --hww-card-fg: #1c1c1c;
  --hww-card-hover-bg: rgba(20, 20, 20, 0.82);
  --hww-card-hover-fg: #ebebeb;
  --hww-text: #1c1c1c;                       /* eyebrow + headline + intro */
  --hww-illu-dot: rgba(28, 28, 28, 0.09);    /* dot pattern in the illustration frame */

  background: var(--hww-section-bg);
  overflow: hidden;
  isolation: isolate;
  gap: clamp(40px, 6vh, 72px);
  justify-content: center;
}

/* Dark-theme inversion. The light/warm values above are the default
   (and what `applyHowWeWork` tunes from settings.md, scoped to
   non-dark themes). In dark theme the band flips: near-black warm bg,
   faint light glass cards with light text + light SVG strokes (via
   currentColor), a subtle light grid + dot pattern, and the hover
   state inverts to a light card with dark text. The orange blobs work
   in both themes, so they're untouched. */
[data-theme="dark"] .howwework {
  --hww-section-bg: #17140f;
  --hww-grid-color: rgba(235, 235, 235, 0.06);
  --hww-card-rgb: 255, 255, 255;
  --hww-card-alpha: 0.05;
  --hww-card-fg: #ebebeb;
  --hww-card-hover-bg: rgba(243, 238, 230, 0.92);
  --hww-card-hover-fg: #1c1c1c;
  --hww-text: #ebebeb;
  --hww-illu-dot: rgba(235, 235, 235, 0.08);
}
/* Background layers (blobs + grid) sit behind the content. */
.hww-bg {
  position: absolute;
  inset: -10%;
  z-index: 0;
  pointer-events: none;
}
.hww-bg-blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(var(--hww-blob-blur));
  opacity: 0.78;
  will-change: transform;
}
.hww-grid {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--hww-grid-color) var(--hww-grid-width), transparent var(--hww-grid-width)),
    linear-gradient(to bottom, var(--hww-grid-color) var(--hww-grid-width), transparent var(--hww-grid-width));
  background-size: var(--hww-grid-size) var(--hww-grid-size);
}
.hww-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(32px, 5vh, 64px);
}
/* Header: title left, intro + CTAs right. */
.hww-head { display: flex; flex-direction: column; gap: clamp(16px, 2.5vh, 28px); }
.hww-head .eyebrow { color: var(--hww-text); }
.hww-head-row {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: clamp(24px, 4vw, 64px);
  align-items: end;
}
.hww-title {
  font-size: var(--size-title-md);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--hww-text);
}
.hww-title span { display: block; }
.hww-head-aside { display: flex; flex-direction: column; gap: clamp(18px, 2.5vh, 28px); }
.hww-intro {
  font-size: var(--size-body);
  line-height: 1.5;
  color: var(--hww-text);
  max-width: 46ch;
}
/* Cards row — 5 across on desktop. */
.hww-cards {
  display: grid;
  /* `minmax(0, 1fr)` (not bare `1fr`, which is `minmax(auto, 1fr)`) so
     the columns can shrink below each card's min-content. Without this,
     the fixed-width description (below) forces a ~247px per-card
     minimum and the 5-up grid overflows its 1280px container, clipping
     the rightmost card on viewports under ~1350px. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}
.hww-card {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--hww-card-fg);
  transition: color var(--dur-medium) var(--ease-out);
}
.hww-panel {
  position: relative;
  background: rgba(var(--hww-card-rgb), var(--hww-card-alpha));
  backdrop-filter: blur(var(--hww-card-blur)) saturate(120%);
  -webkit-backdrop-filter: blur(var(--hww-card-blur)) saturate(120%);
  border-radius: 10px;
  transition: background var(--dur-medium) var(--ease-out);
}
.hww-panel-top {
  height: 306px;
  padding: 27px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.hww-panel-bottom {
  height: 102px;
  padding: 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.hww-card-title {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.15;
  text-align: left;
  color: inherit;
}
.hww-card-illu {
  flex: 1;
  min-height: 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 7px 15px;
  border-radius: 6px;
  background-image: radial-gradient(circle, var(--hww-illu-dot) 0.5px, transparent 0.6px);
  background-size: 13px 13px;
}
.hww-card-illu-svg {
  width: 66%;
  max-width: 152px;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hww-card-illu-svg svg {
  width: 100%;
  height: auto;
  max-height: 100%;
  color: currentColor;
  display: block;
}
/* Recolor the illustration to follow the card's text color so it
   always contrasts with the card — including the hover inversion and
   the per-theme flip — exactly like the dots/brackets/text. Uploaded
   SVGs (Figma exports) hard-code hex colors as presentation
   attributes; mapping every explicitly-colored fill/stroke to
   `currentColor` makes them theme- + hover-aware without editing the
   SVG. `none` is preserved so outline shapes don't fill in. The
   `color` transition on `.hww-card` drives the fade; an explicit
   fill/stroke transition keeps multi-element icons in lockstep. */
.hww-card-illu-svg svg [fill]:not([fill="none"]),
.hww-card-illu-svg svg[fill]:not([fill="none"]) { fill: currentColor; }
.hww-card-illu-svg svg [stroke]:not([stroke="none"]),
.hww-card-illu-svg svg[stroke]:not([stroke="none"]) { stroke: currentColor; }
.hww-card-illu-svg svg * {
  transition: fill var(--dur-medium) var(--ease-out),
              stroke var(--dur-medium) var(--ease-out);
}
.hww-card-desc {
  font-size: 12px;
  line-height: 1.4;
  /* Fluid (was a fixed 150px) so it doesn't inflate the card's
     min-content and overflow the grid. At full card width it still
     resolves to ~150px; it shrinks gracefully on narrower cards. */
  flex: 1;
  min-width: 0;
  color: inherit;
}
.hww-card-dots {
  display: grid;
  grid-template-columns: repeat(3, 3.5px);
  grid-template-rows: repeat(3, 3.5px);
  gap: 13px;
  flex-shrink: 0;
}
.hww-card-dots span {
  width: 3.5px;
  height: 3.5px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
}
/* Corner brackets — only on the illustration frame (per spec). */
.hww-bracket {
  position: absolute;
  width: 10px;
  height: 10px;
  pointer-events: none;
  opacity: 0.65;
}
.hww-bracket-tl { top: 5px; left: 5px;     border-left: 1px solid currentColor; border-top: 1px solid currentColor; }
.hww-bracket-tr { top: 5px; right: 5px;    border-right: 1px solid currentColor; border-top: 1px solid currentColor; }
.hww-bracket-bl { bottom: 5px; left: 5px;  border-left: 1px solid currentColor; border-bottom: 1px solid currentColor; }
.hww-bracket-br { bottom: 5px; right: 5px; border-right: 1px solid currentColor; border-bottom: 1px solid currentColor; }
/* Hover: both panels invert together. */
.hww-card:hover { color: var(--hww-card-hover-fg); }
.hww-card:hover .hww-panel { background: var(--hww-card-hover-bg); }

/* Tablet + mobile: cards become a horizontal scroll-snap rail so the
   five-phase journey reads as a sequence instead of a tall stack. */
/* Cards switch to the horizontal scroll-snap rail below 1100px. The
   `.hww-inner` container caps at 1280px, so the 5-up grid only has room
   to look right near that cap; under ~1100px the columns would get
   uncomfortably thin, so the same rail used on mobile takes over here
   (identical values — the mobile behavior is unchanged, it just starts
   earlier). The header stays two-column down to 900px. */
@media (max-width: 1100px) {
  .hww-cards {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: clamp(260px, 78vw, 320px);
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-left: clamp(24px, 4vw, 64px);
    margin: 0 calc(-1 * clamp(24px, 4vw, 64px));
    padding: 4px clamp(24px, 4vw, 64px) 12px;
    -webkit-overflow-scrolling: touch;
  }
  .hww-card { scroll-snap-align: start; }
}
@media (max-width: 900px) {
  .hww-head-row { grid-template-columns: 1fr; align-items: start; }
}
@media (prefers-reduced-motion: reduce) {
  .hww-bg-blob { will-change: auto; }
}

/* ===== Selected work ===== */
.work {
  align-items: center;
  gap: var(--space-md);
  justify-content: center;
}
.work-head {
  width: 100%;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  align-items: flex-start;
}
.work-title {
  font-size: var(--size-title-md);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--fg);
}
.work-title span { display: block; }
.case-list {
  width: 100%;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
}
.case-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: clamp(40px, 12vw, 200px);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--fg);
  cursor: pointer;
  transition: background var(--dur-medium) var(--ease-out);
}
/* Higher-specificity override — the global [data-reveal] rule (defined later
   in this file) declares its own `transition` shorthand which has equal
   specificity to `.case-item` and would clobber the hover transitions above
   if not re-declared here on `.case-item[data-reveal]` (specificity 0,2,0). */
.case-item[data-reveal] {
  transition:
    background var(--dur-medium) var(--ease-out),
    opacity    var(--dur-slow)  var(--ease-out),
    transform  var(--dur-slow)  var(--ease-out);
}
.case-item::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, rgba(255, 74, 8, 0.04), transparent 60%);
  opacity: 0;
  transition: opacity var(--dur-medium) var(--ease-out);
  pointer-events: none;
}
.case-item:first-child { border-top: 1px solid var(--hairline); }
/* Hover state — deliberately NO geometry/layout/transform changes on the
   .case-item itself. Both padding-inflate and transform: scale altered (or
   appeared to alter) the wrap point inside .case-headline between states.
   All hover feedback now comes from purely visual additions: a vertical
   orange accent bar on the left edge gives the "indent" feel without
   touching layout, the wash fades in, the headline brightens, and the
   arrow slides in from the left. */
.case-item::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 3px;
  height: 0;
  background: var(--accent);
  border-radius: 2px;
  transform: translateY(-50%);
  transition: height var(--dur-medium) var(--ease-out);
  pointer-events: none;
}
.case-item:hover::after { height: 38%; }
.case-item:hover::before { opacity: 1; }
.case-item:hover .case-headline { color: var(--case-headline-hover); }
.case-item:hover .case-arrow {
  opacity: 1;
  transform: translateX(0);
  color: var(--accent);
}
.case-tag {
  position: relative;
  flex-shrink: 0;
  font-size: var(--size-body);
  font-weight: 400;
  white-space: nowrap;
  opacity: 0.6;
  padding-left: 0;
  transition:
    padding-left var(--dur-medium) var(--ease-out),
    opacity      var(--dur-medium) var(--ease-out);
}
.case-item:hover .case-tag {
  padding-left: 24px;
  opacity: 1;
}
.case-body {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 720px;
}
.case-kicker {
  font-size: var(--size-body);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.2;
  opacity: 0.7;
}
.case-headline {
  font-size: var(--size-title-sm);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  transition: color var(--dur-medium) var(--ease-out);
}
.case-arrow {
  position: relative;
  flex-shrink: 0;
  font-size: clamp(20px, 2.2vw, 32px);
  opacity: 0;
  transform: translateX(-16px);
  transition:
    opacity   var(--dur-medium) var(--ease-out),
    transform var(--dur-medium) var(--ease-out),
    color     var(--dur-medium) var(--ease-out);
}

/* "Coming soon" badge — small orange-outline pill. Shown only on cases
   flagged `data-coming-soon="true"`. Desktop: replaces the arrow on
   hover (the arrow is suppressed for coming-soon rows). Mobile: pinned
   to the top-right of the row, level with the project tag (see the
   responsive blocks below). */
.case-badge {
  display: none;
  flex-shrink: 0;
  align-items: center;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: var(--accent);
  padding: 6px 11px;
  border: 1px solid var(--accent);
  border-radius: 999px;
}
/* Desktop: no arrow for coming-soon rows; the badge fades into its slot
   on hover instead. */
@media (min-width: 701px) {
  .case-item[data-coming-soon="true"] .case-arrow { display: none; }
  .case-item[data-coming-soon="true"]:hover .case-badge { display: inline-flex; }
}

/* ===== Case tooltip ===== */
.case-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  width: 400px;
  height: 700px;
  pointer-events: none;
  z-index: 9000;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.9);
  transition: opacity var(--dur-base) var(--ease-out), scale .45s var(--ease-spring);
  will-change: transform, opacity;
  border-radius: 12px;
  /* `overflow: visible` so the "coming soon" badge can sit ABOVE the
     image (outside it). The image keeps its own rounded corners. */
  overflow: visible;
  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(255, 255, 255, 0.06),
    0 0 120px rgba(255, 74, 8, 0.12);
}
.case-tooltip img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 12px;
  transition: opacity var(--dur-fast) ease;
}
.case-tooltip.is-visible {
  opacity: 1;
}
/* Badge floating just above the tooltip image — shown only when the
   hovered case is `coming_soon` (script.js toggles `.is-coming-soon`).
   Filled accent pill (dark text) so it reads over any image. */
.case-tooltip-badge {
  position: absolute;
  top: -16px;
  left: 50%;
  transform: translateX(-50%);
  display: none;
  align-items: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: #1c1c1c;
  background: var(--accent);
  padding: 8px 14px;
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(255, 74, 8, 0.4);
}
.case-tooltip.is-coming-soon .case-tooltip-badge { display: inline-flex; }
@media (max-width: 900px), (pointer: coarse) {
  .case-tooltip { display: none; }
}

/* ===== Contact section =====
   Two flavors share these base styles:
     • `.contact-teaser` — used on the homepage (#contact). Just the
       heading + intro paragraph + a CTA leading to contact.html. No
       grid; content is centered horizontally on the page.
     • `.contact-page` — used on the standalone contact.html. Heading
       block at the top followed by the 3-column grid (image + two
       cards). Stacks to one column on tablet/mobile. The image is a
       stand-in (picsum) — swap for a real Ruta photo when you have one. */
.contact {
  align-items: stretch;
  justify-content: center;
  gap: var(--space-lg);
}

/* Homepage teaser: center the head, no grid below it. Mirrors the
   old `.cta` block visually so the page still closes on a heading +
   intro + CTA, but the rich contact details now live on
   contact.html. */
.contact-teaser {
  align-items: center;
  text-align: center;
}
.contact-teaser .contact-head {
  align-items: center;
}
.contact-teaser .contact-intro { max-width: 640px; }
.contact-teaser .btn-row {
  margin-top: clamp(8px, 1.6vh, 16px);
  justify-content: center;
}

/* Standalone contact page: align everything left and pin the
   heading to a comfortable distance from the top of the viewport
   (the nav-pill is fixed). The section still uses `100svh` from
   `.section`, but on this page content can overflow naturally
   since we want the cards visible without scrolling on desktop. */
.contact-page {
  align-items: stretch;
  justify-content: flex-start;
  padding-top: clamp(120px, 18vh, 200px);
  padding-bottom: var(--space-section-y);
  min-height: 100svh;
  height: auto;
}

/* The contact-page heading matches the home page's `.contact-title`
   exactly — same `var(--size-title-md)` scale, same line-height,
   same letter-spacing — so navigating from the home teaser to the
   contact page doesn't cause the heading to jump. Only the
   alignment and overall page rhythm differ (left-aligned vs.
   centered). */
.contact-page .contact-head { gap: var(--space-sm); }
/* Match the rest of the site's 1200px content rail. Without this
   the section's `align-items: stretch` lets the head + grid span
   the full viewport (minus padding), which feels wide and breaks
   the visual rhythm with the home page. Both children share the
   same left edge so the heading lines up with the image below. */
.contact-page .contact-head,
.contact-page .contact-grid {
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
}

/* contact2.html variant — designed to feel like a direct
   continuation of the home page. Same `--size-title-md` heading +
   `--size-body` intro as the homepage teaser (so the type scale
   doesn't jump on navigation), same horizontal padding via
   `.section`, but with the cards grid below so contact details
   are still on the page. Head is centered; grid below sits at the
   full content width like on contact.html v1. */
.contact-page-v2 {
  align-items: center;
  justify-content: flex-start;
  padding-top: clamp(120px, 18vh, 200px);
  padding-bottom: var(--space-section-y);
  min-height: 100svh;
  height: auto;
  text-align: center;
}
.contact-page-v2 .contact-head {
  align-items: center;
  text-align: center;
}
.contact-page-v2 .contact-intro {
  max-width: 640px;
  /* Inline link styling inside the centered paragraph reads
     better with a touch more breathing room. */
  margin-inline: auto;
}
/* Grid below the centered head: reset text-align so cards read
   naturally; the grid itself fills the section width as on v1. */
.contact-page-v2 .contact-grid { text-align: left; }
/* Same 1200px rail as `.contact-page` so both contact variants sit
   in the same horizontal container as the rest of the site. */
.contact-page-v2 .contact-head,
.contact-page-v2 .contact-grid {
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
}
.contact-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  max-width: 960px;
}
.contact-title {
  font-size: var(--size-title-md);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--fg);
}
.contact-title span { display: block; }
.contact-intro {
  font-size: var(--size-body);
  font-weight: 400;
  line-height: 1.55;
  color: var(--fg);
  opacity: 0.78;
  max-width: 640px;
}
.contact-link {
  color: var(--accent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.contact-link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.contact-grid {
  display: grid;
  /* Two-column layout: image on the left takes most of the width,
     lead card sits to its right. (Was a 3-column with a separate
     "General enquiries" card; that column was removed.) */
  grid-template-columns: 1.4fr 1fr;
  gap: clamp(24px, 3vw, 56px);
  align-items: start;
  width: 100%;
}

.contact-image {
  margin: 0;
  border-radius: 12px;
  overflow: hidden;
  aspect-ratio: 5 / 4;
  background: var(--surface-1);
  border: 1px solid var(--hairline);
}
.contact-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Subtle filter that nudges the photo toward the warm palette. */
  filter: saturate(0.92);
  transition: filter var(--dur-base) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
.contact-image:hover img {
  filter: saturate(1);
  transform: scale(1.02);
}

/* Warm mesh-gradient panel — replaces the contact photo. Several
   radial "blooms" in the brand oranges layered over a diagonal base
   give a rich, premium gradient. Fills the existing 5/4 (responsive)
   image frame; pure CSS, static (no JS, nothing to animate). Transparent
   stops use rgba(samecolor, 0) (not the `transparent` keyword) so the
   fades stay warm instead of going muddy/grey. */
.contact-gradient {
  background:
    radial-gradient(135% 135% at 12% 16%, #ff8a3d 0%, rgba(255, 138, 61, 0) 48%),
    radial-gradient(120% 120% at 86% 12%, #ff9966 0%, rgba(255, 153, 102, 0) 46%),
    radial-gradient(145% 145% at 84% 90%, #ff4a08 0%, rgba(255, 74, 8, 0) 56%),
    radial-gradient(125% 125% at 16% 94%, #ffb37a 0%, rgba(255, 179, 122, 0) 52%),
    linear-gradient(135deg, #ff5a14 0%, #ff8a3d 100%);
}
[data-theme="dark"] .contact-gradient {
  background:
    radial-gradient(135% 135% at 12% 16%, #ff7a3c 0%, rgba(255, 122, 60, 0) 48%),
    radial-gradient(120% 120% at 86% 12%, #ff9966 0%, rgba(255, 153, 102, 0) 44%),
    radial-gradient(150% 150% at 84% 92%, #c2370a 0%, rgba(194, 55, 10, 0) 58%),
    radial-gradient(125% 125% at 18% 96%, #ff6a28 0%, rgba(255, 106, 40, 0) 54%),
    linear-gradient(135deg, #b8350a 0%, #ff6a28 55%, #ff9a52 100%);
}

/* ============================================================
   Contact — editorial redesign (.contact-stage, used by contact.html)
   Two text columns over a soft warm gradient glow; no boxed cards.
   The boxed image+card layout lives in contact3.html / contact2.html
   (.contact-page / -v2). Builds on `.section` (full-height + centered).
   ============================================================ */
.contact-stage { overflow: hidden; isolation: isolate; }

/* Ambient warm orb — soft brand-orange glow bleeding off the right.
   Decorative warmth instead of a contained rectangle. */
.cstage-glow {
  position: absolute;
  z-index: -1;
  width: clamp(460px, 60vw, 860px);
  aspect-ratio: 1 / 1;
  right: -24%;                  /* dense centre sits off the right edge */
  top: 50%;
  transform: translateY(-50%);
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, rgba(255, 74, 8, 0.42), rgba(255, 74, 8, 0) 62%),
    radial-gradient(circle at 38% 42%, rgba(255, 153, 102, 0.30), rgba(255, 153, 102, 0) 58%);
  filter: blur(40px);
  opacity: 0.85;
  pointer-events: none;
}
[data-theme="light"] .cstage-glow { opacity: 0.5; }

.cstage-grid {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(40px, 6vw, 96px);
  align-items: start;
}
.cstage-statement { display: flex; flex-direction: column; gap: clamp(20px, 3vh, 36px); }
.cstage-eyebrow {
  font-size: var(--size-eyebrow);
  font-weight: 400;
  color: var(--fg);
  opacity: 0.6;
}
.cstage-title {
  font-size: var(--size-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.02;
  color: var(--fg);
}
.cstage-title span { display: block; }
.cstage-intro {
  font-size: var(--size-body);
  font-weight: 400;
  line-height: 1.55;
  color: var(--fg);
  opacity: 0.82;
  max-width: 46ch;
}

/* Right column: warm lead line, a big email link, attribution, CTA. */
.cstage-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(8px, 1.4vh, 14px);
  padding-top: clamp(6px, 4vh, 44px);   /* nudge down to balance the tall heading */
}
.cstage-kicker {
  font-size: var(--size-title-sm);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--fg);
  margin-bottom: 6px;
}
/* Email — the focal action. Animated underline grows on hover. */
.cstage-email {
  font-size: clamp(20px, 2.4vw, 34px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--fg);
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 2px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: color var(--dur-base) var(--ease-out), background-size var(--dur-base) var(--ease-out);
}
.cstage-email:hover { color: var(--accent); background-size: 100% 2px; }
.cstage-name {
  font-size: var(--size-body);
  font-weight: 500;
  color: var(--fg);
  opacity: 0.6;
}
.cstage-phone {
  font-size: var(--size-body);
  color: var(--fg);
  opacity: 0.6;
  transition: color var(--dur-fast) ease, opacity var(--dur-fast) ease;
}
.cstage-phone:hover { color: var(--accent); opacity: 1; }
.cstage-actions .contact-schedule { margin-top: clamp(18px, 3vh, 30px); }

@media (max-width: 900px) {
  .cstage-grid { grid-template-columns: 1fr; gap: clamp(36px, 8vh, 64px); }
  .cstage-actions { padding-top: 0; }
  .cstage-glow { right: -30%; top: 36%; opacity: 0.7; }
}

.contact-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 4px;
}
.contact-card-title {
  font-size: var(--size-title-sm);
  font-weight: 700;
  letter-spacing: -0.018em;
  line-height: 1.15;
  color: var(--fg);
}
.contact-card-name {
  font-size: var(--size-body);
  font-weight: 500;
  color: var(--fg);
  opacity: 0.65;
}

.contact-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 6px;
}
.contact-list li {
  display: flex;
  align-items: center;
  gap: 12px;
}
.contact-icon {
  display: inline-flex;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--accent);
}
.contact-icon svg { width: 100%; height: 100%; }
.contact-list a {
  font-size: var(--size-body);
  color: var(--fg);
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 1px;
  transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.contact-list a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* "Schedule a call" CTA — uses the site's standard primary button
   (`.btn-filled` → bv-270 magnetic, applied by applyButtons). Only its
   placement inside the card is set here; the look comes from the
   shared button variant so it matches the hero/nav CTAs. */
.contact-schedule {
  align-self: flex-start;
  margin-top: 6px;
}

/* The `.effect-picker` widget used to live in the hero — it's now on
   components.html only. Its styles moved to buttons.css. Production mode
   is controlled exclusively by `hero.shader_mode` in settings.md. */

/* ===== Site footer =====
   Quiet two-column strip that sits at the bottom of every page, below
   the last section. Theme-aware via `var(--fg)` with a `color-mix`
   for the muted text, and a hairline border-top to separate it from
   whatever section ends above. The year inside `<span data-year>` is
   auto-populated by the IIFE in script.js — no annual edits needed. */
.site-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: var(--space-sm) clamp(24px, 4vw, 64px);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: color-mix(in srgb, var(--fg) 38%, transparent);
  background: var(--bg);
  transition: color var(--dur-base) var(--ease-out);
}
.site-footer a {
  color: inherit;
  text-decoration: none;
  transition: color var(--dur-fast) ease;
}
.site-footer a:hover {
  color: var(--fg);
}
.site-footer-copy   { display: inline-flex; align-items: center; gap: 6px; }
.site-footer-meta   { letter-spacing: 0.02em; }

@media (max-width: 480px) {
  .site-footer { font-size: 11px; padding: 24px 20px; }
  .site-footer-meta { display: none; }
}

/* ===== Hero title shader overlay ===== */
.hero-title-wrap {
  position: relative;
  display: block;
  width: 100%;
}
.hero-title-canvas {
  /* Extended by 48px on every edge beyond the .hero-title-wrap so the
     shader has buffer room to render displaced glyphs (especially in
     Magnetic mode, which pulls letters toward the cursor — without
     this padding, sampling past the texture edge returned clamped
     pixels and the letters appeared cut off). The JS-side
     `TEXT_PADDING` constant in script.js MUST stay in sync with the
     48px figure here. */
  position: absolute;
  top: -48px;
  left: -48px;
  width: calc(100% + 96px);
  height: calc(100% + 96px);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
.hero-title {
  transition: opacity var(--dur-base) var(--ease-out);
}
.hero-title-wrap.is-shader-ready .hero-title {
  opacity: 0;
}
.hero-title-wrap.is-shader-ready .hero-title-canvas {
  opacity: 1;
}

/* ===== Custom cursor ===== */
.cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid var(--cursor-border);
  pointer-events: none;
  z-index: 9999;
  /* `difference` blend looks great on dark theme (white border inverts
     to dark on light surfaces). On light theme we want the cursor to
     be brand-orange instead — `normal` blend lets the accent color
     render literally. Toggled in the `[data-theme="light"] .cursor`
     override below. */
  mix-blend-mode: difference;
  transform: translate(-50%, -50%);
  transition:
    width var(--dur-base) var(--ease-spring),
    height var(--dur-base) var(--ease-spring),
    background var(--dur-fast) ease,
    border-color var(--dur-fast) ease,
    border-width var(--dur-fast) ease;
  display: none;
}
[data-theme="light"] .cursor {
  mix-blend-mode: normal;
  border-color: var(--accent);
}
[data-theme="light"] .cursor.cursor-grow {
  background: rgba(255, 74, 8, 0.10);
  border-color: var(--accent);
}
.cursor.cursor-grow {
  width: 56px;
  height: 56px;
  background: rgba(255, 255, 255, 0.08);
  border-width: 1px;
}

/* ===== Reveal animations =====
   Tighter, more confident entrance. Each element fades + rises 14px over
   600ms with the standard ease-out. Elements that should *not* end at
   full opacity (the decorative spinner at 0.18, scroll-hint at 0.5) set
   `--reveal-opacity` so the end state matches their resting opacity. */
[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity   var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  will-change: opacity, transform;
}
[data-reveal-scale] {
  transform: translateY(14px) scale(0.96);
}
[data-reveal].is-visible {
  opacity: var(--reveal-opacity, 1);
  transform: translateY(0) scale(1);
}

/* ===== Responsive ===== */
@media (min-width: 1024px) {
  .cursor { display: block; }
}

@media (max-width: 900px) {
  .nav-tag { display: none; }
  .nav-links { gap: 14px; }
  .nav-links a { font-size: 14px; }

  .hero-inner { flex-direction: column; gap: 28px; }

  .what-trusted { min-height: auto; }
  .what-stage { height: auto; min-height: 60vh; }
  .what-bg { width: 100%; height: 100%; aspect-ratio: auto; transform: translate(-50%, -50%) rotate(-90deg) scale(1.1); }
  .what-content {
    position: relative;
    left: auto;
    top: auto;
    transform: none;
    padding: 32px 24px;
    max-width: 100%;
  }
  /* Stacked order: subtitle above the buttons (DOM order is
     buttons-then-sub for the desktop side-by-side layout). */
  .what-bottom { flex-direction: column; align-items: flex-start; }
  .what-bottom .what-sub { order: -1; }

  .case-item { flex-direction: column; align-items: flex-start; gap: 12px; }
  .case-arrow { display: none; }
  /* Coming-soon badge pinned to the top-right of the row, level with
     the project tag (the row is a column on mobile; `top` matches the
     row's padding-top so the badge aligns with the tag). */
  .case-item[data-coming-soon="true"] .case-badge {
    display: inline-flex;
    position: absolute;
    top: var(--space-sm);
    right: 0;
  }

  .cta { padding-bottom: 100px; }
  .cta .btn { margin-left: 0; }

  /* Contact: drop to a 2-up grid on tablet, image first row,
     cards on second row sharing the width. */
  .contact-grid {
    grid-template-columns: 1fr 1fr;
    gap: 32px;
  }
  .contact-image {
    grid-column: 1 / -1;
    aspect-ratio: 16 / 9;
  }

  .scroll-hint { display: none; }
}

/* ============================================================
   MOBILE (≤ 700px) — hamburger menu + tighter section spacing
   + display-type scale-down so the hero copy fits.
   ============================================================ */
@media (max-width: 700px) {
  /* ---- Full-screen mobile menu --------------------------- */
  /* The overlay lives at the BODY level (sibling of `.site-header`,
     see HTML), not inside `.nav-pill`. This is intentional and
     critical: with the overlay inside the pill, the pill's intrinsic
     background always paints below it in the same stacking context,
     so we could never get the pill chrome to stay above the overlay.
     With the overlay outside the pill, body-level z-index puts the
     pill (z 100 via .site-header) above the overlay (z 90).

     The pill's in-pill `.nav-links` is hidden on mobile (rule
     below) because the overlay holds the mobile menu. Body scroll
     is locked by `body.menu-open` (toggled by the inline IIFE on
     each page). */

  /* Hero: stack on mobile with the subtitle above the buttons (DOM
     order is buttons-then-sub for the desktop side-by-side layout, so
     order: -1 lifts the sub above the CTAs → title, subtitle, buttons). */
  .hero-bottom { flex-direction: column; align-items: flex-start; gap: 26px; }
  .hero-bottom .hero-sub { order: -1; }
  /* Balance the vertical rhythm of the stacked hero. `--space-md`
     (~42px) between the title and the subtitle read as a detached gap;
     tighten it so title+subtitle group as a unit, with even spacing to
     the icon above and the buttons below (≈28 / 24 / 26). */
  .hero-content { gap: 24px; }

  /* Hide the in-pill nav-links on mobile — the overlay takes over. */
  .nav-pill > .nav-links { display: none; }
  /* The overlay container — full viewport coverage. `z-index: 90`
     sits BELOW the site-header's stacking context (z 100), so the
     pill (logo + close X + theme toggle) always renders on top of
     the overlay without any painting-order tricks. */
  .nav-overlay {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: clamp(96px, 16vh, 144px) clamp(24px, 6vw, 48px) clamp(40px, 8vh, 80px);
    background: var(--bg);
    backdrop-filter: blur(28px) saturate(140%);
    -webkit-backdrop-filter: blur(28px) saturate(140%);
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition:
      opacity var(--dur-base) var(--ease-out),
      visibility 0s linear var(--dur-base);
  }

  /* Faint background flourish — same warm glow tokens the site uses
     under the hero — so the overlay feels like a part of Ruta, not a
     blank curtain. */
  .nav-overlay::before {
    content: '';
    position: absolute;
    inset: 0;
    background:
      radial-gradient(60% 50% at 50% 10%, var(--bg-glow-warm-1), transparent 70%),
      radial-gradient(70% 60% at 50% 100%, var(--bg-glow-warm-2), transparent 75%);
    opacity: 0.6;
    pointer-events: none;
    z-index: -1;
  }

  /* The inner <nav> — vertical column of links, centered. */
  .nav-overlay .nav-links {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 100%;
    max-width: 480px;
  }

  /* Links — display-scale typography, full-width row with an arrow
     accent, hairline divider between rows. */
  .nav-overlay .nav-links a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: clamp(14px, 2.2vh, 22px) 4px;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    border-radius: 0;
    background: transparent;
    color: var(--fg);
    font-size: clamp(32px, 8vw, 52px);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.02;
    text-decoration: none;
    transform: translateY(16px);
    opacity: 0;
    transition:
      color var(--dur-fast) var(--ease-out),
      transform var(--dur-medium) var(--ease-out),
      opacity .45s var(--ease-out);
  }
  .nav-overlay .nav-links a:first-child {
    border-top: 1px solid var(--hairline);
  }
  .nav-overlay .nav-links a::after {
    content: '→';
    font-size: 0.5em;
    font-weight: 500;
    color: var(--fg);
    opacity: 0.35;
    transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) ease, color var(--dur-fast) ease;
  }
  .nav-overlay .nav-links a:hover,
  .nav-overlay .nav-links a:active,
  .nav-overlay .nav-links a:focus-visible {
    color: var(--accent);
  }
  .nav-overlay .nav-links a:hover::after,
  .nav-overlay .nav-links a:active::after {
    color: var(--accent);
    opacity: 1;
    transform: translateX(6px);
  }

  /* The Contact pill inside the menu — neutralize the magnetic-shader
     variant (bv-270 et al) entirely so it reads as just another menu
     row. The base `.btn-contact` rule uses `color !important` and the
     variant class can stack more chrome on top, so we override
     defensively with `!important` on every contested property and
     hide the magnetic canvas. */
  .nav-overlay .nav-links .btn-contact {
    display: flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    width: 100% !important;
    max-width: none !important;
    padding: clamp(14px, 2.2vh, 22px) 4px !important;
    background: transparent !important;
    border: 0 !important;
    border-bottom: 1px solid var(--hairline) !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    color: var(--fg) !important;
    font-size: clamp(32px, 8vw, 52px) !important;
    font-weight: 700 !important;
    letter-spacing: -0.02em !important;
    line-height: 1.02 !important;
    text-align: left !important;
    transform: none !important;
    overflow: visible !important;
  }
  .nav-overlay .nav-links .btn-contact:hover {
    background: transparent !important;
    border-bottom-color: var(--hairline) !important;
    box-shadow: none !important;
    color: var(--accent) !important;
    transform: none !important;
  }
  .nav-overlay .nav-links .btn-contact .btn-label {
    font-size: inherit !important;
    font-weight: inherit !important;
    letter-spacing: inherit !important;
    color: inherit !important;
    opacity: 1 !important;
    text-transform: none !important;
  }
  .nav-overlay .nav-links .btn-contact .btn-shader-canvas {
    display: none !important;
  }

  /* Open state — driven by `body.menu-open` (toggled by the inline
     IIFE on each page). Fade the overlay in, slide each link to
     rest with a subtle stagger so the menu composes itself rather
     than popping. */
  body.menu-open .nav-overlay {
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
    transition:
      opacity var(--dur-base) var(--ease-out),
      visibility 0s linear 0s;
  }
  body.menu-open .nav-overlay .nav-links a {
    transform: translateY(0);
    opacity: 1;
  }
  body.menu-open .nav-overlay .nav-links a:nth-child(1) { transition-delay: 0.08s, 0.12s, 0.12s; }
  body.menu-open .nav-overlay .nav-links a:nth-child(2) { transition-delay: 0.08s, 0.18s, 0.18s; }
  body.menu-open .nav-overlay .nav-links a:nth-child(3) { transition-delay: 0.08s, 0.24s, 0.24s; }
  body.menu-open .nav-overlay .nav-links a:nth-child(4) { transition-delay: 0.08s, 0.30s, 0.30s; }

  /* Respect prefers-reduced-motion — drop the slide-up + stagger */
  @media (prefers-reduced-motion: reduce) {
    .nav-overlay .nav-links a {
      transform: none;
      transition: color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) ease;
    }
    body.menu-open .nav-overlay .nav-links a { transform: none; }
  }

  /* Body scroll lock while the menu is open. The class is toggled by
     the inline open/close IIFE on each page. `overscroll-behavior`
     also stops the iOS rubber-band from leaking touch scroll into the
     body underneath. */
  body.menu-open {
    overflow: hidden;
    overscroll-behavior: contain;
  }

  /* Show the hamburger toggle */
  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 36px;
    height: 36px;
    border-radius: 10px;
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--fg);
    flex-shrink: 0;
  }
  .nav-toggle-line {
    width: 20px;
    height: 2px;
    border-radius: 1px;
    background: currentColor;
    transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
  }
  /* Open state — morph the three lines into an X */
  .nav-pill.is-open .nav-toggle-line:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
  }
  .nav-pill.is-open .nav-toggle-line:nth-child(2) { opacity: 0; }
  .nav-pill.is-open .nav-toggle-line:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
  }

  /* Slightly smaller floating toggle on mobile so it doesn't feel
     oversized in a small viewport. Position stays bottom-right. */
  .theme-toggle {
    width: 38px;
    height: 38px;
    bottom: 16px;
    right: 16px;
  }

  /* Nav-pill compact on mobile — shrink to logo + hamburger. The
     Strategy tag is already hidden by the 900px breakpoint. */
  .nav-pill {
    padding: 8px 8px 8px 14px;
    justify-content: space-between;
  }

  /* ---- Section spacing — tighter on mobile -------------- */
  /* Drop the forced 100vh min-height so sections size to their
     content + a moderate pad. Hero keeps full svh for the first
     impression. */
  .section {
    min-height: auto;
    padding-top: 64px;
    padding-bottom: 64px;
  }
  /* Size the hero to its content. It used to be forced to 100svh, which
     centered the content and left a big empty gap (~215px) below the
     buttons before "What we do" — the scroll-hint that would justify a
     full-height hero is hidden on mobile anyway. Extra top padding
     clears the fixed nav-pill; modest bottom padding lets the next
     section follow with the same rhythm as every other boundary. */
  .hero {
    min-height: auto;
    padding-top: clamp(96px, 14vh, 116px);
    padding-bottom: clamp(36px, 5vh, 52px);
  }
  .what-trusted { gap: 32px; }
  .work { gap: 28px; }

  /* ---- Hero display type at mobile ---------------------- */
  /* `clamp(40px, 6.4vw, 96px)` floors at 40px on phones, which is
     too wide for 390px viewports. Override at mobile widths — tuned so
     the longest hero line ("Diseñamos productos", the widest across
     EN/ES) fits on ONE line within the content width at common phone
     sizes (≈320–430px) instead of wrapping to two. Both the font and
     the container scale with vw, so the fit holds across that range. */
  :root { --size-display: clamp(26px, 8.3vw, 38px); }

  /* Constrain hero-content to the viewport so even pathological
     long lines wrap instead of pushing the layout wider. */
  .hero-content { max-width: 100%; min-width: 0; }
  .hero-title span { white-space: normal; }

  /* Tighter case-row padding on mobile so the work list isn't
     all whitespace. */
  .case-item { padding: 18px 0; }
  /* Keep the coming-soon badge aligned with the tag at this padding. */
  .case-item[data-coming-soon="true"] .case-badge { top: 18px; }
  .case-list { gap: 0; }

  /* Trusted-by marquee — smaller logo slots so more of the rail
     is visible on a 390px viewport. */
  .logo-slot { width: clamp(96px, 30vw, 140px); }

  /* CTA section — pull the closing block in tighter. */
  .cta { padding-bottom: 64px; }

  /* Contact: stack everything in one column. */
  .contact-grid {
    grid-template-columns: 1fr;
    gap: 32px;
  }
  .contact-image {
    aspect-ratio: 4 / 3;
    grid-column: auto;
  }

  /* The standalone contact pages need extra top breathing room on
     mobile so the heading clears the fixed nav-pill. The base
     `.section` rule above forces `padding-top: 64px` on mobile,
     which lands the contact heading directly behind the nav. The
     nav-pill occupies ~72px of vertical space (top:16px + ~56px
     height), so 104px+ pulls the heading clear with a comfortable
     gap. Also tighten the bottom padding to match the new rhythm. */
  .contact-page,
  .contact-page-v2 {
    padding-top: clamp(104px, 14vh, 128px);
    padding-bottom: clamp(48px, 8vh, 80px);
  }
}

@media (max-width: 480px) {
  .nav-pill { padding: 8px 10px; }
  .btn-row { gap: 12px; }
  .section { padding-left: 20px; padding-right: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
}
