/* ============================================================
   PHALANX SYSTEMS - styles.css
   Coin & parchment design system
   ============================================================ */

:root {
  --navy-900: #041838;
  --navy-800: #06215A;
  --navy-700: #0B2A6E;
  --navy-600: #16307C;
  --navy-100: #E8EDF6;
  --ivory:    #FAFBFC;
  --white:    #FFFFFF;
  --ink:      #10203E;
  --slate:    #5A6A85;
  --mist:     #B8C4D9;
  --accent:   #B98D2F;
  --line:     rgba(6, 33, 90, 0.12);
  --line-dark: rgba(255, 255, 255, 0.14);
  --radius: 14px;
  /* Headings moved to Manrope: Cinzel is a display face and was carrying page
     headings it was never designed for. Cinzel now appears only as a brand
     accent (wordmark, kickers, numerals). */
  --font-head:    'Manrope', 'Segoe UI', system-ui, sans-serif;
  --font-body:    'Inter', 'Segoe UI', system-ui, sans-serif;
  --font-display: 'Cinzel', 'Georgia', 'Times New Roman', serif;
  --shadow-sm: 0 8px 30px -12px rgba(4, 24, 56, 0.12);
  --shadow-md: 0 18px 44px -16px rgba(4, 24, 56, 0.18);
  --reveal-distance: 24px;
  /* Single source of truth for the fixed nav's height. The hero's top padding
     and every anchor's scroll offset derive from it, so they can never drift
     apart when the nav resizes. */
  --nav-h: clamp(68px, 7.4vw, 92px);

  /* Fluid spacing scale. Every gap on the page comes from here, so vertical
     rhythm stays proportional from 360px to 2560px instead of snapping at
     two breakpoints. */
  --s-1: clamp(6px, 0.6vw, 8px);
  --s-2: clamp(10px, 1vw, 14px);
  --s-3: clamp(14px, 1.5vw, 20px);
  --s-4: clamp(20px, 2.2vw, 28px);
  --s-5: clamp(28px, 3.2vw, 40px);
  --s-6: clamp(38px, 4.4vw, 56px);
  --s-7: clamp(52px, 6vw, 78px);
  --s-8: clamp(64px, 7.5vw, 100px);

  /* Fluid type scale */
  --t-display: clamp(2.1rem, 1.35rem + 3.1vw, 3.7rem);
  --t-h2:      clamp(1.75rem, 1.3rem + 1.9vw, 2.7rem);
  --t-h3:      clamp(1.08rem, 1rem + 0.4vw, 1.3rem);
  --t-lede:    clamp(1rem, 0.96rem + 0.22vw, 1.12rem);
  --t-body:    clamp(0.9rem, 0.87rem + 0.14vw, 0.97rem);
  --t-small:   clamp(0.78rem, 0.76rem + 0.1vw, 0.85rem);
}

/* ---------- Reset ---------- */
* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background: var(--ivory);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

::selection {
  background: rgba(6, 33, 90, 0.18);
  color: var(--navy-800);
}

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

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

h1, h2, h3, h4 {
  font-family: var(--font-head);
  font-weight: 700;
  line-height: 1.16;
  letter-spacing: -0.015em;
}
h1 { letter-spacing: -0.025em; }
h2 { letter-spacing: -0.02em; }
h3, h4 { letter-spacing: -0.01em; }

.container {
  width: min(1240px, 100% - clamp(32px, 5vw, 64px));
  margin-inline: auto;
}

/* The nav is fixed, so un-offset anchor targets land underneath it. */
section[id] { scroll-margin-top: calc(var(--nav-h) + 14px); }

/* ---------- Skip link ---------- */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--navy-800);
  color: var(--white);
  padding: 10px 18px;
  border-radius: 0 0 10px 0;
  font-weight: 600;
  z-index: 200;
}
.skip-link:focus { left: 0; }

/* Off-screen but announced. Used to give four identical "Learn more" controls
   distinct accessible names without changing what a sighted reader sees. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ---------- Focus ---------- */
:focus-visible {
  outline: 2px solid var(--navy-600);
  outline-offset: 3px;
  border-radius: 3px;
}
.site-footer :focus-visible,
.hero :focus-visible,
.page-hero :focus-visible,
.cta-band :focus-visible,
.prod-band :focus-visible {
  /* .page-hero was missing. Its navy gradient carries the breadcrumb links on
     seven pages, and a navy-600 ring on navy is not a visible focus indicator.
     .prod-band is the fifth navy surface and joined the list when it was added:
     navy-600 on navy-800 is 1.3:1, so without this a keyboard user loses the
     cursor for the five stops inside the home page's products band. */
  outline-color: var(--white);
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  padding: 14px 28px;
  border-radius: 12px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
  white-space: nowrap;
}
.btn-primary {
  background: var(--navy-800);
  color: var(--white);
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 26px -10px rgba(6, 33, 90, 0.45);
}
.btn-white {
  background: var(--white);
  color: var(--navy-800);
}
.btn-white:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 26px -10px rgba(255, 255, 255, 0.35);
}
/* The light-surface counterpart to .btn-ghost-on-navy, for a secondary control
   on ivory, white or navy-100 — the second download on a product page, where two
   .btn-primary buttons stacked would give a reader no steer about which one they
   actually want. Named without a suffix because it is the default case. */
.btn-ghost {
  border: 1px solid rgba(6, 33, 90, 0.28);
  color: var(--navy-800);
  background: transparent;
}
.btn-ghost:hover {
  border-color: var(--navy-800);
  background: rgba(6, 33, 90, 0.04);
  transform: translateY(-2px);
}
.btn-ghost-on-navy {
  border: 1px solid rgba(255, 255, 255, 0.35);
  color: var(--white);
  background: transparent;
}
.btn-ghost-on-navy:hover {
  border-color: rgba(255, 255, 255, 0.65);
  background: rgba(255, 255, 255, 0.06);
  transform: translateY(-2px);
}
.btn-sm { padding: 10px 20px; font-size: 0.88rem; }
.btn-block { width: 100%; }

/* ---------- Kicker / section headers ---------- */
.kicker {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--navy-600);
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}
.kicker::before {
  content: "";
  width: 26px;
  height: 1px;
  background: var(--accent);
  opacity: 0.7;
}
.kicker.on-navy {
  color: var(--mist);
}
.kicker.on-navy::before {
  background: var(--accent);
  opacity: 0.55;
}

.section {
  padding: clamp(56px, 7vw, 84px) 0;
  position: relative;
}
.section.alt {
  background: var(--white);
  border-block: 1px solid var(--line);
  box-shadow: inset 0 0 0 1px rgba(232, 237, 246, 0.6);
}

.section-head {
  max-width: 640px;
  margin-bottom: clamp(28px, 4vw, 44px);
}
.section-head h2 {
  font-size: clamp(1.9rem, 3.6vw, 2.7rem);
  margin-bottom: 16px;
  color: var(--navy-800);
}
.section-head .lede {
  color: var(--slate);
  font-size: 1.06rem;
}

.mist-text { color: var(--mist); }

/* ---------- Code-meander divider ---------- */
.code-meander {
  display: flex;
  justify-content: center;
  gap: 10px;
  padding: 0 0 0;
  font-family: 'Courier New', 'Consolas', monospace;
  font-size: 11px;
  color: var(--navy-800);
  opacity: 0.22;
  letter-spacing: 0.06em;
  user-select: none;
  pointer-events: none;
}

/* ============================================================
   NAVIGATION
   ============================================================ */
.site-nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 100;
  background: var(--white);
  transition: box-shadow 0.3s ease, border-color 0.3s ease;
  border-bottom: 1px solid transparent;
}
.site-nav.scrolled {
  border-bottom-color: var(--line);
  box-shadow: 0 1px 0 var(--line), 0 4px 20px -8px rgba(4, 24, 56, 0.08);
}
.nav-inner {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.2vw, 28px);
  height: var(--nav-h);
}

.brand {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.2vw, 14px);
}
/* The coin ships as its own square asset (assets/phalanx-coin.webp), so this
   is a plain scaled image — no cropping offsets tied to a specific logo file. */
.brand-mark {
  width: clamp(42px, 4.4vw, 56px);
  height: clamp(42px, 4.4vw, 56px);
  flex: none;
}
.brand-mark img { width: 100%; height: 100%; }
.brand-wordmark {
  display: flex;
  flex-direction: column;
  line-height: 1;
}
.bw-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.18rem;
  letter-spacing: 0.16em;
  color: var(--navy-800);
}
/* The sub-line went from 7 characters to 15 ("SYSTEMS" -> "SYSTEMS LIMITED").
   Holding the old 0.52em tracking would have run it half again as wide as
   PHALANX above it and broken the lockup, so size and tracking are retuned to
   land the line at the same measure as the name. Values are measured, not
   guessed: see the width check in the build notes. */
.bw-sub {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.58rem;
  letter-spacing: 0.24em;
  color: var(--slate);
  margin-top: 5px;
}

.nav-links {
  display: flex;
  gap: 26px;
  margin-left: auto;
}
.nav-links a {
  font-size: 0.94rem;
  font-weight: 500;
  color: var(--slate);
  transition: color 0.2s ease;
}
.nav-links a:hover { color: var(--navy-800); }

.nav-cta { margin-left: 4px; }

/* ---------- LinkedIn in the top banner ----------
   Sits between the links and the CTA, and stays visually quieter than both:
   it is a secondary destination and should not compete with "Start a project".
   40px square rather than the 18px glyph so the hit area is a real target.
   Hidden below the desktop breakpoint, where the burger takes over and the
   mobile menu carries a labelled LinkedIn link instead of an icon. */
.nav-social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  /* No margin-left:auto here. .nav-links already has one, and two auto margins
     in the same flex row split the free space between them, opening a gap
     between the links and this icon. The container's own gap is enough. */
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--navy-800);
  transition: color 0.25s ease, border-color 0.25s ease, background 0.25s ease;
}
.nav-social svg { fill: currentColor; }
.nav-social:hover {
  color: var(--accent);
  border-color: rgba(185, 141, 47, 0.5);
  background: rgba(185, 141, 47, 0.08);
}
.nav-social:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (max-width: 1020px) { .nav-social { display: none; } }

/* Labelled, not an icon: in a stacked menu there is room for the word, and a
   bare glyph in a list of text links reads as an orphan. */
.mm-social {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--navy-600);
}
.mm-social::after {
  content: "";
  width: 11px;
  height: 11px;
  flex: none;
  background: currentColor;
  clip-path: polygon(0 22%, 12% 22%, 12% 88%, 78% 88%, 78% 100%, 0 100%,
                     0 22%, 40% 0, 100% 0, 100% 60%, 88% 60%, 88% 20%, 40% 20%);
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 42px;
  height: 42px;
  padding: 10px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  margin-left: auto;
}
.nav-toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--navy-800);
  border-radius: 2px;
  transition: transform 0.25s ease, opacity 0.25s ease;
}
.nav-open .nav-toggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.mobile-menu {
  display: none;
  flex-direction: column;
  gap: 4px;
  padding: 10px 24px 22px;
  background: var(--white);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}
/* Text links only. A bare `.mobile-menu a` outranks `.btn-primary` (0-1-1 beats
   0-1-0), so without the :not() the CTA in .mm-foot picks up slate text, a
   divider border and the wrong padding. */
.mobile-menu a:not(.btn) {
  padding: 12px 4px;
  font-weight: 500;
  color: var(--slate);
  border-bottom: 1px solid var(--line);
}
/* No :last-child button rule here on purpose. The menu used to be a flat list
   whose final <a> was the CTA, so styling a:last-child as a button worked. Once
   the links moved into .mm-group / .mm-foot wrappers that selector started
   matching the last link of EVERY group, painting a navy block behind "Services
   overview" and "Contact". The CTA carries .btn .btn-primary .btn-block and is
   fully styled by those, so it never needed the help. */
.nav-open .mobile-menu { display: flex; }

/* ============================================================
   HERO
   ============================================================ */
.hero {
  position: relative;
  padding: calc(var(--nav-h) + clamp(34px, 5.5vw, 62px)) 0 clamp(44px, 5.5vw, 70px);
  overflow: hidden;
  background: linear-gradient(170deg, var(--navy-700) 0%, var(--navy-800) 45%, var(--navy-900) 100%);
  color: var(--white);
}
.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: linear-gradient(to bottom, black 45%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 45%, transparent 100%);
  pointer-events: none;
}

.hero-watermark {
  position: absolute;
  right: -80px;
  top: 50%;
  transform: translateY(-50%);
  width: 480px;
  height: 480px;
  z-index: 0;
  pointer-events: none;
  background-color: #FFFFFF;
  opacity: 0.06;
  -webkit-mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
          mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(36px, 5vw, 70px);
  align-items: center;
  position: relative;
  z-index: 1;
}

.hero-copy h1 {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  font-weight: 600;
  letter-spacing: 0.02em;
  margin-bottom: 22px;
  color: var(--white);
}
.hero-copy .lede {
  color: var(--mist);
  font-size: 1.08rem;
  max-width: 560px;
  margin-bottom: 34px;
  line-height: 1.65;
}
.hero-cta { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 22px; }
.hero-note {
  color: var(--mist);
  font-size: 0.84rem;
  letter-spacing: 0.02em;
  opacity: 0.8;
}

/* Hero logo plaque */
.hero-visual { display: flex; justify-content: center; position: relative; z-index: 1; }
.hero-logo-card {
  background: var(--white);
  border-radius: calc(var(--radius) + 8px);
  padding: 20px;
  box-shadow: 0 18px 44px -16px rgba(0, 0, 0, 0.35);
}
.hero-logo-card img {
  display: block;
  width: clamp(200px, 25vw, 360px);
  height: auto;
  aspect-ratio: 1;
  border-radius: var(--radius);
}

/* Hero stats band */
.stats-row {
  margin-top: clamp(36px, 5vw, 56px);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.04);
  overflow: hidden;
  position: relative;
  z-index: 1;
}

/* ============================================================
   PROOF STRIP — client logos, directly under the hero
   ============================================================ */
.proof-strip {
  background: var(--white);
  border-bottom: 1px solid var(--line);
  padding: var(--s-4) 0;
}
.proof-inner {
  display: flex;
  align-items: center;
  gap: var(--s-4) var(--s-6);
  flex-wrap: wrap;
  justify-content: center;
}
.proof-label {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--slate);
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
}
.proof-label::after {
  content: "";
  width: 24px;
  height: 1px;
  background: var(--accent);
  opacity: 0.75;
}
.proof-logos {
  list-style: none;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-4) var(--s-6);
}
.proof-logos img {
  width: auto;
  height: auto;
  max-width: 150px;
  opacity: 0.72;
  filter: saturate(0.85);
  transition: opacity 0.25s ease, filter 0.25s ease;
}
.proof-logos li:hover img { opacity: 1; filter: none; }
/* Same optical-area balancing as the client cards, at strip scale. */
.proof-logos img            { max-height: 30px; }
.proof-logos .l-tall  img   { max-height: 38px; }
.proof-logos .l-wide  img   { max-height: 26px; }
.proof-logos .l-xwide img   { max-height: 22px; }

/* ============================================================
   SERVICE PILLARS
   ============================================================ */
.pillars {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-3);
}
.pillar {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}
.pillar:hover {
  transform: translateY(-4px);
  border-color: var(--navy-600);
  box-shadow: var(--shadow-md);
}
.pillar-lead {
  grid-column: 1 / -1;
  background: var(--ivory);
  border-color: rgba(6, 33, 90, 0.2);
}
.pillar-num {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.16em;
  color: var(--navy-600);
  margin-bottom: var(--s-2);
}
.pillar-num::after {
  content: "";
  display: block;
  width: 30px;
  height: 2px;
  background: var(--accent);
  margin-top: 9px;
  border-radius: 2px;
}
.pillar h3 {
  font-size: var(--t-h3);
  color: var(--navy-800);
  margin-bottom: var(--s-2);
}
.pillar > p {
  color: var(--slate);
  font-size: var(--t-body);
  line-height: 1.66;
  max-width: 62ch;
}

/* The three proven specialisms inside the lead pillar */
.specs {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}
.specs li { position: relative; padding-left: var(--s-3); }
.specs li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 4px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 0.75;
}
.specs h4 {
  font-family: var(--font-body);
  font-size: 0.87rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--navy-800);
  margin-bottom: 5px;
}
.specs p { color: var(--slate); font-size: var(--t-small); line-height: 1.55; }
.specs .proof-note {
  display: inline-block;
  margin-top: 7px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--navy-600);
}

/* ============================================================
   CAPABILITIES — the secondary "we also quote on" list
   ============================================================ */
.capabilities {
  margin-top: var(--s-5);
  padding: var(--s-4);
  border: 1px dashed rgba(6, 33, 90, 0.22);
  border-radius: var(--radius);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-4);
}
.capabilities h3 {
  font-family: var(--font-body);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--slate);
  white-space: nowrap;
}
.capabilities ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) 7px;
}
.capabilities li {
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--navy-800);
  background: var(--navy-100);
  border: 1px solid rgba(6, 33, 90, 0.14);
  border-radius: 999px;
  padding: 5px 13px;
}
.capabilities .cap-note {
  flex: 1 1 100%;
  color: var(--slate);
  font-size: var(--t-small);
  line-height: 1.6;
}

/* ============================================================
   SERVICES CARDS
   ============================================================ */
.cards-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

/* ============================================================
   WHY PAKISTAN
   ============================================================ */
.why-grid {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(32px, 5vw, 56px);
  align-items: center;
}
.why-copy h2 {
  font-size: clamp(1.9rem, 3.6vw, 2.7rem);
  margin-bottom: 20px;
  color: var(--navy-800);
}
.why-copy p {
  color: var(--slate);
  margin-bottom: 16px;
  max-width: 520px;
}
.why-copy p strong { color: var(--ink); font-weight: 600; }

.checklist {
  list-style: none;
  margin-top: 26px;
  display: grid;
  gap: 13px;
}
.checklist li {
  position: relative;
  padding-left: 34px;
  color: var(--slate);
  font-size: 0.95rem;
  line-height: 1.55;
}
.checklist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1px;
  width: 20px;
  height: 20px;
  border-radius: 6px;
  background: var(--navy-100);
  border: 1px solid rgba(6, 33, 90, 0.16);
}
.checklist li::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 5px;
  width: 9px;
  height: 5px;
  border-left: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
}

.why-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
.stat-tile {
  background: var(--ivory);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px 22px 20px;
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.stat-tile:hover { transform: translateY(-3px); border-color: var(--navy-600); }
.stat-tile strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--navy-800);
  position: relative;
  padding-bottom: 10px;
}
.stat-tile strong::after {
  content: "";
  display: block;
  width: 34px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
  margin-top: 10px;
}
.stat-tile span {
  color: var(--slate);
  font-size: 0.83rem;
  line-height: 1.5;
  display: block;
}

/* ============================================================
   PROCESS
   ============================================================ */
.steps-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
.step {
  position: relative;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px 24px;
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.step:hover { transform: translateY(-4px); border-color: var(--navy-600); }
.step-num {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: 0.14em;
  color: var(--navy-800);
  margin-bottom: 14px;
}
.step-num::after {
  content: "";
  display: block;
  width: 34px;
  height: 2px;
  background: var(--accent);
  margin-top: 10px;
  border-radius: 2px;
}
.step h3 {
  font-size: 1.12rem;
  margin-bottom: 10px;
  color: var(--navy-800);
}
.step p {
  color: var(--slate);
  font-size: 0.9rem;
  line-height: 1.6;
}

/* ============================================================
   GET A QUOTE
   ============================================================ */
.quote-panel {
  display: grid;
  grid-template-columns: 1.25fr 1fr;
  gap: var(--s-3);
  align-items: start;
}
.quote-terms,
.quote-ask {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
}
.quote-terms { background: var(--ivory); }
.quote-ask {
  background: var(--white);
  border-color: var(--navy-800);
  box-shadow: var(--shadow-md);
  position: relative;
}
.quote-panel h3 {
  font-size: var(--t-h3);
  color: var(--navy-800);
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.quote-terms ul,
.quote-ask ul { list-style: none; display: grid; gap: var(--s-3); }
.quote-terms li {
  position: relative;
  padding-left: 26px;
  color: var(--slate);
  font-size: var(--t-body);
  line-height: 1.62;
}
.quote-terms li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 0.85;
}
.quote-terms li strong {
  display: block;
  color: var(--navy-800);
  font-weight: 600;
  margin-bottom: 3px;
}
.quote-ask > p {
  color: var(--slate);
  font-size: var(--t-body);
  line-height: 1.62;
  margin-bottom: var(--s-3);
}
.quote-ask ul { gap: var(--s-2); margin-bottom: var(--s-4); }
.quote-ask li {
  position: relative;
  padding-left: 26px;
  color: var(--slate);
  font-size: var(--t-body);
  line-height: 1.5;
}
.quote-ask li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 2px;
  width: 15px;
  height: 8px;
  border-left: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg) scale(0.8);
  transform-origin: left center;
}
.quote-note {
  margin-top: var(--s-2);
  color: var(--slate);
  font-size: var(--t-small);
  text-align: center;
  line-height: 1.5;
}

/* ============================================================
   TECHNOLOGIES MARQUEE
   ============================================================ */
#technologies .section-head { margin-bottom: 34px; }
.marquee {
  overflow: hidden;
  padding: 12px 0;
  mask-image: linear-gradient(90deg, transparent, black 12%, black 88%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, black 12%, black 88%, transparent);
}
.marquee-track {
  display: flex;
  gap: 14px;
  width: max-content;
  animation: marquee-left 36s linear infinite;
}
.marquee[data-direction="right"] .marquee-track { animation-name: marquee-right; }
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marquee-left {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@keyframes marquee-right {
  from { transform: translateX(-50%); }
  to { transform: translateX(0); }
}
.tech-pill {
  flex: none;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.95rem;
  color: var(--slate);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 11px 22px;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.tech-pill:hover { border-color: var(--navy-600); color: var(--navy-800); }

/* ============================================================
   CTA BAND
   ============================================================ */
.cta-band {
  position: relative;
  background: linear-gradient(170deg, var(--navy-700) 0%, var(--navy-800) 50%, var(--navy-900) 100%);
  padding: 20px 0 clamp(56px, 7vw, 84px);
  overflow: hidden;
}
.cta-watermark {
  position: absolute;
  right: -40px;
  bottom: -60px;
  width: 320px;
  height: 320px;
  pointer-events: none;
  z-index: 0;
  background-color: #FFFFFF;
  opacity: 0.06;
  -webkit-mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
          mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
}
.cta-panel {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: clamp(40px, 5vw, 64px) 28px;
}
.cta-panel h2 {
  font-size: clamp(1.8rem, 3.8vw, 2.7rem);
  margin-bottom: 14px;
  color: var(--white);
}
.cta-panel p {
  color: var(--mist);
  font-size: 1.05rem;
  margin-bottom: 30px;
}

/* ============================================================
   CONTACT
   ============================================================ */
.contact-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(32px, 5vw, 60px);
  align-items: start;
}
.contact-info {
  display: grid;
  gap: 26px;
  position: relative;
  background: var(--ivory);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px 28px;
  overflow: hidden;
}
.info-item { display: flex; gap: 16px; align-items: flex-start; position: relative; z-index: 1; }
.info-icon {
  flex: none;
  width: 42px;
  height: 42px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  background: var(--navy-100);
  border: 1px solid rgba(6, 33, 90, 0.14);
  color: var(--navy-800);
}
.info-icon svg { width: 21px; height: 21px; }
.info-item h4 {
  font-family: var(--font-body);
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: 4px;
}
.info-item a { color: var(--navy-800); font-weight: 600; }
.info-item a:hover { text-decoration: underline; }
.info-item p { color: var(--slate); font-size: 0.95rem; }
.info-watermark {
  position: absolute;
  right: -30px;
  bottom: -36px;
  width: 150px;
  height: 150px;
  pointer-events: none;
  z-index: 0;
  background-color: #06215A;
  opacity: 0.07;
  -webkit-mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
          mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
}

.contact-form {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px 28px;
}
.field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 18px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field label {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--navy-800);
}
.field input,
.field select,
.field textarea {
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--ivory);
  border: 1px solid var(--line);
  border-radius: 11px;
  padding: 13px 15px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
  width: 100%;
}
.field input::placeholder,
.field textarea::placeholder { color: #9aabbc; }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--navy-600);
  box-shadow: 0 0 0 3px rgba(6, 33, 90, 0.1);
}
.field select {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%235A6A85' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
  background-color: var(--ivory);
  padding-right: 40px;
}
.field textarea { resize: vertical; min-height: 120px; }
/* Honeypot. Off-screen rather than display:none, because some bots skip
   fields that are explicitly hidden. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.form-note {
  margin-top: 16px;
  color: var(--slate);
  font-size: 0.82rem;
  text-align: center;
}
.form-note a { color: var(--navy-800); font-weight: 600; }
.form-note a:hover { text-decoration: underline; }

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer {
  position: relative;
  background: var(--navy-900);
  color: var(--white);
  padding: 46px 0 26px;
  overflow: hidden;
}
.footer-watermark {
  position: absolute;
  right: -100px;
  bottom: -100px;
  width: 380px;
  height: 380px;
  pointer-events: none;
  z-index: 0;
  background-color: #FFFFFF;
  opacity: 0.05;
  -webkit-mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
          mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
}
.footer-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.3fr 1fr 1fr;
  gap: 40px;
  margin-bottom: 44px;
}
.footer-brand {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.footer-brand-mark {
  width: 52px;
  height: 52px;
  color: var(--white);
  margin-bottom: 6px;
}
.footer-monogram { width: 100%; height: 100%; }
.footer-wordmark {
  display: flex;
  flex-direction: column;
  line-height: 1;
}
.fw-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.06rem;
  letter-spacing: 0.18em;
}
.fw-sub {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.55rem;
  letter-spacing: 0.21em;
  color: var(--mist);
  margin-top: 5px;
}
.footer-tagline {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--mist);
  margin: 4px 0 6px;
}
.footer-blurb {
  color: var(--mist);
  font-size: 0.88rem;
  max-width: 300px;
  margin-top: 8px;
  line-height: 1.6;
}
.footer-col h4 {
  font-family: var(--font-body);
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--mist);
  margin-bottom: 16px;
}
/* Text links only. `.footer-col a` is 0-1-1 and would outrank `.btn-white`
   (0-1-0), painting white text on the white "Start a project" button in the
   contact column — a button that reads as an empty white slab. */
.footer-col a:not(.btn) {
  display: block;
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.92rem;
  padding: 5px 0;
  transition: color 0.2s ease;
}
.footer-col a:not(.btn):hover { color: var(--white); }
.footer-bottom {
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--line-dark);
  padding-top: 24px;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  color: var(--mist);
  font-size: 0.84rem;
}

/* ============================================================
   SELECTED WORK — navy inversion band
   ============================================================ */
.work-band {
  position: relative;
  background: linear-gradient(170deg, var(--navy-700) 0%, var(--navy-800) 48%, var(--navy-900) 100%);
  color: var(--white);
  padding: clamp(56px, 7vw, 84px) 0;
  overflow: hidden;
}
.work-band .container { position: relative; z-index: 1; }
.work-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: linear-gradient(to bottom, transparent, black 30%, black 70%, transparent);
  -webkit-mask-image: linear-gradient(to bottom, transparent, black 30%, black 70%, transparent);
}
.work-watermark {
  position: absolute;
  left: -110px;
  bottom: -110px;
  width: 400px;
  height: 400px;
  z-index: 0;
  pointer-events: none;
  background-color: #FFFFFF;
  opacity: 0.05;
  -webkit-mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
          mask: url("../assets/phalanx-mark.svg") center / contain no-repeat;
}
.work-band .section-head h2 { color: var(--white); }
.work-band .section-head .lede { color: var(--mist); }

/* Proof strip reuses .stats-row / .stat from the hero */
.work-band .stats-row { margin-top: 0; margin-bottom: clamp(32px, 4vw, 44px); }

.work-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}
.client-card {
  display: flex;
  flex-direction: column;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  padding: 20px;
  transition: transform 0.25s ease, border-color 0.25s ease, background 0.25s ease;
}
.client-card:hover {
  transform: translateY(-4px);
  border-color: rgba(255, 255, 255, 0.32);
  background: rgba(255, 255, 255, 0.08);
}

/* White plaque normalises client logos of any polarity */
.client-plaque {
  height: 100px;
  display: grid;
  place-items: center;
  padding: 12px 20px;
  margin-bottom: 16px;
  background: var(--white);
  border-radius: 10px;
  box-shadow: 0 10px 26px -16px rgba(0, 0, 0, 0.55);
}
.client-plaque img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 61px;
  object-fit: contain;
}
/* Logos are balanced by optical AREA, not by matching heights — a portrait
   lockup at the same height as a wide one looks half the size. Each step down
   in height compensates for a wider aspect ratio, landing all four within
   ~10.2k–11.5k px² of rendered area.
     plaque-tall  Apex    279×320  (0.87:1)
     (default)    Minawa  396×314  (1.26:1)
     plaque-wide  Pamir   394×209  (1.88:1)
     plaque-xwide Delight 400×152  (2.63:1)                                  */
.client-plaque.plaque-tall  img { max-height: 76px; }
.client-plaque.plaque-wide  img { max-height: 52px; }
.client-plaque.plaque-xwide img { max-height: 43px; }
/* No-logo fallback: a typographic lockup for a client with no mark of their
   own. Currently unused — every client has a logo — but keep it: inventing a
   mark for a client is worse than either using this or leaving the plaque out. */
.client-wordmark {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
}

.client-body { display: flex; flex-direction: column; flex: 1; }
.client-name {
  font-family: var(--font-display);
  font-size: 1.04rem;
  font-weight: 600;
  color: var(--white);
  line-height: 1.3;
  margin-bottom: 4px;
}
.client-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  vertical-align: middle;
  margin-left: 8px;
  font-family: var(--font-body);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #86EFAC;
  border: 1px solid rgba(134, 239, 172, 0.4);
  border-radius: 999px;
  padding: 3px 10px;
}
.client-live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4ADE80;
  box-shadow: 0 0 0 3px rgba(74, 222, 128, 0.2);
}
.client-sector {
  font-family: var(--font-body);
  font-size: 0.67rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}
.client-brief {
  color: var(--mist);
  font-size: 0.875rem;
  line-height: 1.58;
  margin-bottom: 16px;
}
.client-brief strong { color: var(--white); font-weight: 600; }
.client-tags {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: auto;
}
.client-tags li {
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--mist);
  border: 1px solid var(--line-dark);
  border-radius: 999px;
  padding: 3px 10px;
}
.work-cta {
  margin-top: clamp(30px, 4vw, 40px);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 22px;
}
.work-cta p {
  color: var(--mist);
  font-size: 0.9rem;
  max-width: 460px;
}

/* ============================================================
   REVEAL ANIMATIONS
   ============================================================ */
/* Reveal is opt-in: the hidden state only applies once main.js has confirmed
   it can run AND that it will observe these elements. If the script is blocked,
   fails, or throws before the observer is wired, `.js-reveal` is never added
   and every section simply renders visible. Content must never depend on JS. */
/* The three --cho-* knobs are what [data-choreo] writes to (see CHOREOGRAPHY,
   further down). Their fallbacks here are the pre-choreography values, so a
   section that declares nothing behaves exactly as it did before. */
.js-reveal .reveal {
  opacity: 0;
  transform: translate3d(var(--cho-x, 0px), var(--cho-y, var(--reveal-distance)), 0) scale(var(--cho-s, 1));
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.js-reveal .reveal.visible { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }

/* ============================================================
   RESPONSIVE
   ============================================================ */
/* Laptops with short viewports (1366×768 and similar): the hero is the only
   band tall enough to push its own stats off-screen, so trim its vertical
   rhythm rather than letting the fold land mid-headline. */
@media (max-height: 820px) and (min-width: 1021px) {
  .hero { padding-bottom: clamp(34px, 4vw, 48px); }
  .hero-copy .lede { margin-bottom: 26px; }
  .hero-logo-card img { width: clamp(190px, 21vw, 300px); }
}

@media (max-width: 1180px) {
  .nav-links { gap: clamp(14px, 1.8vw, 26px); }
}

@media (max-width: 1020px) {
  .hero-grid { grid-template-columns: 1fr; }
  /* The visual used to lead on narrow screens. The topology that replaced the
     old plaque is far heavier, and putting it first pushed the headline — the
     LCP element and the actual message — below the fold. Copy leads now. */
  .hero-visual { order: 1; margin-top: 38px; }
  .hero-logo-card { padding: 14px; }
  .hero-logo-card img { width: clamp(180px, 32vw, 260px); }
  .why-grid { grid-template-columns: 1fr; }
  .steps-grid { grid-template-columns: 1fr 1fr; }
  .quote-panel { grid-template-columns: 1fr; }

  .nav-links, .nav-cta { display: none; }
  .nav-toggle { display: flex; }

  .contact-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
}

/* Tablet: the client cards are compact enough to hold two columns well below
   the old 1020px cutoff, so they only drop to one here. */
@media (max-width: 800px) {
  .work-grid { grid-template-columns: 1fr; }
  .specs { grid-template-columns: 1fr; gap: var(--s-3); }
  .proof-inner { gap: var(--s-3) var(--s-4); }
  .proof-logos { gap: var(--s-3) var(--s-4); }
}

@media (max-width: 620px) {
  .pillars { grid-template-columns: 1fr; }
  .capabilities { flex-direction: column; align-items: flex-start; }
  .proof-label { width: 100%; justify-content: center; }
}

@media (max-width: 640px) {
  .steps-grid { grid-template-columns: 1fr; }
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .footer-grid { grid-template-columns: 1fr; gap: 30px; }
  .footer-bottom { justify-content: center; text-align: center; }

  .client-card { padding: 18px 16px; }
  .client-plaque { height: 88px; padding: 10px 16px; }
  .work-cta .btn { width: 100%; }
}

/* ============================================================
   REDUCED MOTION
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js-reveal .reveal { opacity: 1; transform: none; transition: none; }
  .marquee-track { animation: none !important; flex-wrap: wrap; width: auto; justify-content: center; }
  .btn, .card, .step, .stat-tile, .pillar, .client-card { transition: none; }
}
/* ============================================================
   ==  REDESIGN 2026-07-29  ===================================
   Multi-page architecture. Components below supersede the
   single-page ones above; dead rules get swept once every
   page in the sitemap is built.
   ============================================================ */

/* ---------- Nav: services dropdown ---------- */
.nav-drop { position: relative; }
.nav-drop.open .nav-drop-trigger svg { transform: rotate(180deg); }
.nav-drop.open .nav-menu { opacity: 1; visibility: visible; transform: none; }
.nav-links a[aria-current="page"] { color: var(--navy-800); font-weight: 600; }

/* ---------- Mobile menu ---------- */
.mm-group { padding: 4px 0 14px; border-bottom: 1px solid var(--line); }
.mm-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: 4px;
}
.mobile-menu .mm-group a {
  display: block;
  padding: 11px 2px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 1rem;
  color: var(--navy-800);
  border-bottom: none;
}
.mm-foot { padding-top: 16px; display: grid; gap: 12px; }
.mm-email { font-size: 0.9rem; color: var(--navy-600); font-weight: 600; }

/* ---------- Home hero ---------- */
.hero-home .hero-grid { align-items: center; }
.hero-home h1 {
  font-family: var(--font-head);
  font-size: var(--t-display);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin-bottom: var(--s-3);
}
.hero-home .lede { max-width: 52ch; }

/* ============================================================
   HERO SYSTEM CORE  (replaces .topo)
   ============================================================
   Rotation is always about the viewBox centre, never about an element's own
   box. `transform-box: fill-box` would rotate each gold arc around the middle
   of its own bounding box, which for a 70-degree arc segment is nowhere near
   160,160 and sends it wandering off. view-box plus an explicit origin is the
   only version that is correct for every shape here.

   Sixteen animated selectors drive sixty six elements: the bezel's 24 ticks
   share one rotating group, the orbits carry their nodes, and the packets
   share one keyframe with a per element delay. That ratio is the whole trick. */
.hero-core {
  position: relative;
  width: 100%;
  max-width: min(520px, 92vw);
  aspect-ratio: 1;
  margin-inline: auto;
}
.fx-core { width: 100%; height: 100%; overflow: visible; }

.fx-core [class^="cr-"],
.fx-core [class*=" cr-"] {
  transform-box: view-box;
  transform-origin: 160px 160px;
}

/* ---- shell ---- */
.cr-glow { fill: url(#coreGlow); }
.cr-ring-a, .cr-ring-b { fill: none; stroke: rgba(255, 255, 255, 0.16); stroke-width: 1; }
.cr-ring-a { stroke-dasharray: 2 10; animation: cr-spin 78s linear infinite; }
.cr-ring-b { stroke-dasharray: 26 14; stroke: rgba(255, 255, 255, 0.10); animation: cr-spin 56s linear infinite reverse; }

/* ---- bezel: 24 ticks, ONE animation ---- */
.cr-bezel { animation: cr-spin 150s linear infinite; }
.cr-bezel line { stroke: rgba(255, 255, 255, 0.22); stroke-width: 1; }
.cr-bezel .tk-major { stroke: rgba(185, 141, 47, 0.75); stroke-width: 1.6; }

/* ---- gold arcs, three speeds ---- */
.cr-arcs path { fill: none; stroke: var(--accent); stroke-linecap: round; }
.cr-arc-1 { stroke-width: 2.2; opacity: 0.85; animation: cr-spin 22s linear infinite; }
.cr-arc-2 { stroke-width: 1.6; opacity: 0.6;  animation: cr-spin 34s linear infinite reverse; }
.cr-arc-3 { stroke-width: 1.2; opacity: 0.45; animation: cr-spin 16s linear infinite; }

/* ---- radar sweep ---- */
.cr-sweep path { fill: url(#sweepFade); }
.cr-sweep { opacity: 0.5; animation: cr-spin 9s linear infinite; }

/* ---- lattice ---- */
.cr-spokes line { stroke: rgba(255, 255, 255, 0.09); stroke-width: 1; }

/* ---- packets: one keyframe, eight delays ---- */
.cr-packets path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 10 90;
  stroke-dashoffset: 100;
  opacity: 0;
  animation: cr-packet 3.4s linear infinite;
  animation-delay: calc(var(--i) * 0.42s);
}
@keyframes cr-packet {
  0%   { stroke-dashoffset: 100; opacity: 0; }
  12%  { opacity: 0.95; }
  80%  { opacity: 0.95; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}

/* ---- orbits ---- */
.cr-track { fill: none; stroke: rgba(255, 255, 255, 0.08); stroke-width: 1; }
.cr-orbit circle:not(.cr-track) { fill: var(--accent); }
.cr-orbit-a { animation: cr-spin 40s linear infinite; }
.cr-orbit-b { animation: cr-spin 28s linear infinite reverse; }
.cr-orbit-a circle:not(.cr-track) { filter: drop-shadow(0 0 4px rgba(185, 141, 47, 0.75)); }
.cr-orbit-b circle:not(.cr-track) { fill: rgba(255, 255, 255, 0.75); }

/* ---- core ---- */
.cr-pulse { fill: none; stroke: var(--accent); stroke-width: 1.2; opacity: 0; }
.cr-pulse-1 { animation: cr-pulse 4.2s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; }
.cr-pulse-2 { animation: cr-pulse 4.2s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; animation-delay: 2.1s; }
@keyframes cr-pulse {
  0%   { transform: scale(0.62); opacity: 0; }
  18%  { opacity: 0.7; }
  100% { transform: scale(1.75); opacity: 0; }
}
.cr-hex {
  fill: rgba(11, 42, 110, 0.55);
  stroke: rgba(185, 141, 47, 0.55);
  stroke-width: 1.2;
  animation: cr-spin 60s linear infinite reverse;
}
.cr-disc {
  fill: rgba(4, 24, 56, 0.9);
  stroke: rgba(185, 141, 47, 0.9);
  stroke-width: 1.4;
  animation: cr-breathe 6s ease-in-out infinite;
}
@keyframes cr-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.05); }
}
.cr-seed { fill: var(--accent); animation: cr-seed 2.6s ease-in-out infinite; }
@keyframes cr-seed {
  0%, 100% { transform: scale(1); opacity: 0.85; }
  50%      { transform: scale(1.35); opacity: 1; }
}

.cr-frame path { fill: none; stroke: rgba(255, 255, 255, 0.20); stroke-width: 1.2; }

@keyframes cr-spin { to { transform: rotate(360deg); } }

/* Offscreen pause, same mechanism the page heroes use. */
.hero-core.fx-paused * { animation-play-state: paused !important; }

@media (max-width: 1020px) {
  .hero-core { max-width: min(400px, 78vw); }
}

/* ============================================================
   HERO ATMOSPHERE  (canvas packets, cursor spotlight)
   ============================================================ */
/* The canvas paints ONLY the drifting packets. The blueprint grid stays a CSS
   background on .hero-bg so it never enters the repaint loop at all. */
.hero-particles {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.8s ease;
}
.hero-particles.lit { opacity: 1; }

/* Composite-only spotlight: a static radial gradient moved with translate3d.
   `screen` lifts the grid lines underneath instead of washing the copy out,
   and the element sits below .hero-grid so text is never blended. */
.hero-spotlight {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
  width: 620px;
  height: 620px;
  margin-left: -310px;
  margin-top: -310px;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle closest-side,
              rgba(185, 141, 47, 0.20) 0%,
              rgba(122, 150, 210, 0.10) 42%,
              rgba(255, 255, 255, 0) 72%);
  mix-blend-mode: screen;
  transition: opacity 0.45s ease;
}
/* Promoted only while it is actually being moved. .lit goes on at pointerenter
   and off at pointerleave, so the layer exists for the seconds the cursor is
   over the hero rather than for the life of the page. */
.hero-spotlight.lit { opacity: 1; will-change: transform; }

/* ---------- gradient headline phrase ---------- */
/* Fallback first: without background-clip:text this stays mist, which is what
   the phrase used to be. Every stop in the gradient is light enough to clear
   4.5:1 on the navy behind it — the darkest is mist at ~9.8:1. */
.grad-text { color: var(--mist); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .grad-text {
    background: linear-gradient(100deg,
      #B8C4D9 0%, #FFFFFF 30%, #E3C285 52%, #FFFFFF 74%, #B8C4D9 100%);
    background-size: 260% 100%;
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    /* ONE sweep on arrival, then static — not an infinite loop.
       background-position is a paint property and this is the LCP element, so
       looping it would repaint the largest text on the page for as long as the
       tab is open. A single pass reads as "the light moves across it" and then
       costs nothing forever after. */
    animation: grad-drift 1.5s cubic-bezier(0.22, 0.61, 0.36, 1) 1 both;
  }
}
@keyframes grad-drift {
  from { background-position: 100% 50%; }
  to   { background-position: 38% 50%; }
}

/* ---------- primary CTA: shimmer + magnetic ---------- */
.btn-shimmer { position: relative; overflow: hidden; isolation: isolate; }
.btn-shimmer::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  width: 45%;
  height: 100%;
  transform: translateX(-160%) skewX(-18deg);
  background: linear-gradient(90deg, transparent, rgba(6, 33, 90, 0.16), transparent);
  pointer-events: none;
}
/* 400ms: a UI response to a hover should land inside the ~500ms ceiling, and
   at 850ms the sheen was still crossing after the pointer had moved on. */
.btn-shimmer:hover::after,
.btn-shimmer:focus-visible::after { animation: btn-sheen 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); }
@keyframes btn-sheen { to { transform: translateX(320%) skewX(-18deg); } }
/* main.js drives this offset from pointer proximity, and does its own easing in
   the rAF loop. There is deliberately NO transform transition here: the offset
   is rewritten every frame, and a transition would be restarted by each write —
   so it would never reach its target and would cost a style recalc per frame.
   The hover lift folds into the same transform so the two cannot overwrite
   each other. */
.btn[data-magnetic] {
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
  transition: box-shadow 0.2s ease, background 0.2s ease, color 0.2s ease;
}
.btn-white[data-magnetic]:hover { transform: translate3d(var(--mx, 0px), calc(var(--my, 0px) - 2px), 0); }

/* ---------- secondary CTA: border glow + arrow ---------- */
.btn-arrow { gap: 10px; position: relative; }
.btn-arrow-glyph {
  width: 20px;
  height: 10px;
  flex: none;
  transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
.btn-arrow:hover .btn-arrow-glyph,
.btn-arrow:focus-visible .btn-arrow-glyph { transform: translateX(4px); }
.btn-ghost-on-navy.btn-arrow:hover {
  border-color: rgba(185, 141, 47, 0.75);
  box-shadow: 0 0 0 1px rgba(185, 141, 47, 0.22), 0 0 22px -8px rgba(185, 141, 47, 0.6);
}

/* ---------- client logo rail ---------- */
.marquee-logos { flex: 1; min-width: 0; }
.marquee-logos .marquee-track {
  align-items: center;
  gap: var(--s-6);
  animation-duration: 46s;
  list-style: none;
}
.marquee-logos .marquee-track li { flex: none; display: flex; align-items: center; }
.marquee-logos img {
  filter: grayscale(1);
  opacity: 0.55;
  transition: filter 0.3s ease, opacity 0.3s ease;
}
.marquee-logos li:hover img { filter: none; opacity: 1; }

/* ---------- Link with arrow ---------- */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--navy-800);
  white-space: nowrap;
}
.link-arrow::after {
  content: "";
  width: 16px;
  height: 8px;
  background: currentColor;
  clip-path: polygon(0 42%, 68% 42%, 68% 0, 100% 50%, 68% 100%, 68% 58%, 0 58%);
  transition: transform 0.2s ease;
}
.link-arrow:hover::after { transform: translateX(4px); }
.link-sm { font-size: 0.84rem; }

.section-head-row {
  max-width: none;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

/* ---------- Two capabilities ---------- */
.cap-split { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--s-4); align-items: stretch; }
.cap-card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}
.cap-card:hover { transform: translateY(-4px); border-color: var(--navy-600); box-shadow: var(--shadow-md); }
.cap-num { font-family: var(--font-display); font-weight: 700; font-size: 0.82rem; letter-spacing: 0.16em; color: var(--navy-600); }
.cap-num::after {
  content: "";
  display: block;
  width: 30px;
  height: 2px;
  background: var(--accent);
  margin: 9px 0 var(--s-3);
  border-radius: 2px;
}
.cap-card h3 { font-size: var(--t-h3); color: var(--navy-800); margin-bottom: var(--s-2); }
.cap-card > p { color: var(--slate); font-size: var(--t-body); line-height: 1.66; margin-bottom: var(--s-3); }
.cap-list { list-style: none; display: grid; gap: 9px; margin-bottom: var(--s-4); }
.cap-list li { position: relative; padding-left: 22px; color: var(--slate); font-size: var(--t-small); }
.cap-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 0.8;
}
.cap-card .link-arrow { margin-top: auto; }
.cap-bridge { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: var(--s-5) 0; }
.cap-bridge-line { flex: 1; width: 1px; background: linear-gradient(var(--line), rgba(185,141,47,0.5), var(--line)); }
.cap-bridge-label {
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  writing-mode: vertical-rl;
}

/* ---------- Featured work ---------- */
.feature-grid { display: grid; grid-template-columns: 1.35fr 1fr; gap: var(--s-3); }
.feature-main {
  display: flex;
  flex-direction: column;
  background: var(--ivory);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.feature-visual { background: linear-gradient(160deg, var(--navy-700), var(--navy-900)); padding: var(--s-4); }
.feature-body { padding: var(--s-4); display: flex; flex-direction: column; gap: var(--s-2); }
.feature-meta, .tile-meta {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 10px;
}
.feature-body h3 { font-size: var(--t-h3); color: var(--navy-800); }
.feature-body p { color: var(--slate); font-size: var(--t-body); line-height: 1.62; }
.feature-body p strong { color: var(--navy-800); font-weight: 600; }
.feature-tags { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s-1); }
.feature-tags li {
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--slate);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 10px;
}
.feature-main .link-arrow { margin-top: auto; padding-top: var(--s-2); }

.feature-side { display: grid; gap: var(--s-3); align-content: start; }
.tile {
  display: grid;
  gap: 7px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-3);
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.tile:hover { transform: translateY(-3px); border-color: var(--navy-600); }
.tile h3 { font-size: 1rem; color: var(--navy-800); }
.tile p { color: var(--slate); font-size: var(--t-small); line-height: 1.55; }
.tile-visual { height: 74px; border-radius: 9px; overflow: hidden; }
.dot-live { display: inline-flex; align-items: center; gap: 5px; color: #15803D; letter-spacing: 0.1em; }
.dot-live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #16A34A;
  box-shadow: 0 0 0 3px rgba(22,163,74,0.16);
}

/* ---------- Journey ---------- */
.journey { list-style: none; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); }
.journey-step {
  position: relative;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
  display: grid;
  gap: 8px;
  align-content: start;
}
.journey-step::before {
  content: "";
  position: absolute;
  top: calc(var(--s-4) + 8px);
  right: calc(var(--s-3) * -1);
  width: var(--s-3);
  height: 1px;
  background: var(--line);
}
.journey-step:last-child::before { display: none; }
.journey-num { font-family: var(--font-display); font-weight: 700; font-size: 0.8rem; letter-spacing: 0.16em; color: var(--navy-600); }
.journey-step h3 { font-size: 1.04rem; color: var(--navy-800); }
.journey-step p { color: var(--slate); font-size: var(--t-small); line-height: 1.58; }
.journey-artifact {
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px dashed rgba(6,33,90,0.2);
  font-size: 0.72rem;
  color: var(--navy-600);
  font-weight: 500;
}

/* ---------- Why split + matrix ---------- */
.why-split { display: grid; grid-template-columns: 1fr 1.05fr; gap: var(--s-6); align-items: start; }
.why-copy h2 { font-size: var(--t-h2); color: var(--navy-800); margin-bottom: var(--s-3); }
.why-copy > p { color: var(--slate); margin-bottom: var(--s-3); max-width: 54ch; }
.why-copy .checklist { margin-bottom: var(--s-4); }

.matrix { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--ivory); }
.matrix-head, .matrix-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1.15fr;
  gap: var(--s-2);
  padding: 13px var(--s-3);
  align-items: center;
}
.matrix-head {
  background: var(--navy-800);
  color: var(--white);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.matrix-head .matrix-us { color: var(--accent); }
.matrix-row { border-top: 1px solid var(--line); font-size: var(--t-small); line-height: 1.45; }
.matrix-row:nth-child(odd) { background: var(--white); }
.matrix-topic { font-weight: 600; color: var(--navy-800); }
.matrix-row .matrix-them { color: var(--slate); }
.matrix-row .matrix-us { color: var(--ink); font-weight: 500; }

/* ---------- CTA band ---------- */
.cta-panel p { max-width: 60ch; margin-inline: auto; }
.cta-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--s-3) var(--s-4); }
.cta-email { color: var(--mist); font-size: var(--t-small); border-bottom: 1px solid rgba(255,255,255,0.3); padding-bottom: 2px; }
.cta-email:hover { color: var(--white); border-bottom-color: var(--white); }

/* ---------- Footer (5 columns) ---------- */
.site-footer .footer-grid { grid-template-columns: 1.4fr repeat(4, 1fr); gap: var(--s-4); }
.footer-col h2 {
  font-family: var(--font-body);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--mist);
  margin-bottom: 14px;
}
.footer-email { display: block; color: var(--white); font-weight: 600; font-size: 0.9rem; margin-bottom: 10px; }
.footer-email:hover { text-decoration: underline; }
.footer-meta { color: var(--mist); font-size: 0.8rem; line-height: 1.55; margin-bottom: 8px; }
.footer-contact .btn { margin-top: 6px; }
.footer-tagline { font-family: var(--font-display); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.12em; color: var(--accent); margin: 6px 0; }

/* ---------- Redesign responsive ---------- */
@media (max-width: 1100px) {
  .site-footer .footer-grid { grid-template-columns: 1fr 1fr 1fr; }
  .feature-grid { grid-template-columns: 1fr; }
  .why-split { grid-template-columns: 1fr; }
  .journey { grid-template-columns: 1fr 1fr; }
  .journey-step::before { display: none; }
}
@media (max-width: 860px) {
  .cap-split { grid-template-columns: 1fr; }
  .cap-bridge { flex-direction: row; padding: 0; }
  .cap-bridge-line { width: auto; height: 1px; background: linear-gradient(90deg, var(--line), rgba(185,141,47,0.5), var(--line)); }
  .cap-bridge-label { writing-mode: horizontal-tb; }
}
@media (max-width: 640px) {
  .site-footer .footer-grid { grid-template-columns: 1fr; }
  .journey { grid-template-columns: 1fr; }
  .matrix-head, .matrix-row { grid-template-columns: 1fr; gap: 4px; }
  .matrix-head { display: none; }
  /* The label comes from the row's own data-label, which the page fills from
     its own matrix header. It used to be hard-coded to the home page's column
     names, so on services.html every "Software Development" cell was announced
     as "Split vendors" and every "Systems Integration" cell as "Phalanx". */
  .matrix-row .matrix-them::before { content: attr(data-label) ": "; font-weight: 600; }
  .matrix-row .matrix-us::before { content: attr(data-label) ": "; font-weight: 600; color: var(--navy-600); }
  .section-head-row { flex-direction: column; align-items: flex-start; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-arrow-glyph { transition: none !important; }
  /* Same resting position the one-shot sweep settles on, reached without it. */
  .grad-text { animation: none !important; background-position: 38% 50%; }
  .btn-shimmer::after { display: none; }
  .btn[data-magnetic] { transform: none !important; transition: none !important; }
  /* The canvas never starts and the spotlight never binds (see main.js); these
     are belt-and-braces in case the media query flips after load. */
  .hero-particles,
  .hero-spotlight { display: none !important; }
  .link-arrow::after { transition: none; }
}

/* ---------- Interior page hero ---------- */
.page-hero {
  position: relative;
  overflow: hidden;
  background: linear-gradient(170deg, var(--navy-700) 0%, var(--navy-800) 45%, var(--navy-900) 100%);
  color: var(--white);
  padding: calc(var(--nav-h) + clamp(34px, 5vw, 60px)) 0 clamp(44px, 5.5vw, 68px);
}
.page-hero .container { position: relative; z-index: 1; }
.page-hero h1 {
  font-family: var(--font-head);
  font-size: clamp(1.95rem, 1.4rem + 2.4vw, 3.1rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.12;
  color: var(--white);
  margin-bottom: var(--s-3);
  max-width: 20ch;
}
.page-hero .lede { color: var(--mist); font-size: var(--t-lede); max-width: 62ch; line-height: 1.68; }
.page-hero-short { padding-bottom: clamp(36px, 4.5vw, 56px); }
.crumb { font-size: 0.82rem; color: var(--mist); margin-bottom: var(--s-3); }
.crumb a { border-bottom: 1px solid rgba(255,255,255,0.3); padding-bottom: 1px; }
.crumb a:hover { border-bottom-color: var(--white); }
.crumb span { opacity: 0.5; margin: 0 8px; }

/* ---------- Services: combos + local note ---------- */
.combo-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); }
.combo {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.combo:hover { transform: translateY(-3px); border-color: var(--navy-600); }
.combo h3 { font-size: 1rem; color: var(--navy-800); margin-bottom: 8px; }
.combo p { color: var(--slate); font-size: var(--t-small); line-height: 1.58; }


/* ---------- Service pillars ----------
   Six numbered cards on the services page. Built on the same card treatment as
   .combo rather than a new one: the lift, the gold top edge and the reveal all
   come from the shared block further down, and only the numbered head and the
   stack line are new here. */
.svc-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-3); }
.svc {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(24px, 2.8vw, 36px);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              border-color 0.45s ease;
}
.svc::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(90deg, var(--accent), rgba(185, 141, 47, 0));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.svc:hover {
  transform: translateY(-5px);
  box-shadow: 0 18px 44px -22px rgba(4, 24, 56, 0.42);
  border-color: rgba(185, 141, 47, 0.42);
}
.svc:hover::before { transform: scaleX(1); }

.svc-head {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-3);
  align-items: start;
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--line);
}
/* Cinzel, because the numeral is a brand accent rather than body text - the
   same role it plays in .cap-num and .prod-code. */
.svc-num {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--navy-800);
  line-height: 1;
  letter-spacing: -0.01em;
}
.svc-den { font-size: 0.72rem; color: var(--slate); letter-spacing: 0.06em; }
.svc-head h3 { font-size: var(--t-h3); color: var(--navy-800); margin-bottom: 5px; }
.svc-stack {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* navy-600, not the gold accent: --accent on white is 3.0:1 and fails AA for
     text at this size. Same call as .brochure-kicker. */
  color: var(--navy-600);
}
.svc > p { color: var(--slate); font-size: var(--t-small); line-height: 1.65; margin-bottom: var(--s-3); }
.svc .cap-list { margin-bottom: var(--s-3); }
/* The disclosure sits at the bottom edge of every card regardless of how much
   copy is above it, so a row of cards has one aligned row of controls. */
.svc .cap-more { margin-top: auto; }

@media (max-width: 900px) {
  .svc-grid { grid-template-columns: 1fr; }
}

/* ---------- Learn more disclosure ----------
   Native <details>. Not a modal and not four extra pages: it opens with JS
   disabled, it is keyboard operable without any ARIA of ours, and browser
   find-in-page can still reach the closed content.

   Only the marker animates. The panel itself appears without a height
   transition because <details> cannot animate its own open state, and faking it
   means scripting the element and breaking find-in-page. */
.cap-more { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.cap-more > summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.86rem;
  color: var(--navy-800);
}
.cap-more > summary::-webkit-details-marker { display: none; }
.cap-more > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 1.6px solid var(--accent);
  border-bottom: 1.6px solid var(--accent);
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.cap-more[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.cap-more > summary:hover { color: var(--accent); }
.cap-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.cap-more-body { animation: faq-settle 0.32s cubic-bezier(0.16, 1, 0.3, 1); }
.cap-more-body h4 {
  font-family: var(--font-head);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--slate);
  margin: 14px 0 6px;
}
.cap-more-body p { color: var(--slate); font-size: var(--t-small); line-height: 1.6; }
.cap-more-body ul { list-style: none; display: grid; gap: 9px; margin: 0; padding: 0; }
.cap-more-body li {
  position: relative;
  padding-left: 15px;
  color: var(--slate);
  font-size: var(--t-small);
  line-height: 1.55;
}
.cap-more-body li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 5px;
  height: 5px;
  border-radius: 1px;
  background: var(--accent);
}
.cap-more-body strong { color: var(--navy-800); font-weight: 600; }

/* An opened card takes the whole row. Measured closed, the HRMIS panel is
   1260px of technical detail; poured into a 282px column that is a ribbon
   nobody reads. Full width also lets the list run in columns instead of one
   long thread.

   :has() does the whole thing in CSS with no state to track. Where it is not
   supported the card simply stays in its column and the panel is tall, which is
   worse-looking but still complete and still readable. */
.combo-grid-feature .combo-feature:has(.cap-more[open]) { grid-column: 1 / -1; }
.combo-feature:has(.cap-more[open]) .cap-more-body ul {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  column-gap: clamp(20px, 3vw, 44px);
}
.combo-feature:has(.cap-more[open]) .cap-more-body p { max-width: 78ch; }

@keyframes faq-settle {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Feature capability cards ----------
   The Services page leads with these three, so they carry more weight than a
   standard card: more padding, a heading a step up the existing scale, and the
   gold top edge lit permanently rather than waiting for a hover that never
   arrives on touch.

   Four columns, matching the four cards. The column count is stated here rather
   than inherited so the row can never end short and read as a missing card. No
   new tokens; this is the .combo card with the dial turned up. */
.combo-grid-feature { grid-template-columns: repeat(4, 1fr); gap: clamp(16px, 2vw, 24px); }
.combo-feature { padding: clamp(24px, 2.8vw, 34px); }
.combo-feature h3 { font-size: var(--t-h3); margin-bottom: 12px; }
.combo-feature > p { margin-bottom: 4px; }
/* Doubled class on purpose. The shared card-lift block further down declares
   .combo::before { transform: scaleX(0) } at the same specificity, and being
   later in the file it would win. .combo.combo-feature outranks it. */
.combo.combo-feature::before { transform: scaleX(1); }
.combo-feature .cap-list { margin-top: 14px; }
@media (max-width: 900px) { .combo-grid-feature { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) { .combo-grid-feature { grid-template-columns: 1fr; } }

/* Deliberately subordinate: hardware is a tertiary, Pakistan-only capability
   and must not read as a third service. No card, no icon, no CTA. */
.local-note {
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  color: var(--slate);
  font-size: var(--t-small);
  line-height: 1.68;
  max-width: 78ch;
}
.local-note strong { color: var(--navy-800); font-weight: 600; }

/* ---------- Contact ---------- */
.contact-layout { display: grid; grid-template-columns: 1.25fr 0.85fr; gap: var(--s-5); align-items: start; }
.form-step-label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.contact-side { display: grid; gap: var(--s-3); }
.side-card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
}
.side-card h2 {
  font-size: 0.98rem;
  color: var(--navy-800);
  margin-bottom: var(--s-3);
}
.side-card-quiet { background: var(--navy-100); border-color: rgba(6,33,90,0.14); }
.side-card-quiet p { color: var(--slate); font-size: var(--t-small); line-height: 1.62; }
.side-steps { list-style: none; display: grid; gap: var(--s-3); counter-reset: side; }
.side-steps li {
  position: relative;
  padding-left: 34px;
  display: grid;
  gap: 3px;
  counter-increment: side;
}
.side-steps li::before {
  content: counter(side);
  position: absolute;
  left: 0;
  top: 0;
  width: 23px;
  height: 23px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--navy-100);
  border: 1px solid rgba(6,33,90,0.16);
  font-family: var(--font-display);
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--navy-800);
}
.side-steps strong { font-size: 0.9rem; color: var(--navy-800); font-weight: 600; }
.side-steps span { color: var(--slate); font-size: var(--t-small); line-height: 1.5; }
.side-email { display: block; font-weight: 600; color: var(--navy-800); margin-bottom: 10px; }
.side-email:hover { text-decoration: underline; }
.side-meta { color: var(--slate); font-size: var(--t-small); line-height: 1.55; margin-bottom: 7px; }

/* ---------- Work list ---------- */
.work-list { display: grid; gap: var(--s-5); }
.work-item {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: var(--s-4);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  scroll-margin-top: calc(var(--nav-h) + 20px);
}
.work-item-visual {
  background: linear-gradient(160deg, var(--navy-700), var(--navy-900));
  padding: var(--s-4);
  display: grid;
  align-content: center;
  min-height: 220px;
}
.work-item-body { padding: var(--s-4) var(--s-4) var(--s-4) 0; display: flex; flex-direction: column; gap: var(--s-2); }
.work-item-body h2 { font-size: clamp(1.25rem, 1.1rem + 0.6vw, 1.6rem); color: var(--navy-800); }
.work-item-body p { color: var(--slate); font-size: var(--t-body); line-height: 1.62; }
.work-item-body p strong { color: var(--navy-800); font-weight: 600; }
.work-stats {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-2);
  margin: var(--s-2) 0;
  padding: var(--s-3) 0;
  border-block: 1px solid var(--line);
}
.work-stats li { display: grid; gap: 2px; }
.work-stats strong {
  font-family: var(--font-head);
  font-size: 1.24rem;
  font-weight: 700;
  color: var(--navy-800);
  letter-spacing: -0.02em;
}
.work-stats span { color: var(--slate); font-size: 0.72rem; line-height: 1.35; }
.work-item-body .link-arrow { margin-top: auto; }

.work-note {
  background: var(--navy-100);
  border: 1px solid rgba(6,33,90,0.14);
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  display: grid;
  gap: var(--s-2);
}
.work-note h2 { font-size: var(--t-h3); color: var(--navy-800); margin-bottom: 4px; }
.work-note p { color: var(--slate); font-size: var(--t-body); line-height: 1.68; max-width: 80ch; }

/* ---------- About: beliefs, regions, timeline ---------- */
.belief-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); }
.belief {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
  display: grid;
  gap: var(--s-2);
  align-content: start;
}
.belief-num { font-family: var(--font-display); font-weight: 700; font-size: 0.78rem; letter-spacing: 0.16em; color: var(--navy-600); }
.belief h3 { font-size: 1.02rem; color: var(--navy-800); }
.belief p { color: var(--slate); font-size: var(--t-small); line-height: 1.6; }
.belief-cost {
  margin-top: 4px;
  padding-top: 11px;
  border-top: 1px dashed rgba(6,33,90,0.2);
}
.belief-cost strong { color: var(--navy-600); font-weight: 600; }

.verify-note {
  margin-top: var(--s-3);
  padding: var(--s-3);
  border-left: 2px solid var(--accent);
  background: var(--ivory);
  color: var(--slate);
  font-size: var(--t-small);
  line-height: 1.62;
}

.regions { background: var(--ivory); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s-4); }
.regions h3 { font-size: 1rem; color: var(--navy-800); margin-bottom: var(--s-3); padding-bottom: var(--s-2); border-bottom: 1px solid var(--line); }
.region-list { list-style: none; display: grid; gap: 2px; }
.region-list li {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  align-items: baseline;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.region-list li:last-child { border-bottom: none; }
.region-name { font-weight: 600; color: var(--navy-800); font-size: 0.92rem; }
.region-time { color: var(--slate); font-size: var(--t-small); text-align: right; }
.region-note { margin-top: var(--s-3); color: var(--slate); font-size: var(--t-small); line-height: 1.58; }

/* ---------- Page responsive ---------- */
@media (max-width: 1100px) {
  .combo-grid { grid-template-columns: 1fr 1fr; }
  .belief-grid { grid-template-columns: 1fr 1fr; }
  .contact-layout { grid-template-columns: 1fr; }
}
@media (max-width: 860px) {
  .work-item { grid-template-columns: 1fr; }
  .work-item-visual { min-height: 170px; }
  .work-item-body { padding: 0 var(--s-4) var(--s-4); }
}
@media (max-width: 640px) {
  .combo-grid, .belief-grid { grid-template-columns: 1fr; }
  .work-stats { grid-template-columns: 1fr 1fr; }
  .page-hero h1 { max-width: none; }
}

/* ---------- Case study ---------- */
.case-visual {
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  margin-bottom: var(--s-4);
  background: linear-gradient(160deg, var(--navy-700), var(--navy-900));
  min-height: 200px;
  display: grid;
  align-content: center;
}
.case-visual:empty { display: none; }
.case-visual-dark { background: linear-gradient(160deg, var(--navy-800), var(--navy-900)); }

.glance {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: var(--s-5);
}
.glance-item { background: var(--white); padding: var(--s-3); display: grid; gap: 4px; }
.glance dt {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--slate);
}
.glance dd { font-size: 0.9rem; color: var(--navy-800); font-weight: 500; line-height: 1.4; }

.case-body { display: grid; grid-template-columns: 1fr 1.1fr; gap: var(--s-6); align-items: start; }
.case-col h2 {
  font-size: var(--t-h3);
  color: var(--navy-800);
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.case-col p { color: var(--slate); font-size: var(--t-body); line-height: 1.7; margin-bottom: var(--s-3); }
.case-deliverables { display: grid; gap: var(--s-3); }
.case-deliverables dt {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--navy-800);
  position: relative;
  padding-left: 22px;
}
.case-deliverables dt::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--accent);
}
.case-deliverables dd { color: var(--slate); font-size: var(--t-small); line-height: 1.62; padding-left: 22px; margin-top: 3px; }

.depth-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.depth-item {
  background: var(--ivory);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
}
.depth-item h3 { font-size: 1rem; color: var(--navy-800); margin-bottom: 9px; }
.depth-item p { color: var(--slate); font-size: var(--t-small); line-height: 1.66; }

.work-stats-lg { grid-template-columns: repeat(4, 1fr); margin-bottom: var(--s-4); }
/* The case-study pages all carry exactly four stats, so the shared rule hard-codes
   four columns. The workforce product page carries three, which left a quarter of
   the row empty on the right. A modifier rather than auto-fit, so the two work-*
   pages keep the layout they were designed with. */
.work-stats-lg.work-stats-3 { grid-template-columns: repeat(3, 1fr); }
.work-stats-lg strong { font-size: clamp(1.5rem, 1.2rem + 1vw, 2.1rem); }
.work-stats-lg span { font-size: var(--t-small); }

.case-stack { border-top: 1px solid var(--line); padding-top: var(--s-4); }
.case-stack h2 {
  font-family: var(--font-body);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: var(--s-3);
}
.case-stack ul { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
.case-stack li {
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--navy-800);
  background: var(--navy-100);
  border: 1px solid rgba(6,33,90,0.12);
  border-radius: 999px;
  padding: 5px 13px;
}
.case-next {
  margin-top: var(--s-4);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  color: var(--slate);
  font-size: var(--t-small);
}

/* ---------- Anatomy / method list ---------- */
.anatomy { list-style: none; display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--s-3); counter-reset: anat; }

/* ---------- Combos ---------- */
.combo-grid-5 { grid-template-columns: repeat(5, 1fr); }

/* ---------- Before / after ---------- */
.beforeafter { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--s-4); align-items: stretch; }

/* ---------- FAQ ---------- */
.faq { display: grid; gap: 10px; max-width: 82ch; }

/* ---------- Industries ---------- */
.industry-list { display: grid; gap: var(--s-4); }
.industry { border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); overflow: hidden; }
.industry-open { background: var(--ivory); border-style: dashed; }
.industry-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  padding: var(--s-4);
  border-bottom: 1px solid var(--line);
}
.industry-head h2 { font-size: var(--t-h3); color: var(--navy-800); }
.industry-badge {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--slate);
  background: var(--ivory);
  white-space: nowrap;
}
.badge-strong { border-color: rgba(185,141,47,0.45); color: #8a6a1f; background: rgba(185,141,47,0.1); }
.industry-body { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-4); padding: var(--s-4); }
.industry-body h3 {
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: 8px;
}
.industry-body p { color: var(--slate); font-size: var(--t-small); line-height: 1.62; }
.industry-body .link-arrow { margin-right: var(--s-3); }

/* ---------- Prose (privacy etc) ---------- */
.prose { max-width: 74ch; }
.prose h2 { font-size: var(--t-h3); color: var(--navy-800); margin: var(--s-5) 0 var(--s-2); }
.prose h2:first-child { margin-top: 0; }
.prose p { color: var(--slate); font-size: var(--t-body); line-height: 1.75; margin-bottom: var(--s-3); }
.prose a { color: var(--navy-800); font-weight: 600; border-bottom: 1px solid rgba(6,33,90,0.3); }
.prose a:hover { border-bottom-color: var(--navy-800); }

/* ---------- Responsive ---------- */
@media (max-width: 1100px) {
  .glance { grid-template-columns: repeat(3, 1fr); }
  .case-body { grid-template-columns: 1fr; }
  .industry-body { grid-template-columns: 1fr; gap: var(--s-3); }
}
@media (max-width: 860px) {
  .depth-grid { grid-template-columns: 1fr; }
  .work-stats-lg { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 640px) {
  .glance { grid-template-columns: 1fr 1fr; }
}

/* ---------- Screenshot frame ---------- */
.shot {
  border-radius: 10px;
  overflow: hidden;
  background: #0d1b34;
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 22px 50px -24px rgba(0, 0, 0, 0.75);
}
.shot-chrome {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px;
  background: rgba(255, 255, 255, 0.06);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.shot-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.24);
  flex: none;
}
.shot-label {
  margin-left: 8px;
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.shot img { width: 100%; height: auto; display: block; }

/* A portrait capture (the Delight Equity control panel is 599x1392) cannot run
   full-bleed like a 16:9 chart: at container width it would be a metre tall.
   Cap the width, then clip the overflow and dissolve the bottom edge into the
   frame colour so the crop reads as deliberate rather than as a broken image. */
.shot-portrait { max-width: 380px; margin-inline: auto; }
.shot-tall { position: relative; max-height: 540px; overflow: hidden; }
.shot-tall::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 110px;
  background: linear-gradient(rgba(13, 27, 52, 0), #0d1b34);
  pointer-events: none;
}
@media (max-width: 700px) {
  .shot-portrait { max-width: 320px; }
  .shot-tall { max-height: 430px; }
}

/* Annotations under a case-study screenshot */
.annotations {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-3);
  margin: calc(var(--s-4) * -1 + 4px) 0 var(--s-5);
  padding-top: var(--s-3);
}
.annotations li {
  position: relative;
  padding-top: var(--s-3);
  border-top: 2px solid var(--accent);
  display: grid;
  gap: 4px;
}
.annotations strong { font-family: var(--font-head); font-size: 0.88rem; font-weight: 600; color: var(--navy-800); }
.annotations span { color: var(--slate); font-size: var(--t-small); line-height: 1.5; }

@media (max-width: 1100px) { .annotations { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px)  { .annotations { grid-template-columns: 1fr; } }

/* ---------- Client-reported result ---------- */
.result-panel { display: grid; grid-template-columns: 0.95fr 1.05fr; gap: var(--s-4); align-items: start; }
.result-claim {
  background: linear-gradient(165deg, var(--navy-700), var(--navy-900));
  border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  color: var(--white);
  position: relative;
  overflow: hidden;
}
.result-claim::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--accent);
}
.result-claim p {
  font-family: var(--font-head);
  font-size: clamp(1.1rem, 0.95rem + 0.7vw, 1.45rem);
  font-weight: 600;
  line-height: 1.42;
  letter-spacing: -0.015em;
  color: var(--white);
  margin-bottom: var(--s-3);
}
.result-claim strong { color: var(--accent); font-weight: 700; }
.result-claim footer {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
}
.result-note {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ivory);
  padding: var(--s-4);
}
.result-note h3 {
  font-size: 1rem;
  color: var(--navy-800);
  margin-bottom: var(--s-2);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.result-note p { color: var(--slate); font-size: var(--t-body); line-height: 1.7; margin-bottom: var(--s-3); }
.result-risk {
  margin-bottom: 0;
  padding: var(--s-3);
  border-left: 2px solid var(--accent);
  background: var(--white);
  font-size: var(--t-small);
}

/* Shorter work entries for the two straightforward website builds */
.work-item-sm .work-item-visual { min-height: 170px; }
.work-item-sm .work-item-body { gap: var(--s-3); }

@media (max-width: 900px) { .result-panel { grid-template-columns: 1fr; } }

/* ---------- Track record ----------
   Replaces the dated timeline. Four entries all stamped with the same month
   read as "we started last month"; leading with each client's own standing
   (est. 1910, 40-year record, SECP-certified) borrows gravitas that is real
   and checkable, without misstating a delivery date. */
.record-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-3); }
.record .link-arrow { margin-top: 4px; }

@media (max-width: 760px) { }

/* ---------- Client logo plate ----------
   Used where there is no product screenshot worth showing. A white plate with
   the client mark reads as deliberate; the abstract CSS shapes it replaces
   read as placeholder art, because that is what they were.
   Sized by optical area on the same scale as the proof strip. */
.logo-plate {
  display: grid;
  place-items: center;
  padding: var(--s-3) var(--s-4);
  background: var(--white);
  border: 1px solid var(--line);
}
.logo-plate img {
  width: auto;
  height: auto;
  max-width: 78%;
  max-height: 54px;
  object-fit: contain;
}
.logo-plate.plate-wide  img { max-height: 46px; }
.logo-plate.plate-xwide img { max-height: 38px; }
.logo-plate.plate-tall  img { max-height: 66px; }

/* In the taller work-item slot the plate can breathe */
.work-item-visual.logo-plate img { max-height: 78px; max-width: 66%; }
.work-item-visual.logo-plate.plate-wide  img { max-height: 66px; }
.work-item-visual.logo-plate.plate-xwide img { max-height: 54px; }

/* External link to a live client site */
.live-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.88rem;
  color: var(--navy-800);
  border-bottom: 1px solid rgba(6, 33, 90, 0.3);
  padding-bottom: 2px;
}
.live-link::after {
  content: "";
  width: 11px;
  height: 11px;
  flex: none;
  background: currentColor;
  /* arrow leaving a box, the conventional external-link mark */
  clip-path: polygon(0 22%, 12% 22%, 12% 88%, 78% 88%, 78% 100%, 0 100%,
                     0 22%, 40% 0, 100% 0, 100% 60%, 88% 60%, 88% 20%, 40% 20%);
}
.live-link:hover { border-bottom-color: var(--navy-800); }
.tile .live-link { font-size: 0.82rem; }

/* ---------- Client identity mark ----------
   For cards whose large visual slot already shows product proof (a screenshot
   or a product representation), so the logo cannot live there. Cards showing a
   .logo-plate do NOT also get a mark: one logo per card.

   The chip is FIXED HEIGHT, AUTO WIDTH. A square chip constrains a wide
   lockup by width, so its height cap never applies and it renders at half the
   optical weight of a portrait one. Letting the chip widen keeps every mark on
   the same visual footing (~980-1080 px2 across the four logos). */
.client-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  height: 46px;
  width: auto;
  min-width: 46px;
  padding: 6px 10px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 9px;
}
.client-mark img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 28px;
  object-fit: contain;
}
.client-mark.mark-tall  img { max-height: 34px; }
.client-mark.mark-wide  img { max-height: 24px; }
.client-mark.mark-xwide img { max-height: 20px; }

/* Heading row: mark + client name side by side */
.client-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.client-head h2, .client-head h3 { margin: 0; }

.tile .client-mark { height: 38px; min-width: 38px; padding: 5px 8px; border-radius: 8px; }
.tile .client-mark img { max-height: 22px; }
.tile .client-mark.mark-tall  img { max-height: 26px; }
.tile .client-mark.mark-wide  img { max-height: 19px; }
.tile .client-mark.mark-xwide img { max-height: 16px; }

/* ---------- Technology proof row ----------
   Sits under the rails. The marquee shows breadth; these four lines tie it to
   something delivered, which is the part a marquee alone cannot do. */
.tech-proof {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-3);
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}
.tech-proof li { display: grid; gap: 6px; align-content: start; }
.tech-proof strong {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--navy-600);
}
.tech-proof span { color: var(--slate); font-size: var(--t-small); line-height: 1.58; }

@media (max-width: 900px) { .tech-proof { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .tech-proof { grid-template-columns: 1fr; } }



/* ---------- Contact form status ----------
   Empty by default so it takes no space until the handler writes to it. */
.form-status {
  margin-top: var(--s-3);
  font-size: var(--t-small);
  line-height: 1.55;
  text-align: center;
  border-radius: 10px;
}
.form-status:empty { display: none; }
.form-status.is-info {
  color: var(--slate);
  padding: 10px var(--s-3);
  background: var(--navy-100);
}
.form-status.is-ok {
  color: #15803D;
  font-weight: 600;
  padding: 12px var(--s-3);
  background: rgba(74, 222, 128, 0.12);
  border: 1px solid rgba(22, 163, 74, 0.3);
}
.form-status.is-err {
  color: #B42318;
  font-weight: 600;
  padding: 12px var(--s-3);
  background: rgba(180, 35, 24, 0.07);
  border: 1px solid rgba(180, 35, 24, 0.28);
}
.contact-form button[disabled] { opacity: 0.6; cursor: progress; }

/* Helper text under a form field */
.field-hint {
  display: block;
  margin-top: 6px;
  color: var(--slate);
  font-size: 0.78rem;
  line-height: 1.45;
}

/* ---------- Postal address ----------
   <address> is italic by default in every browser, which reads as emphasis
   rather than as contact detail. Reset it and match the surrounding text. */
.footer-address,
.side-address {
  font-style: normal;
}
.footer-address {
  color: var(--mist);
  font-size: 0.8rem;
  line-height: 1.6;
  margin-bottom: 10px;
}
.side-address {
  color: var(--slate);
  font-size: var(--t-small);
  line-height: 1.6;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

/* ============================================================
   MOTION SYSTEM  —  added 2026-08-04
   Shared primitives + one signature hero visual per page.

   Rules this block holds itself to:
   - Only transform, opacity, filter and stroke-dashoffset are animated.
     Nothing here animates a layout property.
   - Every hero visual is decorative: aria-hidden, pointer-events none,
     and sitting under the copy at z-index 0 so contrast is unaffected.
   - Every moving part has a reduced-motion resting state at the bottom
     of this file. A visual that only exists to move is dropped entirely;
     one that carries structure is frozen in its finished position.
   ============================================================ */

/* ---------- Hero FX shell ----------
   Replaces the bare .hero-bg div on pages that carry a signature visual.
   The gradient stays a CSS background that never repaints; the SVG above
   it is the only thing that animates. */
.hero-fx {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.hero-fx .hero-bg { position: absolute; inset: 0; }
.page-hero.has-fx .container { position: relative; z-index: 1; }

/* Blueprint rule grid, shared by every FX hero. A single gradient pair,
   painted once, never animated. */
.fx-grid {
  position: absolute;
  inset: -10%;
  background-image:
    linear-gradient(to right, rgba(255,255,255,0.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255,255,255,0.045) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: radial-gradient(115% 90% at 78% 42%, #000 0%, rgba(0,0,0,0.55) 52%, transparent 82%);
  -webkit-mask-image: radial-gradient(115% 90% at 78% 42%, #000 0%, rgba(0,0,0,0.55) 52%, transparent 82%);
}

/* The SVG stage. Anchored right on desktop so it sits beside the copy
   rather than under it; on narrow screens it moves behind and fades. */
.fx-stage {
  position: absolute;
  top: 50%;
  right: clamp(-40px, 2vw, 60px);
  width: clamp(320px, 42vw, 560px);
  height: clamp(320px, 42vw, 560px);
  transform: translateY(-50%);
  overflow: visible;
}
.fx-stage svg { width: 100%; height: 100%; overflow: visible; display: block; }

/* main.js adds this once the hero has scrolled out of view. Nothing is being
   looked at, so nothing needs to be composited. Applied to descendants rather
   than the container because animation-play-state does not inherit through
   an element that has no animation of its own. */
.hero-fx.fx-paused * { animation-play-state: paused !important; }

/* Shared stroke vocabulary */
.fx-line   { fill: none; stroke: rgba(255,255,255,0.20); stroke-width: 1; vector-effect: non-scaling-stroke; }
.fx-line-b { fill: none; stroke: rgba(255,255,255,0.10); stroke-width: 1; vector-effect: non-scaling-stroke; }
.fx-gold   { fill: none; stroke: var(--gold, #B98D2F); stroke-width: 1.4; vector-effect: non-scaling-stroke; }
.fx-fill   { fill: rgba(255,255,255,0.05); }
.fx-dot    { fill: var(--gold, #B98D2F); }
.fx-dot-w  { fill: rgba(255,255,255,0.55); }
.fx-plate  { fill: rgba(255,255,255,0.045); stroke: rgba(255,255,255,0.16); stroke-width: 1; vector-effect: non-scaling-stroke; }

/* ============================================================
   FX 01 — SERVICES: two systems, one conduit
   Two plates either side of a channel; packets cross in both
   directions and the seam pulses gold as they pass.
   ============================================================ */
.fx-bridge .br-plate { animation: br-breathe 6s ease-in-out infinite; transform-origin: center; }
.fx-bridge .br-plate-b { animation-delay: -3s; }
.fx-bridge .br-packet { animation: br-travel 3.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; }
.fx-bridge .br-packet-2 { animation-delay: -1.6s; }
.fx-bridge .br-packet-back { animation-name: br-travel-back; animation-delay: -0.8s; }
.fx-bridge .br-seam { animation: br-seam 3.2s ease-in-out infinite; transform-origin: center; }
.fx-bridge .br-row { animation: br-row 4.4s ease-in-out infinite; }
.fx-bridge .br-row:nth-child(2) { animation-delay: -1.1s; }
.fx-bridge .br-row:nth-child(3) { animation-delay: -2.2s; }
.fx-bridge .br-row:nth-child(4) { animation-delay: -3.3s; }

@keyframes br-breathe { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
@keyframes br-travel      { 0% { transform: translateX(0);    opacity: 0; }
                            12% { opacity: 1; }
                            88% { opacity: 1; }
                            100% { transform: translateX(112px); opacity: 0; } }
@keyframes br-travel-back { 0% { transform: translateX(0);    opacity: 0; }
                            12% { opacity: 1; }
                            88% { opacity: 1; }
                            100% { transform: translateX(-112px); opacity: 0; } }
@keyframes br-seam { 0%, 100% { opacity: 0.25; transform: scaleY(0.9); } 50% { opacity: 0.9; transform: scaleY(1); } }
@keyframes br-row  { 0%, 100% { opacity: 0.30; } 50% { opacity: 0.85; } }

/* ============================================================
   FX 02 — SOFTWARE DEVELOPMENT: five layers, assembling
   The build anatomy as a visual. Plates rise into place bottom
   up, the top plate is gold, and a scan line sweeps the stack.
   ============================================================ */
.fx-layers .ly-plate {
  animation: ly-rise 5.6s cubic-bezier(0.16, 1, 0.3, 1) infinite;
  transform-origin: center;
}

@keyframes ly-rise {
  0%   { transform: translateY(16px); opacity: 0; }
  14%  { transform: translateY(0);    opacity: 1; }
  76%  { transform: translateY(0);    opacity: 1; }
  92%  { transform: translateY(-6px); opacity: 0; }
  100% { transform: translateY(16px); opacity: 0; }
}



/* ============================================================
   FX 03 — SYSTEMS INTEGRATION: lanes, flow, retry
   Three lanes carry dashes left to right. The middle lane stalls
   and loops back once per cycle — the retry, drawn honestly.
   ============================================================ */
.fx-pipe .pp-flow {
  stroke-dasharray: 10 18;
  animation: pp-flow 2.4s linear infinite;
}

@keyframes pp-flow  { to { stroke-dashoffset: -56; } }




/* ============================================================
   FX 04 — WORK: constellation
   Delivered systems as connected nodes. Links draw in sequence,
   nodes light as their link lands, then the whole set holds.
   ============================================================ */
.fx-const .cn-link {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: cn-draw 7s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
.fx-const .cn-link-2 { animation-delay: 0.3s; }
.fx-const .cn-link-3 { animation-delay: 0.6s; }
.fx-const .cn-link-4 { animation-delay: 0.9s; }
.fx-const .cn-link-5 { animation-delay: 1.2s; }
.fx-const .cn-node { animation: cn-light 7s ease-in-out infinite; transform-origin: center; }
.fx-const .cn-node-2 { animation-delay: 0.3s; }
.fx-const .cn-node-3 { animation-delay: 0.6s; }
.fx-const .cn-node-4 { animation-delay: 0.9s; }
.fx-const .cn-halo { animation: cn-halo 7s ease-out infinite; transform-origin: center; }
.fx-const .cn-halo-2 { animation-delay: 0.6s; }

@keyframes cn-draw  { 0% { stroke-dashoffset: 100; } 22% { stroke-dashoffset: 0; } 84% { stroke-dashoffset: 0; opacity: 1; } 96%, 100% { stroke-dashoffset: 0; opacity: 0; } }
@keyframes cn-light { 0%, 8% { opacity: 0.2; transform: scale(0.7); } 24% { opacity: 1; transform: scale(1); } 84% { opacity: 1; transform: scale(1); } 96%, 100% { opacity: 0.2; transform: scale(0.7); } }
@keyframes cn-halo  { 0%, 14% { opacity: 0; transform: scale(0.4); } 30% { opacity: 0.5; } 60%, 100% { opacity: 0; transform: scale(1.9); } }

/* ============================================================
   FX 05 — INDUSTRIES: sector sweep
   Concentric rings with a rotating wedge. Blips brighten as the
   wedge crosses them. Four rings, four sectors — not decoration
   by accident.
   ============================================================ */
.fx-radar .rd-sweep { animation: rd-sweep 8s linear infinite; transform-origin: 160px 160px; }
.fx-radar .rd-ring  { animation: rd-ring 8s ease-in-out infinite; transform-origin: 160px 160px; }
.fx-radar .rd-ring-2 { animation-delay: -2s; }
.fx-radar .rd-ring-3 { animation-delay: -4s; }
.fx-radar .rd-ring-4 { animation-delay: -6s; }
.fx-radar .rd-blip  { animation: rd-blip 8s linear infinite; transform-origin: center; }
.fx-radar .rd-blip-2 { animation-delay: -2s; }
.fx-radar .rd-blip-3 { animation-delay: -4s; }
.fx-radar .rd-blip-4 { animation-delay: -6s; }

@keyframes rd-sweep { to { transform: rotate(360deg); } }
@keyframes rd-ring  { 0%, 100% { opacity: 0.14; } 50% { opacity: 0.34; } }
@keyframes rd-blip  { 0% { opacity: 0; transform: scale(0.5); } 4% { opacity: 1; transform: scale(1.25); } 26% { opacity: 0.35; transform: scale(1); } 100% { opacity: 0.1; transform: scale(1); } }

/* ============================================================
   FX 06 — ABOUT: the phalanx
   The shield wall from the first build of this site, kept. Shields
   step into formation column by column and the lambda holds gold.
   ============================================================ */
.fx-phalanx .ph-shield {
  animation: ph-step 6.4s cubic-bezier(0.16, 1, 0.3, 1) infinite;
  transform-origin: center;
}
.fx-phalanx .ph-lambda { animation: ph-lambda 6.4s ease-in-out infinite; }
@keyframes ph-step   { 0% { opacity: 0; transform: translateY(10px); } 16% { opacity: 1; transform: translateY(0); } 84% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-6px); } }
@keyframes ph-lambda { 0%, 22% { opacity: 0.25; } 46% { opacity: 1; } 84% { opacity: 1; } 100% { opacity: 0.25; } }

/* ============================================================
   FX 07 — CONTACT: outbound signal
   Rings leave a point and a carrier dot runs the line. The reply
   arrives from the far end on the same path — the page promises a
   reply within one business day, so the visual answers back.
   ============================================================ */
.fx-signal .sg-ring { animation: sg-ring 3.6s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; transform-origin: 92px 160px; }
.fx-signal .sg-ring-2 { animation-delay: -1.2s; }
.fx-signal .sg-ring-3 { animation-delay: -2.4s; }
.fx-signal .sg-out   { animation: sg-out 4.4s cubic-bezier(0.5, 0, 0.5, 1) infinite; }
.fx-signal .sg-back  { animation: sg-back 4.4s cubic-bezier(0.5, 0, 0.5, 1) infinite; animation-delay: 2.2s; }
.fx-signal .sg-far   { animation: sg-far 4.4s ease-in-out infinite; transform-origin: center; }

@keyframes sg-ring { 0% { opacity: 0.75; transform: scale(0.2); } 100% { opacity: 0; transform: scale(1); } }
@keyframes sg-out  { 0% { transform: translateX(0); opacity: 0; } 10% { opacity: 1; } 42% { opacity: 1; } 50%, 100% { transform: translateX(88px); opacity: 0; } }
@keyframes sg-back { 0% { transform: translateX(0); opacity: 0; } 10% { opacity: 1; } 42% { opacity: 1; } 50%, 100% { transform: translateX(-88px); opacity: 0; } }
@keyframes sg-far  { 0%, 44% { opacity: 0.3; transform: scale(1); } 52% { opacity: 1; transform: scale(1.3); } 78% { opacity: 0.3; transform: scale(1); } 100% { opacity: 0.3; } }

/* ============================================================
   SHARED PRIMITIVES
   ============================================================ */

/* ---------- Staggered reveal ----------
   main.js writes --i on each child of a [data-stagger] container. The
   delay is capped so a long list never leaves the last item waiting
   for a second and a half. */
/* One rule, one knob. The step is a variable so a variant can lengthen it
   without redeclaring the delay maths. */
.js-reveal [data-stagger] > .reveal { transition-delay: calc(var(--i, 0) * var(--stagger-step, 70ms)); }
.js-reveal [data-stagger="fast"] > .reveal { --stagger-step: 45ms; }
.js-reveal [data-stagger="slow"] > .reveal { --stagger-step: 120ms; }

/* ============================================================
   CHOREOGRAPHY
   ============================================================
   A section declares how its contents arrive with [data-choreo]. Everything
   is driven off three custom properties consumed by ONE transform in the base
   .reveal rule, so a variant is a couple of property writes rather than a new
   transition. No JavaScript: the existing .reveal observer already adds
   .visible, and these rules only change where the element travels from.

   Omitting the attribute keeps the fade-up the site has today, which is why
   the defaults below are exactly the old values. Migration is opt-in. */
.js-reveal .reveal {
  --cho-x: 0px;
  --cho-y: var(--reveal-distance);
  --cho-s: 1;
}

/* Lateral entrances. Used where two columns are being CONTRASTED and the
   direction carries the meaning: the old way arrives from the left, the new
   way from the right. */
.js-reveal [data-choreo="left"] .reveal  { --cho-x: calc(var(--reveal-distance) * -1.4); --cho-y: 0px; }
.js-reveal [data-choreo="right"] .reveal { --cho-x: calc(var(--reveal-distance) * 1.4);  --cho-y: 0px; }

/* Alternating sides, for grids where items should feel gathered rather than
   listed. nth-of-type so a stray non-reveal sibling does not flip the phase. */
.js-reveal [data-choreo="converge"] .reveal:nth-of-type(odd)  { --cho-x: calc(var(--reveal-distance) * -1.1); --cho-y: 4px; }
.js-reveal [data-choreo="converge"] .reveal:nth-of-type(even) { --cho-x: calc(var(--reveal-distance) * 1.1);  --cho-y: 4px; }

/* Settle: a shorter drop with a touch of scale. For panels that should feel
   placed rather than slid. Scale stays above 0.96 because text rendered mid
   sub-pixel-scale looks soft, and this lands on body copy. */
.js-reveal [data-choreo="settle"] .reveal { --cho-y: 12px; --cho-s: 0.972; }

/* Wipe: the block uncovers itself top to bottom. Kept OFF the transform
   channel so it composes with the others, and applied only here because
   clip-path on an arbitrary card would crop its shadow. */
.js-reveal [data-choreo="wipe"] .reveal {
  --cho-y: 10px;
  clip-path: inset(0 0 100% 0);
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              clip-path 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.js-reveal [data-choreo="wipe"] .reveal.visible { clip-path: inset(0 0 0 0); }

/* ---------- Section head gold rule ----------
   Draws itself when the head reveals. scaleX only. */
.section-head .kicker { position: relative; }
.section-head .kicker::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -7px;
  width: 34px;
  height: 2px;
  background: var(--gold, #B98D2F);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.15s;
}
/* Default is drawn. The rule only retracts while a head is still waiting to
   reveal, so with JS off or blocked every rule is simply present — same
   opt-in contract the .reveal system itself uses. */
.section-head .kicker::after { transform: scaleX(1); }
.js-reveal .section-head.reveal:not(.visible) .kicker::after { transform: scaleX(0); }

/* ---------- Card lift ----------
   One hover treatment shared by every card grid on the site, so the
   pages read as one system rather than seven. Transform and shadow
   only; nothing here reflows. */
/* No will-change here, deliberately. It used to be declared on all five
   classes, which on software-development.html alone meant 19 permanently
   promoted layers held in GPU memory for the whole session to serve a hover
   that may never happen. A 0.45s transform transition does not need a
   pre-promoted layer; the compositor promotes on demand and releases after.
   Promotion that IS worth buying is applied per element by main.js for the
   duration of the reveal, then removed. */
.combo, .cap-card, .industry, .depth-item, .stack-card {
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              border-color 0.45s ease;
}
.combo:hover, .cap-card:hover, .depth-item:hover, .stack-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 18px 44px -22px rgba(4, 24, 56, 0.42);
  border-color: rgba(185, 141, 47, 0.42);
}

/* Gold edge that wipes in on hover. Pseudo-element, no layout cost. */
.combo, .stack-card, .depth-item { position: relative; overflow: hidden; }
.combo::before, .stack-card::before, .depth-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(90deg, var(--gold, #B98D2F), rgba(185, 141, 47, 0.15));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.combo:hover::before, .stack-card:hover::before, .depth-item:hover::before,
.combo:focus-within::before, .stack-card:focus-within::before { transform: scaleX(1); }

/* ---------- Anatomy list: numbers count up the rail ---------- */
.anatomy > li { transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); }

/* ============================================================
   NEW COMPONENTS
   ============================================================ */

/* ---------- Ongoing-partnership flag ----------
   Used where a client engagement is still running, so "delivered"
   would be the wrong word. The dot pulses; the text does not move. */
.status-flag {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0 0;
  padding: 5px 13px 5px 11px;
  border: 1px solid rgba(185, 141, 47, 0.5);
  border-radius: 999px;
  background: rgba(185, 141, 47, 0.12);
  color: #E8C97A;
  font-family: var(--font-ui, inherit);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
}
.status-flag-light {
  border-color: rgba(185, 141, 47, 0.45);
  background: rgba(185, 141, 47, 0.1);
  color: #8a6a1f;
}
.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 0 currentColor;
  animation: status-pulse 2.4s ease-out infinite;
  flex: 0 0 auto;
}
@keyframes status-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(185, 141, 47, 0.55); }
  70%  { box-shadow: 0 0 0 7px rgba(185, 141, 47, 0); }
  100% { box-shadow: 0 0 0 0 rgba(185, 141, 47, 0); }
}

/* ---------- Partnership note on the case study ---------- */
.partnership-note {
  margin-top: clamp(28px, 3.5vw, 44px);
  padding: clamp(22px, 2.6vw, 32px);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold, #B98D2F);
  border-radius: 4px;
  background: rgba(185, 141, 47, 0.045);
}
.partnership-note h2 { margin-bottom: 10px; }
.partnership-note p { margin: 0; max-width: 74ch; }

/* ---------- Capability tag (replaces the old client-proof line) ---------- */
.combo-cap {
  display: block;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  color: var(--slate, #5a657a);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
}

/* ---------- Stack grid (Software Development) ---------- */
.stack-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: clamp(14px, 1.6vw, 20px);
}

/* ============================================================
   IN-BODY CHOREOGRAPHY
   ============================================================
   One idiom throughout, the same one the section-head gold rule already uses:
   the FINISHED state is the default, and the start state applies only while a
   JS-revealed ancestor is still pending.

       .js-reveal .reveal:not(.visible) <child> { start state }

   Two things fall out of that. With JS off, blocked, or thrown, `.js-reveal`
   is never set and every diagram renders complete. And the trigger is whichever
   ancestor happens to carry .reveal, so none of this needs its own observer,
   its own class, or a single line of JavaScript. */

/* ---------- Before / After flow ----------
   The two columns deliberately do NOT behave alike. Before resolves step by
   step, each one waiting on the last, because that is what the process it
   describes actually feels like. After arrives as one pass: a gold rail draws
   down the column and the steps land in its wake, faster and overlapping. The
   difference in motion IS the argument the section is making. */
.ba-flow > li {
  transition: opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.js-reveal .reveal:not(.visible) .ba-flow > li { opacity: 0; }
.js-reveal .reveal:not(.visible) .ba-before .ba-flow > li { transform: translateY(9px); }
.js-reveal .reveal:not(.visible) .ba-after  .ba-flow > li { transform: translateX(-10px); }

/* Manual: 130ms apart, one at a time, visibly serial. */
.ba-before .ba-flow > li:nth-child(1) { transition-delay: 0.10s; }

/* Automated: starts after the manual column has made its point, then 60ms
   apart so the five read as one movement rather than five decisions. */
.ba-after .ba-flow > li:nth-child(1) { transition-delay: 0.88s; }
.js-reveal .reveal:not(.visible) .ba-after::after { transform: scaleY(0); }

/* The arrow is the hinge between the two columns, so it moves once, between
   them: after Before has finished and before After begins. */
.ba-arrow { transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.74s, opacity 0.5s ease 0.74s; }
.js-reveal .reveal:not(.visible) .ba-arrow { transform: translateX(-8px); opacity: 0; }

/* ---------- Anatomy rail ----------
   Five numbered stages. They arrive left to right because they are read left
   to right; the number leads its own card in by a fraction so the eye lands on
   the numeral first. */
.anatomy > li {
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
              opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.js-reveal .reveal:not(.visible) .anatomy > li { opacity: 0; transform: translateY(14px); }
.js-reveal .reveal:not(.visible) .anatomy-n { transform: translateY(4px); }

/* ---------- Comparison matrix ----------
   Rows resolve top to bottom, each sliding a little from the left, so the
   table reads as being filled in rather than pasted. */
.matrix-row {
  transition: opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.js-reveal .reveal:not(.visible) .matrix-row { opacity: 0; transform: translateX(-12px); }
.matrix-row:nth-child(2) { transition-delay: 0.06s; }
.matrix-row:nth-child(3) { transition-delay: 0.12s; }
.matrix-row:nth-child(4) { transition-delay: 0.18s; }
.matrix-row:nth-child(5) { transition-delay: 0.24s; }
.matrix-row:nth-child(6) { transition-delay: 0.30s; }
.matrix-row:nth-child(7) { transition-delay: 0.36s; }
/* The column that describes us is the point of the table, so it lands last
   and lands lit. */
.matrix-head .matrix-us { transition: color 0.5s ease 0.42s; }
.js-reveal .reveal:not(.visible) .matrix-head .matrix-us { color: var(--slate); }

/* ---------- Quote / engagement panel ----------
   Placed rather than slid: a small scale settle suits a block that is making a
   commitment. Scale stops at 0.978 because this is body copy and text rendered
   mid sub-pixel-scale goes soft. */
.quote-panel { transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.7s ease; }
.js-reveal .reveal:not(.visible) .quote-panel { transform: scale(0.978); opacity: 0; }

/* ---------- Stat strips ----------
   The figures count (see [data-count] in main.js); the strip itself only has
   to get out of their way. */
.work-stats li {
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.js-reveal .reveal:not(.visible) .work-stats li { opacity: 0; transform: translateY(12px); }
.work-stats li:nth-child(1) { transition-delay: 0.04s; }
.work-stats li:nth-child(2) { transition-delay: 0.12s; }
.work-stats li:nth-child(3) { transition-delay: 0.20s; }
.work-stats li:nth-child(4) { transition-delay: 0.28s; }

/* ---------- Checklist ----------
   The tick is drawn, not faded: it is a mark being made. Scale on the marker
   only, so the label never moves and never reflows. */
.checklist li { transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1); }
.checklist li::after { transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); transform-origin: center; }
.js-reveal .reveal:not(.visible) .checklist li { opacity: 0; transform: translateX(-8px); }
.js-reveal .reveal:not(.visible) .checklist li::after { transform: scale(0); }
.checklist li:nth-child(1) { transition-delay: 0.05s; }
.checklist li:nth-child(2) { transition-delay: 0.12s; }
.checklist li:nth-child(3) { transition-delay: 0.19s; }
.checklist li:nth-child(4) { transition-delay: 0.26s; }
.checklist li:nth-child(5) { transition-delay: 0.33s; }
.checklist li:nth-child(6) { transition-delay: 0.40s; }
.checklist li:nth-child(n + 7) { transition-delay: 0.46s; }

/* ---------- Stack tags ----------
   Small, many, and secondary: they pop in quickly and close together, and the
   whole set is done before the eye has finished the card heading above it. */
.stack-tags li { transition: opacity 0.34s ease, transform 0.34s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s ease; }
.js-reveal .reveal:not(.visible) .stack-tags li { opacity: 0; transform: translateY(6px) scale(0.94); }

/* ---------- FAQ disclosure ----------
   <details> cannot animate its own open/close, and forcing it to would mean
   scripting the element and breaking find-in-page. The marker rotation and a
   content settle are the honest amount of motion available here. */
.faq-item summary { transition: background 0.25s ease, padding-left 0.25s ease; }
@keyframes faq-settle {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Link arrow ----------
   Already slides on hover. The rule underneath draws with it so the whole
   affordance reads as one gesture. */
.link-arrow { position: relative; }
.link-arrow::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.link-arrow:hover::before, .link-arrow:focus-visible::before { transform: scaleX(1); }

/* ---------- Heading reveal ----------
   A clip wipe on the whole block, NOT per-line spans. Wrapping lines in
   elements is the usual way to get a line-by-line effect, and it breaks
   copy/paste, breaks screen-reader phrasing, and has to be recomputed at every
   width. clip-path on the intact element costs none of that: the text stays
   one text node, selection and find-in-page are untouched, and it re-wraps
   freely because the clip is a percentage of whatever box results.
   On a two or three line heading it reads as the lines arriving in order,
   which is the effect that was actually wanted. */
.js-reveal .reveal h2,
.js-reveal .reveal .h-wipe {
  transition: clip-path 0.85s cubic-bezier(0.16, 1, 0.3, 1) 0.05s;
  clip-path: inset(-0.25em -0.15em -0.25em -0.15em);
}
.js-reveal .reveal:not(.visible) h2,
.js-reveal .reveal:not(.visible) .h-wipe {
  clip-path: inset(-0.25em -0.15em 100% -0.15em);
}

/* ---------- Section ambient ----------
   A single gold bloom, low opacity, anchored to a section edge and never
   behind body copy. Opacity and transform only. */
.amb { position: relative; isolation: isolate; }
.amb::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: -8%;
  right: -6%;
  width: min(420px, 46%);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle closest-side, rgba(185, 141, 47, 0.10), rgba(185, 141, 47, 0) 70%);
  pointer-events: none;
  animation: amb-drift 18s ease-in-out infinite;
}
.amb-left::before { right: auto; left: -6%; top: auto; bottom: -8%; animation-delay: -9s; }
@keyframes amb-drift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 0.85; }
  50%      { transform: translate3d(-3%, 4%, 0) scale(1.08); opacity: 1; }
}

/* ---------- Scroll progress ----------
   Driven by scroll(), which runs off the main thread. The JS fallback writes
   --sp on <html> instead; both feed the same transform, so there is one bar
   and one visual, not two implementations. */
.scroll-progress {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 200;
  width: 100%;
  height: 2px;
  transform-origin: left center;
  transform: scaleX(var(--sp, 0));
  background: linear-gradient(90deg, var(--accent), rgba(185, 141, 47, 0.45));
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  .scroll-progress {
    transform: scaleX(0);
    animation: sp-grow linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes sp-grow { to { transform: scaleX(1); } }
}

/* ---------- Nav response to scroll direction ----------
   Retracts on the way down, returns on the way up. The class is written by the
   one shared rAF loop in main.js. */
.site-nav { transition: transform 0.42s cubic-bezier(0.16, 1, 0.3, 1); }
.site-nav.nav-hidden { transform: translateY(-100%); }
.site-nav.nav-open, .site-nav:focus-within { transform: none; }


/* ---------- Pinned sequence ----------
   .pin-wrap supplies the scroll distance; .pin-stage is what stays put. The
   number written by main.js is --pin (0 to 1) plus data-stage (0 to 5), and the
   CSS below reads ONLY those two, so the JS never touches a style directly.

   position:sticky means the hold itself is CSS: with JS disabled the section
   still pins, and because every stage defaults to lit, nothing is hidden. */
/* Two numbers that have to be right together, and were not.

   Height was 220vh, which is 1.2 extra viewports of scroll for a five item
   rail: measured at 1280x900 it left 1586px of empty page below the content.
   The travel is now an explicit pixel budget rather than a multiple of the
   viewport, so the hold is the same length on a laptop and on a tall monitor.

   And the stage now FILLS the viewport it is holding. Sticky pins it to the
   top; if it is shorter than the screen, everything below it is blank for the
   whole hold. Centring it in a full-height stage is what makes the pin read as
   deliberate instead of broken. */
.pin-wrap { position: relative; height: calc(100vh + 460px); }

/* Lit is the default. Dimming applies only once a stage number exists, which
   is the same JS-off contract used everywhere else in this system. */
.anatomy-pinned > li { transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1); }
/* The stage currently arriving gets the gold numeral, so there is always
   exactly one focus point rather than a growing block of highlight. */
[data-stage="1"] .anatomy-pinned > li:nth-child(1) .anatomy-n,
[data-stage="2"] .anatomy-pinned > li:nth-child(2) .anatomy-n,
[data-stage="3"] .anatomy-pinned > li:nth-child(3) .anatomy-n,
[data-stage="4"] .anatomy-pinned > li:nth-child(4) .anatomy-n,
[data-stage="5"] .anatomy-pinned > li:nth-child(5) .anatomy-n { color: var(--accent); }

/* A pinned section on a phone means a long dead scroll for a list that is one
   column tall anyway, so the pin is desktop-only. */
@media (max-width: 900px) {
}

/* ============================================================
   PRODUCTS
   ============================================================
   The products page carries four platform write-ups, each with a diagram.
   Everything below is scoped to classes that appear nowhere else, so nothing
   here can reach the other seven pages.

   The diagrams are the reason this section is long. They are inline SVG rather
   than images because they have to recolour with the palette, stay sharp at any
   width, and weigh nothing: the four of them together are smaller than one
   screenshot would have been.

   On what they animate: transform, opacity and stroke-dashoffset as everywhere
   else, plus fill and stroke on the state markers and offset-distance on the
   workforce runners. fill and stroke are paint-only - no SVG geometry depends
   on them, so nothing reflows - but unlike the rest of the motion system they
   repaint on the main thread rather than compositing. That is the reason every
   diagram carries [data-fx-pause]: the observer already in main.js stops them
   the moment they leave the viewport. */

/* ---------- Hero: one core, four systems ---------- */
.fx-suite { width: 100%; height: 100%; }

.su-link {
  stroke: rgba(255, 255, 255, 0.26);
  stroke-width: 1.5;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: su-draw 0.9s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.su-link-1 { animation-delay: 0.15s; }
.su-link-2 { animation-delay: 0.30s; }
.su-link-3 { animation-delay: 0.45s; }
.su-link-4 { animation-delay: 0.60s; }
@keyframes su-draw { to { stroke-dashoffset: 0; } }

.su-node { opacity: 0; transform-origin: center; animation: su-node-in 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
.su-node-1 { animation-delay: 0.55s; }
.su-node-2 { animation-delay: 0.70s; }
.su-node-3 { animation-delay: 0.85s; }
.su-node-4 { animation-delay: 1.00s; }
@keyframes su-node-in {
  from { opacity: 0; transform: scale(0.9); }
  to   { opacity: 1; transform: scale(1); }
}
.su-row { fill: rgba(255, 255, 255, 0.28); }

.su-core { fill: none; stroke: rgba(185, 141, 47, 0.75); stroke-width: 1.5; }
.su-halo {
  fill: none;
  stroke: rgba(185, 141, 47, 0.4);
  stroke-width: 1;
  transform-origin: 160px 160px;
  animation: su-halo 3.6s ease-out infinite;
}
@keyframes su-halo {
  0%   { opacity: 0.7; transform: scale(0.6); }
  100% { opacity: 0;   transform: scale(1.25); }
}

/* One packet per spoke, each leaving the core on its own beat. Longhands, not
   the shorthand: naming a keyframe here that does not exist and relying on each
   variant to replace it would leave any packet without a variant class stuck at
   opacity 0 and invisible. Only the direction differs, so only the name and the
   delay are set per spoke. */
.su-packet {
  opacity: 0;
  animation-duration: 4.2s;
  animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
  animation-iteration-count: infinite;
}
.su-packet-1 { animation-name: su-out-up; animation-delay: 1.2s; }
.su-packet-2 { animation-name: su-out-right; animation-delay: 1.6s; }
.su-packet-3 { animation-name: su-out-down; animation-delay: 2.0s; }
.su-packet-4 { animation-name: su-out-left; animation-delay: 2.4s; }
@keyframes su-out-up    { 0% { transform: translateY(0); opacity: 0; } 15% { opacity: 1; } 60% { opacity: 1; } 100% { transform: translateY(-78px); opacity: 0; } }
@keyframes su-out-right { 0% { transform: translateX(0); opacity: 0; } 15% { opacity: 1; } 60% { opacity: 1; } 100% { transform: translateX(78px);  opacity: 0; } }
@keyframes su-out-down  { 0% { transform: translateY(0); opacity: 0; } 15% { opacity: 1; } 60% { opacity: 1; } 100% { transform: translateY(78px);  opacity: 0; } }
@keyframes su-out-left  { 0% { transform: translateX(0); opacity: 0; } 15% { opacity: 1; } 60% { opacity: 1; } 100% { transform: translateX(-78px); opacity: 0; } }

/* ---------- Product index tiles ---------- */
.prod-index { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-3); }
.prod-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: clamp(24px, 2.8vw, 34px);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              border-color 0.45s ease;
}
/* Same gold edge the cards elsewhere use, so a tile reads as part of the
   same family rather than a new component. */
.prod-tile::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(90deg, var(--accent), rgba(185, 141, 47, 0));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Split from the :has() variants below on purpose. CSS drops an entire selector
   list if one selector in it will not parse, so pairing :hover with :has() in
   one list would cost a browser without :has() the hover lift as well. */
.prod-tile:hover {
  transform: translateY(-5px);
  box-shadow: 0 18px 44px -22px rgba(4, 24, 56, 0.42);
  border-color: rgba(185, 141, 47, 0.42);
}
.prod-tile:hover::before { transform: scaleX(1); }

/* Stretched link. The heading anchor is the only link in the tile; this spreads
   its hit area over the whole card without making the card itself an <a>, which
   is what kept the accessible name down to the product name. */
.prod-tile h3 a, .prod-mini h3 a { color: inherit; }
.prod-tile h3 a::after, .prod-mini h3 a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  /* Required, not decorative. .link-arrow is position:relative (see the link
     arrow block above) and is the LAST child of the tile, so with both at
     z-index:auto it paints after this overlay and takes the clicks. Because the
     tile is a column flex container, that span stretches to the full inner
     width, which made the entire bottom strip of every tile unclickable while
     still lifting and underlining on hover. */
  z-index: 1;
}
/* The underline is driven from the card now: the overlay sits above the span,
   so the span's own :hover no longer fires. */
.prod-tile:hover .link-arrow::before { transform: scaleX(1); }
/* Move the focus ring off the heading text and onto the card, but only where
   :has() can put it back. Without the guard, a browser that cannot match
   :has() would get the inner ring removed and nothing in its place. */
@supports selector(:has(a)) {
  .prod-tile h3 a:focus-visible, .prod-mini h3 a:focus-visible { outline: none; }
  .prod-tile:has(a:focus-visible) {
    outline: 2px solid var(--navy-600);
    outline-offset: 3px;
    transform: translateY(-5px);
    box-shadow: 0 18px 44px -22px rgba(4, 24, 56, 0.42);
    border-color: rgba(185, 141, 47, 0.42);
  }
  .prod-tile:has(a:focus-visible)::before { transform: scaleX(1); }
  .prod-tile:has(a:focus-visible) .link-arrow::before { transform: scaleX(1); }
  .prod-mini:has(a:focus-visible) {
    outline: 2px solid var(--white);
    outline-offset: 3px;
    transform: translateY(-5px);
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(185, 141, 47, 0.5);
  }
}

.prod-code {
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--accent);
}
.prod-tile h3 { font-size: var(--t-h3); color: var(--navy-800); }
.prod-kind {
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--slate);
  margin-top: -6px;
}
.prod-tile > p { color: var(--slate); font-size: var(--t-small); line-height: 1.6; }
.prod-tags { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.prod-tags li {
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--navy-700);
  background: var(--navy-100);
  border: 1px solid rgba(6, 33, 90, 0.1);
  border-radius: 999px;
  padding: 3px 10px;
}
.prod-tile .link-arrow { margin-top: auto; padding-top: 6px; }

/* ---------- Product write-up ----------
   Head across the top, then diagram and detail side by side. Flipping puts the
   diagram on the right, which is what alternates the rhythm down the page. */
.product {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(24px, 3.4vw, 52px) clamp(28px, 4vw, 60px);
  align-items: start;
}
.product-head { grid-column: 1 / -1; max-width: 68ch; }
.product-head h2 { font-size: clamp(1.7rem, 3vw, 2.35rem); color: var(--navy-800); margin-bottom: 14px; }
.product-head .lede { color: var(--slate); font-size: var(--t-lede); line-height: 1.7; }
.product-flip .product-visual { order: 2; }
.product-flip .product-detail { order: 1; }
.product-detail h3 {
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--slate);
  margin: var(--s-4) 0 var(--s-3);
}
/* Any first child, not just the stats strip. The School product has no stats
   row, so scoping this to .work-stats left its detail column starting a whole
   --s-4 below the diagram beside it while the other three sat flush. */
.product-detail > :first-child { margin-top: 0; }
/* The shared rule is a fixed four columns; these rows carry two, three and
   four, so they size to their content instead. */
.product-detail .work-stats { grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); }
.product-detail .cap-list { margin-bottom: 0; }
.product + .combo-grid { margin-top: var(--s-6); }

/* ---------- Numbered pipeline steps ---------- */
.prod-steps { list-style: none; display: grid; gap: var(--s-3); counter-reset: none; }
.prod-steps li { display: grid; grid-template-columns: auto 1fr; gap: 14px; align-items: start; }
.prod-step-n {
  font-family: var(--font-display);
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--accent);
  padding-top: 2px;
  min-width: 22px;
}
.prod-steps h4 { font-size: 0.95rem; color: var(--navy-800); margin-bottom: 3px; }
.prod-steps p { color: var(--slate); font-size: var(--t-small); line-height: 1.6; }

/* ---------- Detection family grid ---------- */
.famgrid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--s-2);
  margin-bottom: var(--s-5);
}
.fam {
  display: grid;
  gap: 3px;
  padding: var(--s-3);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 10px;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.4s ease;
}
.fam:hover { transform: translateY(-4px); border-color: rgba(185, 141, 47, 0.42); }
.fam-code {
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--accent);
}
.fam strong {
  font-family: var(--font-head);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--navy-800);
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.fam-name { color: var(--slate); font-size: 0.7rem; line-height: 1.35; }

/* ---------- Module chip grid ---------- */
.modgrid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-bottom: var(--s-5);
}
.mod {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 8px;
  transition: border-color 0.35s ease, transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.mod:hover { border-color: rgba(185, 141, 47, 0.42); transform: translateY(-2px); }
.mod-code {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--accent);
}
.mod-name { color: var(--navy-800); font-size: 0.76rem; }
.mod-n { color: var(--slate); font-size: 0.72rem; font-variant-numeric: tabular-nums; }

/* ---------- Brochure download ----------
   The source document for a product page, offered as a file. Deliberately
   heavier than a text link: it is the one thing on the page a procurement
   reader is looking for, and it should be findable at a glance down the page. */
.brochure {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-top: var(--s-5);
  padding: clamp(22px, 2.6vw, 32px);
  background: var(--navy-100);
  border: 1px solid rgba(6, 33, 90, 0.14);
  border-radius: var(--radius);
}
.brochure + .brochure { margin-top: var(--s-3); }
.brochure-body { flex: 1 1 30ch; }
.brochure-kicker {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* navy-600, not the gold accent, and for a measurable reason: --accent on
     --navy-100 is 2.6:1, which fails AA for text this size. This follows what
     .kicker already does on light surfaces - navy for the words, gold kept for
     the rule beside them. */
  color: var(--navy-600);
  margin-bottom: 8px;
}
.brochure-kicker::before {
  content: "";
  display: inline-block;
  width: 20px;
  height: 1px;
  margin-right: 9px;
  vertical-align: middle;
  background: var(--accent);
  opacity: 0.75;
}
.brochure h3 { font-size: var(--t-h3); color: var(--navy-800); margin-bottom: 8px; }
/* Scoped to the body column, not `.brochure p`. The kicker and the meta line are
   both <p> too, and a bare `.brochure p` (0,1,1) outranks their own single-class
   rules (0,1,0) - which silently repainted the kicker slate and reset the meta
   line's size. */
.brochure-body p:not(.brochure-kicker) { color: var(--slate); font-size: var(--t-small); line-height: 1.6; max-width: 62ch; }
.brochure-action { display: grid; gap: 8px; justify-items: start; }
/* The size and page count belong next to the control, not in the prose: a
   reader deciding whether to tap a download on mobile data wants the number
   before the tap, not after it. */
.brochure-meta { color: var(--slate); font-size: 0.74rem; }

@media (max-width: 620px) {
  .brochure { flex-direction: column; align-items: stretch; }
  .brochure-action { justify-items: stretch; }
  .brochure-action .btn { justify-content: center; }
}

/* ---------- What it runs on ---------- */
.prod-runs {
  border-top: 1px solid var(--line);
  padding-top: var(--s-4);
}
.prod-runs h3 {
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: var(--s-3);
}
.prod-runs-note { color: var(--slate); font-size: var(--t-small); margin-top: var(--s-3); max-width: 70ch; }

/* .stack-tags had no base rule anywhere in this file - only a reveal transition
   further down - so the list it was reused for rendered as a default bulleted
   <ul>. Same pill as .case-stack, which is the equivalent component on the case
   study pages. */
.stack-tags { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
.stack-tags li {
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--navy-800);
  background: var(--navy-100);
  border: 1px solid rgba(6, 33, 90, 0.12);
  border-radius: 999px;
  padding: 5px 13px;
}

/* ============================================================
   PRODUCT DIAGRAMS
   ============================================================ */
.pv { width: 100%; }
.pv svg { width: 100%; height: auto; overflow: visible; }

/* main.js already observes [data-fx-pause] and toggles .fx-paused on exit, but
   the rule that acts on it was written per-component. On descendants, not the
   container: animation-play-state does not inherit through an element that has
   no animation of its own, and .pv itself has none. */
.pv.fx-paused * { animation-play-state: paused !important; }

/* Shared vocabulary across all four diagrams, so they read as one set. */
.pv-panel { fill: rgba(6, 33, 90, 0.03); stroke: var(--line); stroke-width: 1; }
.pv-chip  { fill: var(--white); stroke: var(--line); stroke-width: 1; }
.pv-chip-gold { stroke: rgba(185, 141, 47, 0.55); }
.pv-card  { fill: var(--white); stroke: var(--line); stroke-width: 1; }
.pv-cardbar { fill: var(--accent); opacity: 0.85; }
.pv-line  { fill: rgba(6, 33, 90, 0.16); }
.pv-line-dim { opacity: 0.55; }
.pv-rail, .pv-feed, .pv-drop, .pv-return {
  stroke: rgba(6, 33, 90, 0.2);
  stroke-width: 1.4;
  fill: none;
}
.pv-return { stroke-dasharray: 3 5; opacity: 0.6; }
.pv-pkt { fill: var(--accent); }
.pv-pkt-dot { fill: var(--accent); }
.pv-led { fill: var(--accent); }
.pv-lbl {
  font-family: var(--font-body);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  fill: var(--navy-800);
}
.pv-sub { font-family: var(--font-body); font-size: 7.5px; fill: var(--slate); }
.pv-sub-gold { fill: var(--accent); font-weight: 600; }
.pv-cap {
  font-family: var(--font-body);
  font-size: 7.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  fill: var(--slate);
}
.pv-tag { font-family: var(--font-body); font-size: 7px; font-weight: 700; fill: var(--navy-900); }
.pv-tagbg { fill: var(--accent); }
.pv-pill { font-family: var(--font-body); font-size: 8px; font-weight: 700; letter-spacing: 0.1em; fill: var(--navy-800); }
.pv-pillbg { fill: var(--navy-100); stroke: rgba(6, 33, 90, 0.1); stroke-width: 1; }

/* Every diagram's links draw themselves in once, on the same curve as the
   .reveal system, so the drawing reads as part of the entrance. */
.pv-rail, .pv-feed, .pv-drop {
  stroke-dasharray: 400;
  stroke-dashoffset: 400;
  animation: pv-draw 1.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes pv-draw { to { stroke-dashoffset: 0; } }

/* ---------- Vigil diagram ---------- */
.pv-box rect:first-child { fill: none; stroke: var(--accent); stroke-width: 1.4; }
.pv-box { opacity: 0; animation: pv-box-in 5.6s ease-in-out infinite; }
.pv-box-1 { animation-delay: 0.4s; }
.pv-box-2 { animation-delay: 1.2s; }
.pv-box-3 { animation-delay: 2.0s; }
@keyframes pv-box-in {
  0%, 6%    { opacity: 0; }
  12%, 52%  { opacity: 1; }
  62%, 100% { opacity: 0; }
}
.pv-scan {
  fill: var(--accent);
  opacity: 0.5;
  animation: pv-scan 5.6s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
@keyframes pv-scan {
  0%        { transform: translateY(0);     opacity: 0; }
  8%        { opacity: 0.55; }
  46%       { transform: translateY(118px); opacity: 0.55; }
  54%, 100% { transform: translateY(118px); opacity: 0; }
}
.pv-flow { animation: pv-flow 5.6s cubic-bezier(0.5, 0, 0.5, 1) infinite; opacity: 0; }
.pv-flow-b { animation-delay: 0.5s; }
@keyframes pv-flow {
  0%        { transform: translateX(0);   opacity: 0; }
  10%       { opacity: 1; }
  50%       { transform: translateX(272px); opacity: 1; }
  58%, 100% { transform: translateX(272px); opacity: 0; }
}
.pv-stage .pv-led { animation: pv-led 5.6s ease-in-out infinite; }
.pv-stage-1 .pv-led { animation-delay: 1.1s; }
.pv-stage-2 .pv-led { animation-delay: 1.7s; }
.pv-stage-3 .pv-led { animation-delay: 2.3s; }
@keyframes pv-led {
  0%, 14%   { opacity: 0.25; }
  20%, 54%  { opacity: 1; }
  62%, 100% { opacity: 0.25; }
}
.pv-incident { opacity: 0; animation: pv-incident 5.6s ease-out infinite; animation-delay: 2.4s; }
@keyframes pv-incident {
  0%, 4%    { opacity: 0; transform: translateY(7px); }
  14%, 60%  { opacity: 1; transform: translateY(0); }
  72%, 100% { opacity: 0; transform: translateY(0); }
}
.pv-alert { fill: none; stroke: rgba(6, 33, 90, 0.22); stroke-width: 1.4; }
.pv-alert { animation: pv-alert 5.6s ease-out infinite; }
.pv-alert-1 { animation-delay: 3.0s; }
.pv-alert-2 { animation-delay: 3.15s; }
.pv-alert-3 { animation-delay: 3.30s; }
.pv-alert-4 { animation-delay: 3.45s; }
@keyframes pv-alert {
  0%, 8%   { stroke: rgba(6, 33, 90, 0.22); }
  16%, 46% { stroke: var(--accent); }
  60%, 100% { stroke: rgba(6, 33, 90, 0.22); }
}

/* ---------- Workforce diagram ---------- */
.pv-ch .pv-led { animation: pv-led 6s ease-in-out infinite; }
.pv-ch-1 .pv-led { animation-delay: 0s; }
.pv-ch-2 .pv-led { animation-delay: 0.4s; }
.pv-ch-3 .pv-led { animation-delay: 0.8s; }

/* Runners ride the same paths the feeders draw. offset-path is the whole
   mechanism, so where it is unsupported the dots are removed rather than left
   parked at the origin, which would read as three stray marks. */
.pv-runner { display: none; }
@supports (offset-path: path("M0 0 L1 1")) {
  .pv-runner { display: block; offset-rotate: 0deg; animation: pv-run 6s cubic-bezier(0.45, 0, 0.55, 1) infinite; opacity: 0; }
  .pv-runner-1 { offset-path: path("M134 57 H172 a10 10 0 0 1 10 10 V132 a10 10 0 0 0 10 10 H214"); animation-delay: 0.2s; }
  .pv-runner-2 { offset-path: path("M134 141 H214"); animation-delay: 0.6s; }
  .pv-runner-3 { offset-path: path("M134 225 H172 a10 10 0 0 0 10 -10 V152 a10 10 0 0 1 10 -10 H214"); animation-delay: 1.0s; }
}
@keyframes pv-run {
  0%       { offset-distance: 0%;   opacity: 0; }
  8%       { opacity: 1; }
  34%      { offset-distance: 100%; opacity: 1; }
  40%, 100% { offset-distance: 100%; opacity: 0; }
}

.pv-gate .pv-chip-gold { animation: pv-gate 6s ease-in-out infinite; animation-delay: 1.6s; }
@keyframes pv-gate {
  0%, 24%   { stroke: rgba(185, 141, 47, 0.35); }
  32%, 56%  { stroke: rgba(185, 141, 47, 0.95); }
  66%, 100% { stroke: rgba(185, 141, 47, 0.35); }
}
.pv-verdict { opacity: 0.34; animation: pv-verdict 6s ease-out infinite; }
.pv-verdict-ok  { animation-delay: 2.1s; }
.pv-verdict-ref { animation-delay: 2.3s; }
.pv-verdict-rej { animation-delay: 2.5s; }
@keyframes pv-verdict {
  0%, 30%   { opacity: 0.34; }
  38%, 62%  { opacity: 1; }
  74%, 100% { opacity: 0.34; }
}
/* Accepted is the ordinary path, so it is the one that lights gold. */
.pv-verdict-ok .pv-pillbg { animation: pv-verdict-ok 6s ease-out infinite; animation-delay: 2.1s; }
@keyframes pv-verdict-ok {
  0%, 30%   { fill: var(--navy-100); stroke: rgba(6, 33, 90, 0.1); }
  38%, 62%  { fill: rgba(185, 141, 47, 0.14); stroke: rgba(185, 141, 47, 0.5); }
  74%, 100% { fill: var(--navy-100); stroke: rgba(6, 33, 90, 0.1); }
}
.pv-record { opacity: 0; animation: pv-incident 6s ease-out infinite; animation-delay: 3.1s; }

/* ---------- School diagram ---------- */
.pv-mark { fill: rgba(6, 33, 90, 0.08); }
.pv-row .pv-mark { animation: pv-mark 7s ease-out infinite; animation-delay: var(--d, 0s); }
/* The row carries its own beat as a custom property, and the three columns read
   it with an offset. Done this way because the obvious version - a delay on
   .pv-row-N .pv-mark plus a per-column override - cannot work: the row selector
   is the more specific of the two, so the column rule never lands and every
   mark in a row fires together. */
.pv-row-1 { --d: 0.20s; }
.pv-row-2 { --d: 0.45s; }
.pv-row-3 { --d: 0.70s; }
.pv-row-4 { --d: 0.95s; }
.pv-row-5 { --d: 1.20s; }
.pv-row-6 { --d: 1.45s; }
.pv-row .pv-mark-b { animation-delay: calc(var(--d, 0s) + 0.10s); }
.pv-row .pv-mark-c { animation-delay: calc(var(--d, 0s) + 0.20s); }
@keyframes pv-mark {
  0%, 8%    { fill: rgba(6, 33, 90, 0.08); }
  16%, 62%  { fill: rgba(185, 141, 47, 0.55); }
  74%, 100% { fill: rgba(6, 33, 90, 0.08); }
}
.pv-role .pv-led { animation: pv-led 7s ease-in-out infinite; }
.pv-role-1 .pv-led { animation-delay: 2.0s; }
.pv-role-2 .pv-led { animation-delay: 2.2s; }
.pv-role-3 .pv-led { animation-delay: 2.4s; }
.pv-role-4 .pv-led { animation-delay: 2.6s; }
.pv-role-5 .pv-led { animation-delay: 2.8s; }
.pv-role .pv-chip { animation: pv-role 7s ease-out infinite; }
.pv-role-1 .pv-chip { animation-delay: 2.0s; }
.pv-role-2 .pv-chip { animation-delay: 2.2s; }
.pv-role-3 .pv-chip { animation-delay: 2.4s; }
.pv-role-4 .pv-chip { animation-delay: 2.6s; }
.pv-role-5 .pv-chip { animation-delay: 2.8s; }
@keyframes pv-role {
  0%, 22%   { stroke: var(--line); }
  30%, 60%  { stroke: rgba(185, 141, 47, 0.55); }
  72%, 100% { stroke: var(--line); }
}

/* ---------- Commerce diagram ---------- */
.pv-surface .pv-led { animation: pv-led 6.4s ease-in-out infinite; }
/* Each surface lights on the same beat as the order state it owns, so the two
   read as one sequence: raised on the website, taken up in the admin panel,
   carried by the rider, confirmed back to the customer app. */
.pv-surface-1 .pv-led { animation-delay: 0.2s; }
.pv-surface-2 .pv-led { animation-delay: 1.8s; }
.pv-surface-3 .pv-led { animation-delay: 3.4s; }
.pv-surface-4 .pv-led { animation-delay: 5.0s; }
.pv-order { animation: pv-order 6.4s ease-in-out infinite; }
@keyframes pv-order {
  0%, 100% { stroke: var(--line); }
  50%      { stroke: rgba(185, 141, 47, 0.5); }
}
/* One state at a time, walking round the order: raised, accepted, picked up,
   settled. */
.pv-state { fill: rgba(6, 33, 90, 0.14); animation: pv-state 6.4s ease-out infinite; }
.pv-state-1 { animation-delay: 0.2s; }
.pv-state-2 { animation-delay: 1.8s; }
.pv-state-3 { animation-delay: 3.4s; }
.pv-state-4 { animation-delay: 5.0s; }
@keyframes pv-state {
  0%, 2%    { fill: rgba(6, 33, 90, 0.14); }
  6%, 22%   { fill: var(--accent); }
  28%, 100% { fill: rgba(6, 33, 90, 0.14); }
}

/* ---------- Home: products band ----------
   The one navy band in the middle of the page. It borrows the footer's
   treatment rather than inventing a third dark surface. */
.prod-band {
  position: relative;
  overflow: hidden;
  background: linear-gradient(165deg, var(--navy-700) 0%, var(--navy-800) 52%, var(--navy-900) 100%);
  color: var(--white);
}
.prod-band .section-head h2 { color: var(--white); }
.prod-band-lede { color: var(--mist); max-width: 74ch; margin-bottom: var(--s-5); font-size: var(--t-lede); }
.link-arrow-light { color: var(--white); }
.link-arrow-light::before { background: var(--accent); }

.prod-band-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-3); }
.prod-mini {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: var(--s-4);
  border: 1px solid var(--line-dark);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
              background 0.45s ease,
              border-color 0.45s ease;
}
.prod-mini:hover {
  transform: translateY(-5px);
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(185, 141, 47, 0.5);
}
.prod-mini h3 { font-size: 1rem; color: var(--white); }
.prod-mini p { color: var(--mist); font-size: var(--t-small); line-height: 1.58; }
.prod-mini-stat {
  margin-top: auto;
  padding-top: 10px;
  font-family: var(--font-head);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent);
}

/* ---------- Products: responsive ---------- */
@media (max-width: 1080px) {
  .famgrid { grid-template-columns: repeat(4, 1fr); }
  .modgrid { grid-template-columns: repeat(3, 1fr); }
  .prod-band-grid { grid-template-columns: repeat(2, 1fr); }

  /* The diagrams collapse to one column here rather than at 900, because the
     two-column layout gets WORSE before it gets better. The diagram is a
     viewBox scaled to its track, so its 8.5px labels render at track/520. Just
     above 900px the flipped products put their diagram in the narrower 0.95fr
     track (~390px, scale 0.75, labels at 6.4px) — smaller than they render at
     899px, where the diagram takes the full container. Collapsing early removes
     that non-monotonic band instead of leaving it to be found by accident. */
  .product { grid-template-columns: 1fr; }
  .product-flip .product-visual { order: 0; }
  .product-flip .product-detail { order: 0; }
}
@media (max-width: 900px) {
  .prod-index { grid-template-columns: 1fr; }
  .famgrid { grid-template-columns: repeat(3, 1fr); }
  .modgrid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .famgrid { grid-template-columns: repeat(2, 1fr); }
  .modgrid { grid-template-columns: 1fr; }
  .product-detail .work-stats { grid-template-columns: repeat(2, 1fr); }
  .prod-band-grid { grid-template-columns: 1fr; }

  /* Below this the diagrams stop being readable rather than just small: at a
     360px viewport the container is 328px, so the 8.5px labels land at 5.4px
     and the 7.5px ones at 4.7px. They are decorative — aria-hidden, and every
     element in them is written out in the copy beside them — so they are
     dropped rather than shipped as 189px of grey smear per product. Same
     reasoning as the pinned sequence being desktop-only. */
  .pv { display: none; }
}

/* ---------- Cross-document view transitions ----------
   Opt-in only. Browsers without it navigate exactly as before, and because it
   is still a real navigation the back button, bfcache and scroll restoration
   are untouched. Nothing here blocks first paint: no JS is involved and the
   old page is captured by the browser, not by script. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vt-out 0.18s ease both; }
::view-transition-new(root) { animation: vt-in 0.28s cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(8px); } }

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 900px) {
  /* The stage stops competing with the copy and becomes atmosphere. */
  .fx-stage {
    right: -14%;
    width: clamp(300px, 62vw, 460px);
    height: clamp(300px, 62vw, 460px);
    opacity: 0.5;
  }
}
@media (max-width: 620px) {
  .fx-stage { right: -26%; top: 38%; opacity: 0.32; }
  .fx-grid { background-size: 40px 40px; }
}

/* ============================================================
   REDUCED MOTION
   Every animation introduced above, answered.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  /* Structural visuals freeze in their completed state: links fully
     drawn, plates in place, nodes lit. The reader sees the finished
     diagram, which is what the animation was building toward anyway. */
  .fx-bridge  .br-plate, .fx-bridge .br-seam, .fx-bridge .br-row,
  .fx-layers  .ly-plate, .fx-layers .ly-tick,
  .fx-pipe    .pp-flow, .fx-pipe .pp-node,
  .fx-const   .cn-link, .fx-const .cn-node,
  .fx-radar   .rd-ring,
  .fx-phalanx .ph-shield, .fx-phalanx .ph-lambda,
  .fx-signal  .sg-far {
    animation: none !important;
    opacity: 1;
    transform: none;
  }
  .fx-const .cn-link { stroke-dashoffset: 0; }
  .fx-radar .rd-ring { opacity: 0.24; }

  /* Things that exist only to move are removed, not frozen: a packet
     parked mid-channel reads as a bug, not as a diagram. */
  .fx-bridge .br-packet,
  .fx-layers .ly-scan,
  .fx-pipe   .pp-retry, .fx-pipe .pp-alert,
  .fx-const  .cn-halo,
  .fx-radar  .rd-sweep, .fx-radar .rd-blip,
  .fx-signal .sg-ring, .fx-signal .sg-out, .fx-signal .sg-back {
    display: none !important;
  }

  .status-dot { animation: none !important; box-shadow: none !important; }
  /* Choreography carries no information the copy does not already carry, so
     every variant collapses to "present". The clip must be cleared explicitly:
     a wipe left at inset(0 0 100% 0) would hide the block entirely. */
  .js-reveal [data-choreo] .reveal {
    clip-path: none !important;
    transform: none !important;
    transition: none !important;
  }
  .js-reveal [data-stagger] > .reveal { transition-delay: 0ms !important; }
  .section-head .kicker::after { transition: none !important; transform: scaleX(1) !important; }
  .combo, .cap-card, .industry, .depth-item, .stack-card,
  .anatomy > li, .anatomy-n, .stack-tags li { transition: none !important; }
  .combo:hover, .cap-card:hover, .depth-item:hover, .stack-card:hover,
  .anatomy > li:hover, .stack-tags li:hover { transform: none !important; }
  .combo::before, .stack-card::before, .depth-item::before { transition: none !important; }
  /* ---------- in-body choreography ----------
     Everything here carries structure, so it freezes in its FINISHED state
     rather than being removed: the diagrams must still read as diagrams. */
  .ba-flow > li, .ba-after::after, .ba-arrow,
  .anatomy > li, .anatomy-n,
  .matrix-row, .matrix-head .matrix-us,
  .quote-panel, .work-stats li,
  .checklist li, .checklist li::after,
  .stack-tags li, .faq-item summary, .faq-item summary::after,
  .link-arrow::before, .site-nav {
    transition: none !important;
    transition-delay: 0s !important;
    transform: none !important;
    opacity: 1 !important;
  }
  .checklist li::after { transform: scale(1) !important; }
  /* Left clipped, a heading is invisible. This one MUST be cleared. */
  .js-reveal .reveal h2, .js-reveal .reveal .h-wipe,
  .js-reveal .reveal:not(.visible) h2, .js-reveal .reveal:not(.visible) .h-wipe {
    clip-path: none !important;
    transition: none !important;
  }
  /* Exists only to drift. Nothing is lost by removing it. */
  .amb::before { display: none !important; }
  /* The progress bar reports the reader's own scrolling rather than moving on
     its own, so it stays. Only the CSS-driven variant is stood down; the
     custom property path in main.js keeps it accurate. */
  .scroll-progress { animation: none !important; }
  .site-nav.nav-hidden { transform: none !important; }
  .cap-more > summary::after { transition: none !important; }
  .cap-more-body { animation: none !important; }

  /* ---------- hero system core ----------
     Structure freezes where it is: the rings, bezel, orbits, lattice and core
     all still describe a system, they just stop turning. */
  .cr-ring-a, .cr-ring-b, .cr-bezel, .cr-arc-1, .cr-arc-2, .cr-arc-3,
  .cr-orbit-a, .cr-orbit-b, .cr-hex, .cr-disc, .cr-seed {
    animation: none !important;
    transform: none !important;
  }
  /* These exist only to move. A packet parked mid-spoke or a radar wedge frozen
     at one bearing reads as a rendering bug, not as a diagram. */
  .cr-packets, .cr-sweep, .cr-pulse { display: none !important; }
  /* The pin holds and the stages stay lit: the sequence is still readable,
     it just does not dim ahead of the reader. */
  [data-stage] .anatomy-pinned > li { opacity: 1 !important; }
  /* A cross-fade between documents is motion the reader did not ask for. */
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }

  /* ---------- products ----------
     The four diagrams carry structure, so they freeze in their FINISHED state:
     links drawn, plates present, LEDs lit, the register filled, the incident
     and the day record on screen. A reader with reduced motion gets the
     completed diagram, which is the thing the animation was assembling. */
  .su-link, .su-node,
  .pv-rail, .pv-feed, .pv-drop,
  .pv-box, .pv-led, .pv-incident, .pv-record,
  .pv-alert, .pv-gate .pv-chip-gold, .pv-verdict, .pv-verdict-ok .pv-pillbg,
  .pv-mark, .pv-role .pv-chip, .pv-order, .pv-state {
    animation: none !important;
    opacity: 1;
    transform: none;
  }
  .su-link { stroke-dashoffset: 0; }
  .pv-rail, .pv-feed, .pv-drop { stroke-dashoffset: 0; }
  .pv-mark { fill: rgba(185, 141, 47, 0.55); }
  .pv-state { fill: var(--accent); }
  .pv-verdict-ok .pv-pillbg { fill: rgba(185, 141, 47, 0.14); stroke: rgba(185, 141, 47, 0.5); }
  .pv-gate .pv-chip-gold { stroke: rgba(185, 141, 47, 0.95); }
  .pv-role .pv-chip { stroke: rgba(185, 141, 47, 0.55); }
  /* Exists only to travel. A packet parked mid-spoke, a scan line stopped
     halfway down a feed, or a runner frozen on a feeder reads as a rendering
     bug rather than as a diagram. */
  .su-packet, .su-halo,
  .pv-scan, .pv-flow, .pv-runner {
    display: none !important;
  }

  .prod-tile, .prod-tile::before, .fam, .mod, .prod-mini,
  .svc, .svc::before { transition: none !important; }
  /* Kept free of :has() so an unparseable selector cannot take .fam and .mod
     down with it; the focus variants get their own guarded rule below. */
  .prod-tile:hover, .fam:hover, .mod:hover, .prod-mini:hover,
  .svc:hover { transform: none !important; }
  @supports selector(:has(a)) {
    .prod-tile:has(a:focus-visible),
    .prod-mini:has(a:focus-visible) { transform: none !important; }
  }
}
