/* =====================================================================
   Phocus Marketing Website — dark theme, Bootstrap 5.3 + plain CSS
   Design tokens ported from design_handoff_marketing_site/tokens/*.
   Site ships in DARK MODE as the base theme (per client request).
   ===================================================================== */

/* Fonts are loaded with <link rel="preconnect"> + <link rel="stylesheet"> in
   index.html rather than @import here. @import makes the browser download and
   parse this file BEFORE it discovers the font CSS, serialising
   html -> styles.css -> fonts.css -> font files and delaying first paint.
   The <link> version starts the font request in parallel with this file. */

:root {
  /* ---- Base neutrals ---- */
  --black: #0A0B0A;
  --gray-950: #131413;
  --gray-900: #1C1E1C;
  --gray-800: #2C2F2C;
  --gray-700: #454845;
  --gray-600: #63665F;
  --gray-500: #8A8D85;
  --gray-400: #ADAFA8;
  --gray-300: #CCCEC7;
  --gray-200: #E2E4DE;
  --gray-100: #EEEFEA;
  --gray-50:  #F6F6F2;
  --white: #FFFFFF;

  /* ---- Brand green ---- */
  --green-900: #1F3305;
  --green-800: #2C4A0B;
  --green-700: #3F6710;
  --green-600: #5C8F17;
  --green-500: #82DB23; /* true brand green */
  --green-400: #9BE454;
  --green-300: #B9EC85;
  --green-200: #D6F4B3;
  --green-100: #EFFAE0;

  /* ---- Type ---- */
  --font-display: 'Archivo', 'Arial Narrow', sans-serif;
  --font-body: 'Work Sans', 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', 'SFMono-Regular', monospace;

  --text-display-xl: 700 clamp(2.6rem, 6vw, 6.5rem)/0.98 var(--font-display);
  --text-display-lg: 700 clamp(2.2rem, 4.4vw, 4.5rem)/1.0 var(--font-display);
  --text-display-md: 600 clamp(1.7rem, 3vw, 3rem)/1.05 var(--font-display);
  --text-display-sm: 600 1.5rem/1.15 var(--font-display);

  --text-body-lg: 400 clamp(1.05rem, 1.6vw, 1.25rem)/1.55 var(--font-body);
  --text-body-md: 400 1.0625rem/1.6 var(--font-body);
  --text-body-sm: 400 0.9375rem/1.55 var(--font-body);
  --text-caption: 500 0.8125rem/1.4 var(--font-body);
  --text-label: 600 0.8125rem/1.2 var(--font-body);

  --tracking-tight: -0.02em;
  --tracking-wide: 0.08em;
  --tracking-wider: 0.16em;

  /* ---- Spacing / radius / motion ---- */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px;
  --space-9: 96px; --space-10: 128px;

  --radius-sm: 2px; --radius-md: 4px; --radius-lg: 8px; --radius-pill: 999px;

  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5);

  --ease-standard: cubic-bezier(0.2, 0.6, 0.2, 1);
  --duration-slow: 420ms;

  --container-max: 1240px;
  --container-narrow: 760px;

  /* ---- Semantic aliases (DARK base theme) ---- */
  --bg-page: var(--black);
  --bg-sunken: var(--gray-900);
  --bg-card: var(--gray-900);
  --bg-inverse: var(--black);

  --fg-primary: var(--white);
  --fg-secondary: var(--gray-400);
  --fg-tertiary: var(--gray-500);
  --fg-accent: var(--green-500);
  --fg-on-inverse: var(--white);
  --fg-on-inverse-secondary: var(--gray-400);
  --fg-accent-on-inverse: var(--green-500);

  --border-subtle: var(--gray-800);
  --border-default: var(--gray-800);
  --border-inverse: var(--gray-800);

  --accent-primary: var(--green-500);
  --accent-primary-hover: var(--green-400);
  --accent-on-fill: var(--black);
  --focus-ring: var(--green-500);
}

/* =========================== Base =========================== */
* { box-sizing: border-box; }

/* The browser's own [hidden] rule is only `display: none` at attribute
   specificity, so any author rule that sets display beats it. That silently
   broke every element the app hides this way: both modals stayed in the layout
   as invisible full-screen overlays that swallowed clicks, and the hero CTA
   still showed on the slide that has none. !important keeps the attribute
   authoritative no matter what a component sets. */
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font: var(--text-body-md);
  color: var(--fg-primary);
  background: var(--bg-page);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
h1, h2, h3, h4, p, figure { margin: 0; }
img { max-width: 100%; }
a { color: var(--fg-accent); text-decoration: none; }
a:hover { color: var(--accent-primary-hover); }

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

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

.divider { height: 1px; background: var(--border-default); border: 0; margin: 0; }
/* Replaces Bootstrap's .d-none, the only thing the framework was still being
   loaded for. !important because it has to beat the display on the elements it
   hides (.trusted, .f-socials), which set their own flex/grid. */
.is-hidden { display: none !important; }

/* =========================== Typography helpers =========================== */
.t-display-xl { font: var(--text-display-xl); letter-spacing: var(--tracking-tight); }
.t-display-lg { font: var(--text-display-lg); letter-spacing: var(--tracking-tight); }
.t-display-md { font: var(--text-display-md); letter-spacing: var(--tracking-tight); }
.t-display-sm { font: var(--text-display-sm); }
.t-body-lg { font: var(--text-body-lg); }
.t-body-md { font: var(--text-body-md); }
.t-body-sm { font: var(--text-body-sm); }
.t-caption { font: var(--text-caption); }
.fg-secondary { color: var(--fg-secondary); }
.fg-accent { color: var(--fg-accent); }

/* =========================== Section marker (eyebrow) =========================== */
.section-marker {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--text-label);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--fg-secondary);
  /* Every marker's text is injected by JS and collapses to zero height until
     then, nudging the whole section down when it arrives. One line of the
     label font is 16px; the hero's marker is long enough to wrap on narrow
     screens, handled in the Responsive section. */
  min-height: 16px;
}
.section-marker .idx { color: var(--fg-accent); }

/* =========================== Buttons =========================== */
.btn-phocus {
  --pad-y: 10px; --pad-x: 20px; --fs: 0.9375rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs);
  line-height: 1;
  padding: var(--pad-y) var(--pad-x);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--duration-slow) var(--ease-standard),
              color var(--duration-slow) var(--ease-standard),
              border-color var(--duration-slow) var(--ease-standard),
              transform 120ms var(--ease-standard);
  text-align: center;
}
.btn-phocus:active { transform: translateY(1px); }
.btn-phocus.sm { --pad-y: 7px; --pad-x: 14px; --fs: 0.875rem; }
.btn-phocus.lg { --pad-y: 14px; --pad-x: 28px; --fs: 1.0625rem; }

.btn-phocus.primary {
  background: var(--accent-primary);
  color: var(--accent-on-fill);
}
.btn-phocus.primary:hover { background: var(--accent-primary-hover); color: var(--accent-on-fill); }

.btn-phocus.secondary {
  background: transparent;
  color: var(--fg-primary);
  border-color: var(--border-default);
}
.btn-phocus.secondary:hover { border-color: var(--fg-primary); }

/* Off-screen host for SVG filter definitions (no layout impact). */
.glass-defs { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; }

/* =========================== Nav =========================== */
/* "Liquid glass" bar, built as three stacked sibling layers:
     ::before    frost (blur + saturate)  — every browser
     .nav-glass  SVG refraction           — Chromium only
     ::after     black tint + sheen       — every browser
   Two rules this structure exists to satisfy:
   1. The bar itself must carry NO filter. An element with backdrop-filter
      becomes a "backdrop root", which would limit .nav-glass to sampling only
      the bar's own paint — leaving it nothing but flat tint to distort.
   2. Blur and url() must live in separate declarations. Safari claims support
      for url() in backdrop-filter but renders nothing for it, so a combined
      declaration loses the blur there. */
.site-nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 1000;
  height: 72px;
  display: flex;
  align-items: center;
  padding-inline: var(--space-6);
  background: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}
/* Layer 1 — frost. No url() here, so nothing can override it. */
.site-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  backdrop-filter: blur(5px) saturate(165%);
  -webkit-backdrop-filter: blur(5px) saturate(165%);
}
/* Layer 2 — refraction only. Paints after layer 1, so it warps the already
   frosted backdrop. Inert where url() filters aren't supported. */
.site-nav .nav-glass {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
@supports (backdrop-filter: url("#glassRefraction")) {
  .site-nav .nav-glass {
    backdrop-filter: url("#glassRefraction");
  }
}
/* Layer 3 — black tint, top specular highlight, faint bottom edge light. */
.site-nav::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.13) 0%,
      rgba(255, 255, 255, 0.04) 38%,
      rgba(255, 255, 255, 0) 62%,
      rgba(255, 255, 255, 0.04) 100%),
    rgba(10, 11, 10, 0.3);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.26),
    inset 0 -1px 0 rgba(255, 255, 255, 0.07);
}
.site-nav .nav-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-6);
}
/* width:auto keeps the width/height attributes acting only as the aspect ratio
   used to reserve the box, instead of forcing the intrinsic 1402px. */
.site-nav .brand img { height: 50px; width: auto; display: block; }
.site-nav .nav-menu {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-6);
}
.site-nav .nav-links {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  list-style: none;
  padding: 0; margin: 0;
  /* Links, labels and the CTA are all injected by JS. Empty, these boxes
     collapse to a single line-height and then double when the text lands,
     which showed up as layout shift in the bar. Reserve the settled heights
     (measured: menu 32px, links 27px). */
  min-height: 27px;
}
.site-nav .nav-links a {
  font: var(--text-body-sm);
  font-weight: 500;
  color: var(--fg-primary);
  /* 24px touch target; the row already reserves 27px so this changes nothing
     on desktop, and gives the mobile panel's links a comfortable tap area. */
  display: flex;
  align-items: center;
  min-height: 24px;
}
.site-nav .nav-links a:hover { color: var(--fg-accent); }
/* 33px clears the settled 32.19px of the language toggle (its padding plus one
   line of caption text), so injecting the labels cannot grow this row. The bar
   is a fixed 72px, so the spare fraction of a pixel is invisible. */
.site-nav .nav-actions { display: flex; align-items: center; gap: var(--space-4); min-height: 33px; }

.lang-toggle {
  background: none;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  font: var(--text-caption);
  font-weight: 700;
  cursor: pointer;
  color: var(--fg-secondary);
}
.lang-toggle:hover { color: var(--fg-primary); border-color: var(--fg-primary); }

/* Mobile nav toggle */
.nav-burger {
  display: none;
  margin-left: auto;
  background: none;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  color: var(--fg-primary);
  width: 40px; height: 36px;
  cursor: pointer;
  align-items: center;
  justify-content: center;
}

/* =========================== Hero =========================== */
.hero {
  position: relative;
  /* Hard-locked height: the section never resizes as slides rotate, so
     nothing below it can shift. Content is vertically centered inside. */
  min-height: clamp(600px, 86svh, 880px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: calc(72px + var(--space-8)) var(--space-6) var(--space-8);
  overflow: hidden;
}
.hero .hero-texture {
  position: absolute;
  inset: 0;
  opacity: 0.08;
  pointer-events: none;
  background-image:
    radial-gradient(circle, #fff 1px, transparent 1px),
    linear-gradient(#fff 1px, transparent 1px),
    linear-gradient(90deg, #fff 1px, transparent 1px);
  background-size: 28px 28px, 140px 140px, 140px 140px;
  animation: phocus-drift 40s linear infinite;
  transition: opacity 500ms var(--ease-standard);
}
.hero .hero-heatmap,
.hero .hero-dashboard,
.hero .hero-insight {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  transition: opacity 500ms var(--ease-standard);
}
.hero.heatmap-on .hero-heatmap { opacity: 1; }
.hero.dashboard-on .hero-dashboard { opacity: 1; }
.hero.insight-on .hero-insight { opacity: 1; }
/* Fade the dot grid out while an animated background is showing */
.hero.heatmap-on .hero-texture,
.hero.dashboard-on .hero-texture,
.hero.insight-on .hero-texture { opacity: 0; }
/* Legibility scrim for the animated slides: darkens the left where the text
   sits, fading out to keep the animation visible on the right. Sits above the
   canvas but below the text. */
.hero.heatmap-on::after,
.hero.dashboard-on::after,
.hero.insight-on::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(105deg,
    rgba(0, 0, 0, 0.9) 0%,
    rgba(0, 0, 0, 0.75) 30%,
    rgba(0, 0, 0, 0.42) 55%,
    rgba(0, 0, 0, 0) 78%);
}
.hero .hero-inner { position: relative; z-index: 1; width: 100%; max-width: var(--container-max); margin: 0 auto; }
/* The headline and body text are injected by JS, so their boxes MUST be
   reserved here or the hero visibly grows on load (this was worth 0.25 CLS).
   The min-heights below are the measured worst case across all slides in both
   languages, in em so they track the clamped font-size, expressed as the line
   count each breakpoint actually wraps to. See the Responsive section for the
   narrower steps — every band needs its own value because the line count, not
   the font size, is what changes. */
.hero h1 {
  font: var(--text-display-xl);
  letter-spacing: var(--tracking-tight);
  color: var(--fg-primary);
  margin: var(--space-5) 0;
  max-width: 920px;
  min-height: 3.95em; /* 4 lines */
}
.hero .hero-body {
  font: var(--text-body-lg);
  color: var(--fg-secondary);
  max-width: 560px;
  margin: 0 0 var(--space-6);
  min-height: 6.25em; /* 4 lines */
}
/* Reserve the CTA row's height even on slides that have no CTA, otherwise
   hiding the button collapses the row and shifts everything below it. */
.hero .hero-ctas { display: flex; gap: var(--space-4); flex-wrap: wrap; min-height: 47px; }

/* Modern slide indicator dots — active dot elongates into a pill that
   fills with the accent color as the slide's time elapses.
   The button is a transparent 24px touch target (9px was far too small to tap
   on a phone) and the pill itself is the inner .dot-visual span, so the design
   is unchanged while the tappable area is finger-sized. The negative margins
   cancel the extra padding so the row still lines up with the text above it. */
/* The dots are injected, so reserve the row height or the hero grows by it when
   they appear, moving every section below. */
.hero-dots {
  display: flex;
  align-items: center;
  margin-top: calc(var(--space-7) - 8px);
  margin-left: -7px;
  min-height: 24px;
}
.hero-dots button {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 24px; height: 24px;
  padding: 0 7px;
  border: 0;
  background: none;
  cursor: pointer;
}
.hero-dots .dot-visual {
  position: relative;
  overflow: hidden;
  width: 9px; height: 9px;
  border-radius: var(--radius-pill);
  background: var(--gray-700);
  transition: width 340ms var(--ease-standard), background 340ms var(--ease-standard);
}
.hero-dots button:hover .dot-visual { background: var(--gray-600); }
.hero-dots button.active .dot-visual { width: 34px; background: var(--gray-700); }
.hero-dots .dot-fill {
  position: absolute;
  top: 0; left: 0;
  height: 100%;
  width: 0;
  border-radius: var(--radius-pill);
  background: var(--accent-primary);
}

.fade-rise { animation: phocus-fade-rise 480ms var(--ease-standard); }

/* Crossfade between rotating slides */
.hero h1,
.hero .hero-body {
  transition: opacity 340ms var(--ease-standard), transform 340ms var(--ease-standard);
}
.hero h1.is-leaving,
.hero .hero-body.is-leaving {
  opacity: 0;
  transform: translateY(-10px);
}

@keyframes phocus-fade-rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes phocus-drift {
  from { background-position: 0 0, 0 0, 0 0; }
  to { background-position: 28px 28px, 140px 140px, -140px 140px; }
}

/* =========================== Stats =========================== */
.stats { padding: var(--space-8) var(--space-6) var(--space-6); }
/* The eyebrow is followed by the values grid rather than a heading, so it
   needs its own bottom spacing. */
.stats .section-marker { margin-bottom: var(--space-6); }
.stats .stat-values,
.stats .stat-labels {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-7);
}
/* Stats, labels and the industries paragraph are all injected by JS, so their
   boxes are reserved here for the same reason as the hero copy.
   stat-values is always a single line of display-md, whose size is
   clamp(27.2px, 3vw, 48px) at line-height 1.05 — so this expression tracks it
   exactly at every width, with no breakpoints needed. */
.stats .stat-values { min-height: clamp(28.56px, 3.15vw, 50.4px); }
/* stat-labels and industries use unclamped body fonts, so their height changes
   only when the line count changes. Values are px measured at the transitions
   (worst case of ES and EN); see the Responsive section for the narrower ones. */
.stats .stat-labels { min-height: 47px; }  /* 2 lines */
.stats .stat-value { font: var(--text-display-md); color: var(--fg-accent); font-weight: 700; }
.stats .stat-line { height: 1px; background: var(--border-inverse); margin: var(--space-4) 0; }
.stats .stat-label { font: var(--text-body-sm); color: var(--fg-secondary); max-width: 240px; }
/* Spans the full stats container rather than the usual reading measure: it
   reads as a run-on list of sectors, not prose, so the extra width just costs
   it a line or two of height. */
.stats .industries {
  font: var(--text-body-md);
  color: var(--fg-secondary);
  margin: var(--space-7) 0 0;
  min-height: 54px; /* 2 lines */
}

/* =========================== Trusted by =========================== */
.trusted { padding: var(--space-7) var(--space-6) var(--space-9); }
.trusted .logos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4);
  align-items: stretch;
}
/* Uniform light tiles so every logo reads at a consistent size and keeps
   its full detail (no silhouette flattening). */
.trusted .logos img {
  height: 150px;
  width: 100%;
  object-fit: contain;
  padding: var(--space-4) var(--space-5);
  background: var(--gray-50);
  border: 1px solid var(--border-inverse);
  border-radius: var(--radius-md);
  transition: transform var(--duration-slow) var(--ease-standard),
              background var(--duration-slow) var(--ease-standard);
}
.trusted .logos img:hover { background: var(--white); transform: translateY(-2px); }

/* =========================== Section shell =========================== */
/* The Knowledge Center reuses these header classes but is a .knowledge band
   rather than a .section, so it's listed explicitly to keep both headers
   identical. */
.section { padding: var(--space-9) var(--space-6); }
.section .section-title,
.knowledge .section-title { font: var(--text-display-md); letter-spacing: var(--tracking-tight); color: var(--fg-primary); margin: var(--space-5) 0 var(--space-3); }
.section .section-lead,
.knowledge .section-lead { font: var(--text-body-md); color: var(--fg-primary);  margin: 0 0 var(--space-3); }
.section .section-body { font: var(--text-body-md); color: var(--fg-secondary); max-width: 680px; margin: 0 0 var(--space-7); }

/* =========================== Justified prose =========================== */
/* Justification and hyphenation are a package deal: Spanish averages longer
   words than English, so justifying without hyphens tears open "rivers" of
   white space. hyphens:auto picks its dictionary from the lang attribute,
   which main.js keeps in sync with the active language (es / en).
   Applied to long-form prose only — short or narrow text (buttons, labels,
   card copy, stat labels) stays ragged-right, where justification would look
   gappy. */
.section .section-lead,
.section .section-body,
.knowledge .section-lead,
.stats .industries,
.knowledge .k-lead,
.knowledge .k-body {
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  /* Don't hyphenate words under 6 chars, and never leave a fragment of
     fewer than 3 chars on either side of the break. */
  hyphenate-limit-chars: 6 3 3;
  -webkit-hyphenate-limit-before: 3;
  -webkit-hyphenate-limit-after: 3;
  /* Cap consecutive hyphenated lines — a hyphen ladder reads badly.
     Prefixed only; the unprefixed CSS Text 4 property isn't implemented
     anywhere yet, so Chromium simply ignores this limit. */
  -webkit-hyphenate-limit-lines: 2;
}

/* =========================== Philosophy layout =========================== */
/* Full-width copy, with the team portraits in a single row beneath it. */
.phil-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  align-items: start;
}
/* The grid gap already separates the copy from the portraits below. */
.phil-copy .section-body { margin-bottom: 0; }
/* The prose measure is capped for readability elsewhere; here the copy should
   span the section. */
.phil-copy .section-lead,
.phil-copy .section-body { max-width: none; }
/* auto-flow:column keeps everyone on one row whatever the head count, instead
   of hard-coding a column count that breaks when the team changes. */
#philosophy .team-grid {
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
}

/* =========================== Team grid =========================== */
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--space-5);
}
.team-member { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); text-align: center; }
.team-photo {
  width: 96px; height: 96px;
  border-radius: var(--radius-pill);
  background: var(--gray-800);
  display: flex; align-items: center; justify-content: center;
  color: var(--gray-500);
  font: var(--text-caption);
  filter: grayscale(1);
  overflow: hidden;
}
/* Fill the circle and crop, so portraits of differing aspect ratios all
   render identically. */
.team-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.team-id { display: flex; flex-direction: column; gap: 2px; }
.team-name { font: var(--text-body-sm); font-weight: 600; color: var(--fg-primary); }
/* Roles sit quieter than names: smaller, secondary colour, and the wide
   tracking of a label so they read as metadata, not a second name. */
.team-role {
  font: var(--text-caption);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  color: var(--fg-secondary);
  text-wrap: balance;
}

/* =========================== Services flip cards =========================== */
/* Six services in a fixed 3 x 2 grid on desktop (see Responsive for the
   2-col and 1-col steps down). */
.services-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5);
  counter-reset: svc;
}
.flip-card { perspective: 1200px; height: 252px; cursor: pointer; counter-increment: svc; border-radius: var(--radius-lg); }
.flip-card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
.flip-card-inner {
  position: relative;
  width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform 520ms var(--ease-standard);
}
/* Flip on hover where a real pointer exists. The .flipped class is kept for
   keyboard (Enter/Space) and for touch devices, which have no hover state. */
@media (hover: hover) and (pointer: fine) {
  .flip-card:hover .flip-card-inner { transform: rotateY(180deg); }
}
.flip-card.flipped .flip-card-inner { transform: rotateY(180deg); }
.flip-face {
  position: absolute;
  inset: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}
.flip-front {
  background: linear-gradient(155deg, var(--gray-900) 0%, var(--gray-950) 100%);
  border: 1px solid var(--border-inverse);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  /* Keep the centered question clear of the index badge and the + affordance. */
  padding-block: calc(var(--space-5) + var(--space-6));
  gap: var(--space-4);
  transition: transform 240ms var(--ease-standard),
              border-color 240ms var(--ease-standard),
              box-shadow 240ms var(--ease-standard);
}
/* Index badge (01, 02, …) top-left */
.flip-front::before {
  content: counter(svc, decimal-leading-zero);
  position: absolute;
  top: var(--space-5); left: var(--space-5);
  font: var(--text-label);
  letter-spacing: var(--tracking-wider);
  color: var(--fg-accent);
}
/* Flip affordance — a plus that rotates on hover */
.flip-front::after {
  content: '+';
  position: absolute;
  top: var(--space-4); right: var(--space-5);
  font: 400 1.5rem/1 var(--font-display);
  color: var(--gray-600);
  transition: color 240ms var(--ease-standard), transform 300ms var(--ease-standard);
}
/* Lift only on keyboard focus — on hover the card is already flipping, and
   translating a face inside the rotating 3D context just fights it. */
.flip-card:focus-visible .flip-front {
  transform: translateY(-4px);
  border-color: var(--green-700);
  box-shadow: var(--shadow-md);
}
.flip-card:hover .flip-front::after {
  color: var(--fg-accent);
  transform: rotate(90deg);
}
.flip-front .q {
  font: 600 clamp(1.3rem, 1.75vw, 1.6rem)/1.25 var(--font-display);
  letter-spacing: var(--tracking-tight);
  color: var(--fg-primary);
}
.flip-back {
  transform: rotateY(180deg);
  background: var(--gray-950);
  border: 1px solid var(--green-900);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
}
.flip-back .svc-title { font: var(--text-display-sm); color: var(--fg-accent-on-inverse); }
.flip-back .svc-desc { font: var(--text-body-md); color: var(--fg-on-inverse-secondary); }
/* Opens the detail modal. align-self keeps it from stretching the full width. */
.flip-back .svc-more {
  align-self: flex-start;
  /* Padding rather than min-height so the underline stays tight under the text
     while the button still clears 24px for touch; the margin is reduced by the
     same amount the padding adds, so spacing on the card is unchanged. */
  margin-top: 3px;
  padding: 5px 0 4px;
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  border-radius: 0;
  color: var(--fg-accent-on-inverse);
  font: var(--text-label);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
  transition: color 240ms var(--ease-standard);
}
.flip-back .svc-more:hover { color: var(--fg-on-inverse); }
.flip-back .svc-more:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

/* =========================== Service detail modal =========================== */
/* Set while the dialog is open so the page behind it can't scroll. */
body.no-scroll { overflow: hidden; }
.svc-modal {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  opacity: 0;
  transition: opacity 200ms var(--ease-standard);
}
.svc-modal.open { opacity: 1; }
.svc-modal-scrim {
  position: absolute;
  inset: 0;
  background: rgba(4, 5, 4, 0.72);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.svc-modal-panel {
  position: relative;
  width: min(720px, 100%);
  /* Long project lists must not push the panel off screen. */
  max-height: min(86vh, 780px);
  overflow-y: auto;
  padding: var(--space-7);
  background: linear-gradient(160deg, var(--gray-900) 0%, var(--gray-950) 100%);
  border: 1px solid var(--green-900);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  transform: translateY(12px) scale(0.98);
  transition: transform 200ms var(--ease-standard);
}
/* The issues archive needs more room for its cover grid. */
.svc-modal-panel.is-wide { width: min(980px, 100%); }
.svc-modal.open .svc-modal-panel { transform: translateY(0) scale(1); }
.svc-modal-panel:focus { outline: none; }
.svc-modal-close {
  position: absolute;
  top: var(--space-5); right: var(--space-5);
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  background: none;
  border: 1px solid var(--border-inverse);
  border-radius: var(--radius-pill);
  color: var(--fg-on-inverse-secondary);
  cursor: pointer;
  transition: color 240ms var(--ease-standard),
              border-color 240ms var(--ease-standard);
}
.svc-modal-close:hover { color: var(--fg-accent-on-inverse); border-color: var(--fg-accent-on-inverse); }
.svc-modal-title {
  font: var(--text-display-sm);
  letter-spacing: var(--tracking-tight);
  color: var(--fg-accent-on-inverse);
  /* Clear of the close button. */
  margin: var(--space-3) var(--space-8) var(--space-2) 0;
}
.svc-modal-q {
  /* The token already carries a weight, so override it separately rather
     than prefixing the shorthand (which would be invalid). */
  font: var(--text-body-lg);
  font-weight: 600;
  color: var(--fg-primary);
  margin: 0 0 var(--space-4);
}
.svc-modal-tagline {
  font: var(--text-body-md);
  font-weight: 600;
  color: var(--fg-accent-on-inverse);
  margin: 0 0 var(--space-4);
}
.svc-modal-body p {
  font: var(--text-body-md);
  color: var(--fg-on-inverse-secondary);
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
  hyphenate-limit-chars: 6 3 3;
  margin: 0 0 var(--space-4);
}
.svc-modal-body p:last-child { margin-bottom: 0; }
.svc-modal-projects {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-inverse);
}
.svc-modal-projects-label {
  font: var(--text-label);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg-accent-on-inverse);
  margin-bottom: var(--space-4);
}
.svc-modal-projects ul {
  /* Two columns so the longer lists stay compact. */
  columns: 2;
  column-gap: var(--space-6);
  list-style: none;
  padding: 0;
  margin: 0;
}
.svc-modal-projects li {
  font: var(--text-body-sm);
  color: var(--fg-on-inverse-secondary);
  padding-left: var(--space-4);
  margin-bottom: var(--space-3);
  break-inside: avoid;
  position: relative;
}
.svc-modal-projects li::before {
  content: '';
  position: absolute;
  left: 0; top: 0.55em;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--fg-accent);
}

/* =========================== Knowledge Center (slideshow) =========================== */
.knowledge { background: var(--bg-sunken); padding: var(--space-9) var(--space-6); }
.knowledge .k-inner { max-width: var(--container-max); margin: 0 auto; }

.k-slider { margin-top: var(--space-7); }
/* Clips the track; the slides themselves are full-width. */
.k-viewport { overflow: hidden; border-radius: var(--radius-md); }
.k-track {
  display: flex;
  /* Equal-height flex children means the viewport settles at the height of
     the tallest slide, so switching slides doesn't jump the page. */
  align-items: stretch;
  transition: transform 520ms var(--ease-standard);
}
.k-slide {
  flex: 0 0 100%;
  display: flex;
  gap: var(--space-8);
  flex-wrap: wrap;
  align-items: stretch;
}
/* Off-screen slides must not be reachable by tab or read aloud. */
.k-slide[aria-hidden='true'] { visibility: hidden; }
.k-media {
  flex: 1 1 320px;
  min-height: 340px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--black);
}
.k-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Shown when the artwork is missing, instead of a broken image. */
.k-media.is-missing {
  background: linear-gradient(155deg, var(--gray-900) 0%, var(--gray-950) 100%);
  border: 1px dashed var(--border-inverse);
}
.k-copy {
  flex: 1 1 420px;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  justify-content: center;
}
.k-kicker {
  font: var(--text-label);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--fg-accent);
}
.k-title { font: var(--text-display-lg); letter-spacing: var(--tracking-tight); color: var(--fg-primary); }
.k-subtitle { font: var(--text-body-md); color: var(--gray-400); font-style: italic; }
.k-lead { font: var(--text-body-lg); color: var(--fg-primary); margin: 0; }
.k-body { font: var(--text-body-md); color: var(--fg-secondary); margin: 0; }
.k-stat { display: flex; gap: var(--space-5); align-items: baseline; padding: var(--space-5) 0; flex-wrap: wrap; }
.k-stat-value { font: var(--text-display-md); color: var(--fg-accent); }
.k-stat-label { font: var(--text-body-sm); color: var(--fg-secondary); max-width: 320px; }
.k-actions { display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap; }

/* Endorsement quotes (the book slide) */
.k-quotes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.k-quotes li { padding-left: var(--space-5); border-left: 1px solid var(--green-900); }
.k-quote-text { display: block; font: var(--text-body-sm); font-style: italic; color: var(--fg-secondary); }
.k-quote-source {
  display: block;
  margin-top: var(--space-2);
  font: var(--text-caption);
  letter-spacing: var(--tracking-wide);
  color: var(--fg-accent);
}

/* Prev / dots / next */
.k-controls {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-top: var(--space-6);
}
.k-arrow {
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: none;
  border: 1px solid var(--border-inverse);
  border-radius: var(--radius-pill);
  color: var(--fg-secondary);
  cursor: pointer;
  transition: color 240ms var(--ease-standard),
              border-color 240ms var(--ease-standard);
}
.k-arrow:hover { color: var(--fg-accent); border-color: var(--green-700); }
.k-dots { display: flex; align-items: center; margin-inline: -8px; }
/* Same 24px touch-target treatment as the hero dots, but these have no inner
   element to animate, so padding grows the target and background-clip keeps the
   paint on the 8px core — no extra markup needed. content-box sizing is what
   makes width mean the visible dot rather than the whole target. */
.k-dot {
  box-sizing: content-box;
  width: 8px; height: 8px;
  padding: 8px;
  background: var(--gray-700);
  background-clip: content-box;
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background 240ms var(--ease-standard), width 240ms var(--ease-standard);
}
.k-dot.is-active { background: var(--fg-accent); background-clip: content-box; width: 22px; }

/* Digital World issue grid (inside the modal) */
.issues-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-5);
  margin-top: var(--space-6);
}
.issue { text-decoration: none; display: block; }
.issue-cover {
  /* Magazine-cover proportions, so tiles align before images load. */
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--black);
  border: 1px solid var(--border-inverse);
  transition: transform 240ms var(--ease-standard),
              border-color 240ms var(--ease-standard);
}
.issue-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.issue-cover.is-missing {
  background: linear-gradient(155deg, var(--gray-900) 0%, var(--gray-950) 100%);
  border-style: dashed;
}
a.issue:hover .issue-cover { transform: translateY(-4px); border-color: var(--green-700); }
.issue-label {
  margin-top: var(--space-3);
  font: var(--text-body-sm);
  color: var(--fg-on-inverse-secondary);
}
a.issue:hover .issue-label { color: var(--fg-accent-on-inverse); }
.issues-empty { font: var(--text-body-md); color: var(--fg-on-inverse-secondary); margin: var(--space-6) 0 0; }

/* =========================== Closing CTA =========================== */
.closing { padding: var(--space-9) var(--space-6); }
.closing .c-inner { max-width: var(--container-narrow); margin: 0 auto; text-align: center; }
.closing h2 { font: var(--text-display-md); letter-spacing: var(--tracking-tight); color: var(--fg-primary); margin: 0 0 var(--space-6); }
.closing form { display: flex; flex-direction: column; gap: var(--space-6); align-items: stretch; text-align: left; }
.closing .form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4) var(--space-5);
}
.closing .field { display: flex; flex-direction: column; gap: var(--space-2); }
.closing .field-full { grid-column: 1 / -1; }
.closing label {
  font: var(--text-label);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg-secondary);
}
.closing input,
.closing textarea {
  width: 100%;
  font: var(--text-body-md);
  color: var(--fg-primary);
  background: var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  transition: border-color 200ms var(--ease-standard);
}
.closing textarea { resize: vertical; min-height: 120px; }
.closing input:focus,
.closing textarea:focus { outline: none; border-color: var(--accent-primary); }
.closing input::placeholder,
.closing textarea::placeholder { color: var(--fg-tertiary); }
.closing button[type="submit"] { align-self: center; }
.closing .sent-msg { font: var(--text-body-lg); color: var(--fg-accent); }
/* Send failures: readable but not alarming, and never the brand green, which
   the success message owns. */
.closing .form-error {
  font: var(--text-body-sm);
  color: #F0A0A0;
  margin: calc(var(--space-3) * -1) 0 0;
}
/* In-flight state: the label already reads "Enviando…", so just soften the
   button and block further clicks. */
.closing button[type="submit"]:disabled {
  opacity: 0.6;
  cursor: progress;
}
@media (max-width: 560px) {
  .closing .form-grid { grid-template-columns: 1fr; }
}

/* =========================== Footer =========================== */
.site-footer {
  background: var(--bg-inverse);
  color: var(--fg-on-inverse);
  padding: var(--space-9) var(--space-6) var(--space-6);
  border-top: 1px solid var(--border-inverse);
}
.site-footer .f-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-7);
}
/* width:auto is required, not optional: the width/height attributes on the
   <img> act as presentational hints, so without this the logo would render at
   its intrinsic 1402px wide. With it, the attributes only supply the aspect
   ratio used to reserve the box. */
.site-footer .f-logo { height: 56px; width: auto; margin-bottom: var(--space-5); }
.site-footer .f-address { font: var(--text-body-sm); color: var(--fg-on-inverse-secondary); }
.site-footer .f-socials { display: flex; gap: var(--space-4); margin-top: var(--space-5); }
.site-footer .f-socials a {
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-inverse);
  border-radius: var(--radius-pill);
  color: var(--fg-on-inverse);
  transition: color 240ms var(--ease-standard),
              border-color 240ms var(--ease-standard);
}
.site-footer .f-socials a:hover {
  color: var(--fg-accent-on-inverse);
  border-color: var(--fg-accent-on-inverse);
}
.site-footer .f-socials svg { width: 16px; height: 16px; display: block; }
.site-footer .f-sitemap { display: flex; flex-direction: column; gap: var(--space-2); }
.site-footer .f-sitemap-label {
  font: var(--text-label);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg-accent-on-inverse);
  margin-bottom: 4px;
}
/* min-height plus centring makes each link a 24px touch target; the text keeps
   its own size, so only the gap between rows grows slightly. */
.site-footer .f-sitemap a {
  color: var(--fg-on-inverse-secondary);
  font: var(--text-body-sm);
  display: flex;
  align-items: center;
  min-height: 24px;
}
.site-footer .f-sitemap a:hover { color: var(--fg-on-inverse); }
.site-footer .f-copyright {
  max-width: var(--container-max);
  margin: var(--space-7) auto 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-inverse);
  font: var(--text-caption);
  /* gray-600 was only 3.38:1 on the footer's near-black background, under the
     4.5:1 that 13px text needs. gray-500 is the next step up the scale and the
     quietest option that passes, at 5.85:1. */
  color: var(--gray-500);
}

/* =========================== Responsive =========================== */
@media (max-width: 860px) {
  .nav-burger { display: inline-flex; }
  .site-nav .nav-menu {
    position: fixed;
    top: 72px;
    left: 0; right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-5);
    background: rgba(10, 11, 10, 0.98);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    padding: var(--space-6);
    border-bottom: 1px solid var(--border-inverse);
    margin: 0;
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms var(--ease-standard), transform 200ms var(--ease-standard);
  }
  .site-nav .nav-links { flex-direction: column; align-items: flex-start; gap: var(--space-4); }
  .site-nav .nav-links a { font-size: 1.125rem; }
  .site-nav.open .nav-menu {
    opacity: 1; transform: translateY(0); pointer-events: auto;
  }
}

/* ---- Hero text reservations per band ----
   The headline and body are injected by JS, so their boxes must already be the
   right size at first paint or the hero grows and everything in it shifts.
   Each breakpoint below is a measured line-count transition for the longest
   slide across ES and EN (the copy differs per language, so these are the
   worst case of the two). Values are em so they follow the clamped font-size.
   Reserving too much only adds slack inside the hero's own locked height,
   which is harmless; reserving too little is a layout shift. Never `auto`.
   To re-derive after a copy change, measure the max height of every slide at
   each width and take the line count. */
@media (max-width: 1250px) {
  .hero h1 { min-height: 3em; }            /* 3 lines */
}
@media (max-width: 1180px) {
  .hero .hero-body { min-height: 4.7em; }  /* 3 lines */
}
/* 560px dips back to 3 lines between two 4-line widths; reserving 4 across the
   whole band absorbs that wobble instead of chasing it with another rule. */
@media (max-width: 570px) {
  .hero h1 { min-height: 3.97em; }         /* 4 lines */
}
@media (max-width: 540px) {
  .hero .hero-body { min-height: 6.25em; } /* 4 lines */
}
@media (max-width: 450px) {
  .hero h1 { min-height: 4.95em; }         /* 5 lines */
}
@media (max-width: 430px) {
  .hero .hero-body { min-height: 7.8em; }  /* 5 lines */
}
@media (max-width: 390px) {
  .hero h1 { min-height: 5.94em; }         /* 6 lines */
}
@media (max-width: 360px) {
  .hero .hero-body { min-height: 9.35em; } /* 6 lines */
}
@media (max-width: 350px) {
  .hero h1 { min-height: 6.9em; }          /* 7 lines */
}
@media (max-width: 320px) {
  .hero .hero-body { min-height: 10.9em; } /* 7 lines */
}

/* ---- Stats reservations per band ----
   Same purpose as the hero rules above: these blocks are injected by JS, so
   their height has to be known before the copy lands. px is exact here because
   both use unclamped body fonts, so only the line count changes. */
@media (max-width: 1105px) {
  .stats .industries { min-height: 82px; }  /* 3 lines */
}
@media (max-width: 765px) {
  .stats .industries { min-height: 109px; } /* 4 lines */
}
@media (max-width: 670px) {
  .stats .stat-labels { min-height: 70px; } /* 3 lines */
}
@media (max-width: 595px) {
  .stats .industries { min-height: 136px; } /* 5 lines */
}
@media (max-width: 485px) {
  .stats .stat-labels { min-height: 93px; } /* 4 lines */
}
@media (max-width: 480px) {
  .stats .industries { min-height: 163px; } /* 6 lines */
}
@media (max-width: 405px) {
  .stats .industries { min-height: 190px; } /* 7 lines */
}
@media (max-width: 360px) {
  .stats .industries { min-height: 218px; } /* 8 lines */
}
@media (max-width: 325px) {
  .stats .industries { min-height: 245px; } /* 9 lines */
}
/* "CONSUMER INSIGHTS & GROWTH CONSULTING" is long enough to wrap here. */
@media (max-width: 420px) {
  .hero .section-marker { min-height: 31px; } /* 2 lines */
}

@media (max-width: 992px) {
  .hero { padding-top: calc(72px + var(--space-8)); }
  .knowledge .k-media { min-height: 260px; }
  /* 3 x 2 becomes 2 x 3 */
  .services-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .phil-layout { gap: var(--space-7); }
  .phil-copy .section-body { margin-bottom: 0; }
}

/* Seven 96px portraits plus their gaps need ~865px; below that the single row
   would squeeze the names, so let the grid wrap. */
@media (max-width: 900px) {
  #philosophy .team-grid {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  }
}

@media (max-width: 560px) {
  .section, .knowledge, .closing { padding-inline: var(--space-5); }
  .hero { padding-inline: var(--space-5); }
  .stats { padding-inline: var(--space-5); }
  /* Single column; let taller wrapped questions grow the card */
  .services-grid { grid-template-columns: 1fr; }
  .flip-card { height: 236px; }
  /* Narrow panel: tighter padding, single-column project list */
  .svc-modal { padding: var(--space-4); }
  .svc-modal-panel { padding: var(--space-6); }
  .svc-modal-projects ul { columns: 1; }
  .trusted { padding-inline: var(--space-5); }
  .stats .stat-values, .stats .stat-labels { gap: var(--space-6); }
  .site-footer .f-inner { flex-direction: column; }
}

@media (prefers-reduced-motion: reduce) {
  .hero .hero-texture { animation: none; }
  .fade-rise { animation: none; }
  .hero h1, .hero .hero-body { transition: none; }
  .hero h1.is-leaving, .hero .hero-body.is-leaving { opacity: 1; transform: none; }
  html { scroll-behavior: auto; }
}
