/* ============================================================
   Vivid GovTech — site design system
   Direction: "Cinema" — true-black authority frame, editorial white
   sections, condensed Barlow display, hairline rules, mono
   eyebrows, azure + civic-gold accents. Curtain load + masked
   line intros. Craft-tuned per impeccable rules:
   display tracking ≥ -0.04em, hero type ≤ 6rem, AA contrast,
   extra line-height for light-on-dark text.
   Display: Barlow Condensed · Body: Public Sans · Mono: Azeret Mono
   ============================================================ */

:root {
  /* Frame — the dark authority surfaces: header, hero, trust band, footer,
     page heroes, on-dark sections. True black, held near-neutral so the azure
     accent stays the only hue in play. */
  --frame-900: #000000;   /* page-level black */
  --frame-800: #0c0c0e;   /* raised band / solid button */
  --frame-700: #1a1a1e;   /* solid button hover */

  /* Brand azure */
  --blue-500: #0497ca;   /* brand — fills, graphics */
  --blue-600: #1095c4;   /* logo blue */
  --blue-700: #0a6f95;   /* accessible text accent on light */
  --blue-300: #4cbde3;   /* accent text on navy */
  --blue-50:  #e6f6fc;

  /* Civic gold — accent CTA, hero emphasis, sparingly */
  --gold-500: #eacb13;
  --gold-600: #c4a90c;
  --gold-700: #8f7c08;

  /* Neutrals */
  --white:    #ffffff;
  --gray-50:  #f9fafb;
  --gray-100: #f5f5f5;
  --gray-200: #ebedef;
  --gray-300: #dde1e4;
  --gray-500: #9aa2a9;
  --gray-600: #5d666e;
  --gray-800: #2b3137;
  --gray-900: #1a1d20;

  /* Brand hairlines — azure-tinted rules for section and item dividers.
     Surfaces (cards, inputs) keep neutral borders; rules carry the brand. */
  --rule: #c9e3ef;
  --rule-faint: #ddeef6;
  --wash: #f2f9fd;       /* faint azure section wash */

  /* Text roles */
  --text-strong: var(--gray-900);
  --text-body:   var(--gray-800);
  --text-muted:  var(--gray-600);
  --text-subtle: #5f6a72;

  /* On-frame text */
  --on-frame-body:  rgba(255, 255, 255, .8);
  --on-frame-muted: rgba(255, 255, 255, .68);
  --on-frame-hair:  rgba(255, 255, 255, .16);

  /* Type */
  --f-display: 'Barlow Condensed', 'Helvetica Neue Condensed', system-ui, sans-serif;
  --f-body: 'Public Sans', system-ui, -apple-system, sans-serif;
  --f-mono: 'Azeret Mono', ui-monospace, 'SF Mono', monospace;

  /* Layout */
  --wrap: 1240px;
  --gutter: clamp(20px, 4vw, 40px);
  --section: clamp(88px, 12vw, 140px);
  --radius: 12px;
  --radius-lg: 20px;

  --shadow-card: 0 24px 60px rgba(0, 0, 0, .14);
  --shadow-frame: 0 18px 44px rgba(0, 0, 0, .2);

  --ease-rise: cubic-bezier(.16, 0, .1, 1);
  --ease-wipe: cubic-bezier(.72, 0, .2, 1);
  --ease-snap: cubic-bezier(.19, 1, .22, 1);  /* ease-out-expo — the hero snap */
}

/* ---------- Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--f-body);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--text-body);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg { max-width: 100%; display: block; }
a { color: var(--blue-700); text-decoration: none; }
a:hover { color: var(--text-strong); }
:focus-visible { outline: 2px solid var(--blue-700); outline-offset: 3px; border-radius: 2px; }
.on-dark :focus-visible, .site-head :focus-visible, .site-foot :focus-visible,
.hero :focus-visible, .page-hero :focus-visible { outline-color: var(--gold-500); }

h1, h2, h3, h4 { font-family: var(--f-display); color: var(--text-strong); margin: 0; }
p { margin: 0; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }

.skip-link {
  position: absolute; left: -9999px; top: 12px; z-index: 200;
  background: var(--frame-900); color: #fff; padding: 10px 18px; border-radius: 6px;
  font-size: .9rem; font-weight: 600;
}
.skip-link:focus { left: 12px; color: #fff; }

/* ---------- Type roles ---------- */
.display {
  font-family: var(--f-display);
  font-weight: 800;
  letter-spacing: -0.012em;
  line-height: 1.01;
  color: var(--text-strong);
  text-wrap: balance;
}
.h2 {
  font-family: var(--f-display);
  font-weight: 800;
  font-size: clamp(2.2rem, 4.4vw, 3.9rem);
  letter-spacing: -0.011em;
  line-height: 1.04;
  color: var(--text-strong);
  text-wrap: balance;
}
.h3 {
  font-family: var(--f-display);
  font-weight: 800;
  font-size: clamp(1.2rem, 1.7vw, 1.4rem);
  letter-spacing: -0.006em;
  line-height: 1.16;
}
.lead {
  font-size: clamp(1.1rem, 1.5vw, 1.28rem);
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 660px;
}
.small { font-size: .9rem; line-height: 1.6; color: var(--text-muted); }
.accent { color: var(--blue-700); font-style: normal; }
.on-dark .accent { color: var(--blue-300); }
.mono { font-family: var(--f-mono); }

/* Mono eyebrow — the Cinema section label */
.label {
  font-family: var(--f-mono);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--blue-700);
  margin: 0;
}
.on-dark .label, .label--light { color: var(--blue-300); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  font-family: var(--f-body); font-weight: 600; font-size: .95rem;
  padding: 13px 24px; border-radius: 9px; border: 1.5px solid transparent;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background .18s ease, color .18s ease, border-color .18s ease, filter .18s ease;
}
.btn svg { flex: none; transition: transform .18s ease; }
.btn:hover svg.arr { transform: translateX(3px); }
.btn--lg { padding: 16px 30px; font-size: 1.02rem; border-radius: 10px; }
.btn--sm { padding: 9px 18px; font-size: .88rem; }

.btn--accent { background: var(--gold-500); color: var(--gray-900); border-color: var(--gold-500); }
.btn--accent:hover { background: #f0d63f; border-color: #f0d63f; color: var(--gray-900); }
.btn--solid { background: var(--frame-800); color: #fff; border-color: var(--frame-800); }
.btn--solid:hover { background: var(--frame-700); border-color: var(--frame-700); color: #fff; }
.btn--outline { background: transparent; color: var(--text-strong); border-color: var(--gray-900); }
.btn--outline:hover { background: var(--gray-100); color: var(--text-strong); }
.btn--ghost-light { background: transparent; color: #fff; border-color: rgba(255,255,255,.5); }
.btn--ghost-light:hover { border-color: #fff; color: #fff; }

/* ---------- Header (black frame) ---------- */
.site-head {
  position: sticky; top: 0; z-index: 100;
  background: rgba(0, 0, 0, .82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(255,255,255,.1);
  transition: background .25s ease, border-color .25s ease;
}
.site-head .logo .logo-c { display: none; }

/* Overlay header (home): fixed over the hero, transparent until scrolled.
   Over the light hero the chrome flips dark; over navy it stays white. */
.site-head--over { position: fixed; top: 0; left: 0; right: 0; }
.site-head--over:not(.is-scrolled) {
  background: transparent; border-bottom-color: transparent;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
html:not(.theme-dark) .site-head--over:not(.is-scrolled) .site-nav a { color: var(--text-body); }
html:not(.theme-dark) .site-head--over:not(.is-scrolled) .site-nav a:hover,
html:not(.theme-dark) .site-head--over:not(.is-scrolled) .site-nav a[aria-current="page"] { color: var(--gray-900); }
html:not(.theme-dark) .site-head--over:not(.is-scrolled) .menu-btn { color: var(--gray-900); }
html:not(.theme-dark) .site-head--over:not(.is-scrolled) .logo .logo-w { display: none; }
html:not(.theme-dark) .site-head--over:not(.is-scrolled) .logo .logo-c { display: block; }
.site-head .inner {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  height: 76px;
}
.site-head .logo img { height: 28px; width: auto; }
.site-nav { display: flex; gap: 28px; }
.site-nav a {
  font-size: .93rem; font-weight: 600; color: rgba(255,255,255,.82);
  padding: 6px 0; transition: color .15s ease;
}
.site-nav a:hover { color: #fff; }
.site-nav a[aria-current="page"] { color: #fff; box-shadow: 0 2px 0 var(--gold-500); }
.head-cta { display: flex; align-items: center; gap: 12px; }

.menu-btn {
  display: none; background: none; border: 0; padding: 8px; cursor: pointer; color: #fff;
}
.mob-nav { display: none; }

@media (max-width: 980px) {
  .site-nav { display: none; }
  /* Keep the primary action visible next to the hamburger — compact */
  .head-cta { gap: 8px; }
  .head-cta .btn { padding: 8px 13px; font-size: .8rem; }
  .menu-btn { display: inline-flex; }
  .mob-nav.open {
    display: flex; flex-direction: column;
    border-top: 1px solid rgba(255,255,255,.1);
    background: var(--frame-900); padding: 10px var(--gutter) 22px;
  }
  .mob-nav a { padding: 13px 4px; font-size: 1.05rem; font-weight: 600; color: rgba(255,255,255,.9); border-bottom: 1px solid rgba(255,255,255,.08); }
  .mob-nav a:last-child { border-bottom: 0; color: var(--gold-500); }
}

/* ---------- Sections ---------- */
/* Vertical rhythm is deliberately uneven. One --section value on every band
   is machine cadence; real layouts breathe differently depending on what the
   section is doing. */
.section { padding: var(--section) 0; }
.section--tight { padding: calc(var(--section) * .62) 0; }
.section--loose { padding: calc(var(--section) * 1.32) 0; }
.section--flush-top { padding-top: 0; }
.section--tint { background: var(--gray-50); }

.sec-head { max-width: 960px; }
.sec-head .h2 { margin-top: 20px; }
.sec-head .lead { margin-top: 22px; max-width: 700px; }

.on-dark { background: var(--frame-800); color: var(--on-frame-body); }
.on-dark h2, .on-dark h3, .on-dark .h2, .on-dark .h3 { color: #fff; }
.on-dark .lead { color: var(--on-frame-body); line-height: 1.68; }

/* ---------- Hero (black statement) ---------- */
.hero {
  position: relative; overflow: hidden;
  background: var(--frame-900);
  display: flex; align-items: center;
  min-height: calc(100svh - 76px);
}
.hero-glow {
  position: absolute; top: -140px; right: -90px; width: 620px; height: 620px; pointer-events: none;
  background: radial-gradient(circle, rgba(4,151,202,.28), transparent 62%);
}
.hero-glow--left {
  right: auto; left: -120px; top: auto; bottom: -160px;
  background: radial-gradient(circle, rgba(234,203,19,.1), transparent 60%);
}
.hero .inner { position: relative; width: 100%; padding-top: clamp(48px, 7vh, 84px); padding-bottom: clamp(40px, 6vh, 72px); }
/* Hero masthead: wordmark with the LPI kicker centered beneath it.
   Colored mark on the light hero, white mark on navy (dark theme). */
.hero-mast { display: inline-flex; flex-direction: column; align-items: center; gap: 16px; margin-bottom: 12px; }
/* Five items in the kicker orphan the last one on narrow screens */
.hero-mast .label { text-wrap: balance; }
.hero-logo { height: clamp(58px, 6.5vw, 92px); width: auto; }
.hero-logo--w { display: none; }
html.theme-dark .hero-logo--w { display: block; }
html.theme-dark .hero-logo--c { display: none; }
.hero h1 {
  font-size: clamp(2.9rem, 5.4vw, 5.2rem);
  font-weight: 800; letter-spacing: -0.012em; line-height: 1;
  color: #fff; margin: 28px 0 0;
}
.hero h1 .gold { color: var(--gold-500); font-style: normal; }
.hero-foot { margin-top: 40px; }
.hero .lead { color: var(--on-frame-body); line-height: 1.68; max-width: 560px; margin: 0; }
.hero-ctas { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 32px; }

/* ============================================================
   Hero — "every department, one system"

   Composition: a U-shaped pipeline brackets a centred statement. Its
   two legs rise either side of the copy and its bottom bar runs beneath;
   fourteen use-case tiles hang off the OUTSIDE of the U, three on each
   leg and eight below the bar on two depths. Full-bleed and centred,
   which is deliberately unlike every other section on the site (all
   left-aligned two-column splits).

   The rig is the whole hero box rather than a band below the copy, so
   the pipeline can wrap the type instead of competing with it for
   vertical space. A 78fr/22fr grid puts the copy centred in the U's
   interior while the rig spans both rows.

   The aligned + connected state is the DEFAULT. Scatter, the pipe draw,
   and the pulse all live inside the html.js + no-preference query, so
   scripting-off and reduced-motion render the finished composition
   instead of a wall of debris.
   ============================================================ */
.hero--uni {
  display: grid; grid-template-columns: minmax(0, 1fr);
  /* row 1 is the U's interior, row 2 the band below its bottom bar */
  grid-template-rows: 84fr 16fr;
  min-height: 100svh;
  /* .hero sets align-items:center for its flex layout; inherited into this
     grid it would shrink the rig to content height and collapse every
     percentage-positioned tile onto one line. The rig must fill the hero. */
  align-items: stretch;
}
/* Symmetric padding keeps the copy optically centred while guaranteeing it
   never rides up under the fixed header. */
.hero--uni > .inner {
  grid-area: 1 / 1; align-self: center; z-index: 1;
  padding-top: 68px; padding-bottom: 68px;
}
.uni-copy {
  text-align: center; display: flex; flex-direction: column; align-items: center;
  /* Narrow enough to leave an inner channel between the copy and the legs
     (13%/87%) for the inside tiles. This is what makes both-sided possible;
     widen it and the inner tiles collide with the text. */
  max-width: min(860px, 46vw); margin-inline: auto;
}
/* margin-bottom lifts the mark: the copy block is centred in the U's interior,
   so space added below the mast raises it and lowers the text by half each,
   which also opens up the crowded gap between the wordmark and the headline. */
.hero--uni .hero-mast { gap: 10px; margin-bottom: 32px; }
.hero--uni .hero-logo { height: clamp(48px, 4.8vw, 72px); }
/* Three lines rather than two: at this narrower measure a 3-line setting
   carries a bigger face than cramming the first clause onto one line. */
.hero--uni h1 { margin-top: 16px; font-size: clamp(2.2rem, 4.4vw, 4rem); }
.hero--uni .hero-foot { margin-top: 20px; }
.hero--uni .lead { max-width: min(56ch, 42vw); margin-inline: auto; line-height: 1.6; }
.hero--uni .hero-ctas { justify-content: center; margin-top: 20px; }

/* ---------- The rig: the U and everything hanging off it ---------- */
.uni-rig {
  /* Mirrors the SVG viewBox: legs at 13%/87%, bottom bar at 84%. Tiles read
     these so markup, path and connectors can never drift apart. */
  --leg-l: 13%;
  --leg-r: 87%;
  --bar: 84%;
  --conn: clamp(18px, 1.8vw, 28px);
  --gap: 56px;
  grid-area: 1 / 1 / 3 / 2; position: relative; z-index: 0;
}
/* The path and every tile are positioned as a share of THIS box, so capping it
   caps the whole assembly in one place and nothing can drift out of register.
   Uncapped, the legs sit at 13%/87% of the viewport, which on a 2000px monitor
   strands the leg tiles hundreds of pixels from the copy and stretches the U
   into a void. inset:0 + max-* + margin:auto centres an absolutely positioned
   box without touching grid stretch; the caps only engage above these sizes. */
.uni-frame {
  position: absolute; inset: 0; margin: auto;
  max-width: 1700px; max-height: 980px;
}
.uni-u { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
/* Dash figures below are the path's REAL length in viewBox units (2390.09,
   via getTotalLength) rather than a pathLength-normalised value, and
   non-scaling-stroke is deliberately absent. Those two features conflict:
   non-scaling-stroke measures dashes in screen pixels while pathLength
   normalises against user units, so the rendered length shifts with the
   viewport and a fixed dash figure draws fully at one window size and stops
   short at another (it stubbed the right leg on wide monitors). Working in
   user units makes the draw exact at every size. Cost: the hairline now scales
   with the box, which the caps above keep to roughly 1.05-1.5px. */
.uni-u-line {
  fill: none; stroke: rgba(76,189,227,.55); stroke-width: 1.4;
}
/* A short bright dash travelling the whole U: in at the top left, around
   everything, out at the top right. */
.uni-u-pulse {
  fill: none; stroke: #9fe4ff; stroke-width: 2.4; stroke-linecap: round;
  opacity: 0;
  /* 72-unit comet (6% of a half); the 1500 gap exceeds half + comet so only
     ever one dash is on each half. */
  stroke-dasharray: 72 1500; stroke-dashoffset: 80;
  filter: drop-shadow(0 0 5px rgba(120,220,255,.85));
}
/* The point everything resolves to, named. Both pipe halves start at this
   exact coordinate, so the lines emerge from behind the mark rather than
   crossing it, and the halo hides the few pixels that would show through the
   logo's transparent corners. */
/* A junction node, not a floating logo: the ring gives the mark enough weight
   to hold the centre of the composition, and it reads as the fitting the pipe
   terminates in rather than a badge dropped on top of a line. The opaque fill
   is what hides the two path ends underneath. */
.uni-core {
  position: absolute; top: var(--bar); left: 50%;
  width: clamp(62px, 5.4vw, 78px); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: #000;
  border: 1px solid rgba(76,189,227,.55);
  box-shadow: 0 0 26px rgba(76,189,227,.28);
}
.uni-core img { height: 52%; width: auto; }
/* r=48.5 of a 100 viewBox sits the stroke on the ring's own border. Real
   user-unit dash figures (half-circumference = pi * 48.5 = 152.4, so 153) and
   no non-scaling-stroke, for the same reason as the pipe: exact coverage. */
.uni-core-ring { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.uni-core-arc {
  fill: none; stroke: #9fe4ff; stroke-width: 3.4; stroke-linecap: round;
  opacity: 0; stroke-dasharray: 153; stroke-dashoffset: 153;
  filter: drop-shadow(0 0 4px rgba(120,220,255,.9));
}

/* ---------- Use-case tiles ----------
   Six placements, one per side of each of the pipe's three segments.
   --bx / --by hold each placement's resting offset so ONE keyframe set can
   serve all of them; the scatter keyframes add --sx/--sy on top via calc
   rather than redefining the base transform per variant. */
.uni-bank { list-style: none; margin: 0; padding: 0; }
.uni-tile {
  --edge: rgba(76,189,227,.5);
  --edge-loose: rgba(124,201,232,.16);
  position: absolute;
  transform: translate(var(--bx), var(--by)) scale(var(--s, 1));
  padding: 8px 13px 9px;
  border: 1px solid var(--edge);
  /* A faint raised film, not a darker one: on true black a darker fill would
     be invisible, so the tile reads as a panel by sitting just above the page. */
  background: rgba(255,255,255,.045);
  font-family: var(--f-mono); font-size: .688rem; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
  /* Label contrast stays constant. "Disconnected" is carried by the
     rotation and the absent connector, never by dimming the text. */
  color: rgba(255,255,255,.92);
}
/* Legs: outside (--lo/--ro) and inside (--li/--ri) */
.uni-tile--lo, .uni-tile--li { left: var(--leg-l); top: var(--ty); --by: -50%; }
.uni-tile--ro, .uni-tile--ri { left: var(--leg-r); top: var(--ty); --by: -50%; }
.uni-tile--lo, .uni-tile--ri { --bx: calc(-100% - var(--conn)); }
.uni-tile--li, .uni-tile--ro { --bx: var(--conn); }
/* Bottom bar: below (--bb) and above (--ba) */
.uni-tile--bb { left: var(--x); top: calc(var(--bar) + var(--gap)); --bx: -50%; --by: 0px; }
.uni-tile--ba { left: var(--x); top: calc(var(--bar) - var(--gap)); --bx: -50%; --by: -100%; }

/* Connector into the pipe. Its absence during the scattered beat IS the
   legacy tell, so no extra "broken" ornament is needed. Each grows from the
   pipe outward, so transform-origin always sits on the pipe side. */
.uni-tile::after { content: ""; position: absolute; background: rgba(76,189,227,.6); }
.uni-tile--lo::after, .uni-tile--ri::after {
  left: 100%; top: 50%; width: var(--conn); height: 1px; margin-top: -.5px;
  transform-origin: right center;
}
.uni-tile--li::after, .uni-tile--ro::after {
  right: 100%; top: 50%; width: var(--conn); height: 1px; margin-top: -.5px;
  transform-origin: left center;
}
.uni-tile--bb::after {
  bottom: 100%; left: 50%; width: 1px; height: var(--gap); margin-left: -.5px;
  transform-origin: center top;
}
.uni-tile--ba::after {
  top: 100%; left: 50%; width: 1px; height: var(--gap); margin-left: -.5px;
  transform-origin: center bottom;
}

/* ---------- The choreography ----------
   The pipe draws; each tile holds scattered, then snaps as the draw head
   passes it. --i orders tiles along the U, so the wave traces the pipe
   rather than sweeping the screen. Deliberately unhurried: the pipe takes
   4s to draw and the last tile settles around 5.9s. The travelling pulse
   afterwards keeps its own faster tempo. */
@media (prefers-reduced-motion: no-preference) {
  /* 2400 >= the path's 2390.09, so offset 0 covers it exactly once, with no
     dead time at the end of the draw. */
  /* Both halves draw together, outward from the hub. */
  html.js .uni-u-line {
    stroke-dasharray: 1200; stroke-dashoffset: 1200;
    animation: uniDraw 3.2s var(--ease-wipe) 1.3s forwards;
  }
  /* Salesforce lands FIRST, before the pipe exists, and settles rather than
     just fading, so it reads as the thing the rest grows out of. */
  html.js .uni-core { animation: uniCoreIn .9s var(--ease-snap) .7s both; }

  /* One keyframe set covers all three beats: fade in scattered (0-11%),
     hold the mess (11-52%), snap home (52-100%). CSS eases each interval
     independently, so the hold stays still and only the snap decelerates. */
  html.js .uni-tile {
    animation: uniSnap 2.4s var(--ease-snap) both;
    animation-delay: calc(1.7s + var(--i) * .34s);
  }
  html.js .uni-tile::after {
    animation: uniLink .5s var(--ease-rise) both;
    animation-delay: calc(4.1s + var(--i) * .34s);
  }
  html.js .uni-tile--lo::after, html.js .uni-tile--li::after,
  html.js .uni-tile--ro::after, html.js .uni-tile--ri::after { animation-name: uniLinkX; }
  html.js .uni-u-pulse { animation: uniPulse 7s linear 7s infinite; }
  html.js .uni-core-arc { animation: uniCoreArc 7s linear 7s infinite; }
}
@keyframes uniDraw { to { stroke-dashoffset: 0; } }
@keyframes uniSnap {
  0% {
    opacity: 0; border-color: var(--edge-loose);
    transform: translate(calc(var(--bx) + var(--sx)), calc(var(--by) + var(--sy))) rotate(var(--sr)) scale(var(--s, 1));
  }
  11%, 52% {
    opacity: 1; border-color: var(--edge-loose);
    transform: translate(calc(var(--bx) + var(--sx)), calc(var(--by) + var(--sy))) rotate(var(--sr)) scale(var(--s, 1));
  }
  100% {
    opacity: 1; border-color: var(--edge);
    transform: translate(var(--bx), var(--by)) rotate(0deg) scale(var(--s, 1));
  }
}
@keyframes uniLink  { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes uniLinkX { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* Offset runs from just before the path start (comet fully off the front) to
   past its end, both in the same user units as the dasharray. */
/* Offset -1195 is exactly the half's length, i.e. the comet head landing on the
   hub. It parks there, invisible, for the rest of the cycle while the ring
   arcs take over. */
@keyframes uniPulse {
  0%   { opacity: 0; stroke-dashoffset: 80; }
  4%   { opacity: 1; }
  56%  { opacity: 1; }
  60%  { opacity: 0; stroke-dashoffset: -1195; }
  100% { opacity: 0; stroke-dashoffset: -1195; }
}
/* Picks up where the comets land: each arc traces half the ring, and they meet
   on the far side to close the circle. Shares the 7s cycle and delay with
   uniPulse, so the handoff stays locked forever rather than drifting. */
@keyframes uniCoreArc {
  0%, 58% { opacity: 0; stroke-dashoffset: 153; }
  63%     { opacity: 1; }
  86%     { opacity: 1; stroke-dashoffset: 0; }
  95%     { opacity: 0; stroke-dashoffset: 0; }
  100%    { opacity: 0; stroke-dashoffset: 0; }
}
/* Scale settle, not a bare fade: the mark arrives with weight. */
@keyframes uniCoreIn {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.72); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

/* ============================================================
   Live flow: continuous traffic on the pipe, and route tracing on intent.

   html.uni-live is set by JS only once the canvas context is up, so every
   rule here is additive. With JS off, a failed 2D context, or reduced
   motion, none of it applies and the CSS comet loop above stays the
   behaviour — the enhancement never gates what the hero already shows.
   ============================================================ */

/* Explicit order inside the frame: pipe, then packets, then the mark on top
   (so an arrival glow never washes out the Salesforce logo), then tiles. */
.uni-u    { z-index: 0; }
.uni-flow { z-index: 1; }
.uni-core { z-index: 2; }
.uni-bank { z-index: 3; }
.uni-flow {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none;
}
/* The renderer reads its palette from here rather than hard-coding either
   theme, and re-reads when the html class flips. --flow-glow is a bare RGB
   triplet because the canvas composes its own alpha per trail segment. */
.uni-rig {
  --flow-ink: #eaf9ff;
  --flow-glow: 159, 228, 255;
  --flow-trace: #9fe4ff;
}

/* One route per tile: its connector, its junction, then the pipe home to the
   hub. Built by JS from the same constants the U's own `d` uses, so a route
   can never disagree with the line it runs along. Invisible until traced. */
.uni-route {
  fill: none; stroke: var(--flow-trace); stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: 0;
  transition: opacity .18s ease, stroke-dashoffset .52s var(--ease-wipe);
}
.uni-route.is-on { opacity: 1; }

/* The canvas owns the travelling light once live, so the CSS comet and the
   timed ring stand down. The ring is then driven by real arrivals instead of
   a timer, which is the whole point: it closes because things landed. */
html.uni-live .uni-u-pulse,
html.uni-live .uni-core-arc { animation: none !important; }

/* Tracing: the chosen route and its tile hold, everything else recedes. */
html.uni-live .uni-tile {
  transition: opacity .28s var(--ease-rise), border-color .28s ease, background-color .28s ease;
}
html.uni-live .uni-rig.is-tracing .uni-tile { opacity: .3; }
html.uni-live .uni-rig.is-tracing .uni-tile.is-traced {
  opacity: 1;
  border-color: var(--flow-trace);
  background: rgba(255, 255, 255, .1);
}
html.uni-live .uni-u-line { transition: opacity .28s var(--ease-rise); }
html.uni-live .uni-rig.is-tracing .uni-u-line { opacity: .38; }

/* The copy band is a full-width row, so it would swallow hover on the leg
   tiles sitting behind it. Only the copy column itself needs to be
   interactive; the empty gutters beside it pass the pointer through. */
html.uni-live .hero--uni > .inner { pointer-events: none; }
html.uni-live .uni-copy { pointer-events: auto; }
html.uni-live .uni-tile { pointer-events: auto; }
/* One tab stop for the whole bank, then arrow keys step the trace. The
   traced tile is the focus indicator, so no outline on the list itself. */
.uni-bank:focus { outline: none; }

/* Leaving the hero is a hand-off, not a cut: the rig recedes as the fold
   scrolls away. Scroll-driven, so it costs no JS and no scroll listener.
   Unsupported browsers simply keep the rig where it is.

   Scoped to the wide layout on purpose: at 1200px and below the rig is a
   static chip list in normal flow, where translating and fading it as you
   scroll would just look like the content is broken. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 1201px) {
    html.uni-live .uni-rig {
      animation: uniRecede linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 92svh;
    }
  }
}
@keyframes uniRecede {
  to { opacity: .16; transform: translateY(-30px) scale(.975); }
}

/* ---------- Light theme ---------- */
html:not(.theme-dark) .hero--uni { background: var(--wash); }
html:not(.theme-dark) .hero--uni h1 { color: var(--gray-900); }
html:not(.theme-dark) .hero--uni h1 .gold { color: var(--gold-700); }
html:not(.theme-dark) .hero--uni .lead { color: var(--text-muted); }
html:not(.theme-dark) .hero--uni .label--light { color: var(--blue-700); }
html:not(.theme-dark) .hero--uni .btn--ghost-light { color: var(--text-strong); border-color: rgba(0,0,0,.42); }
html:not(.theme-dark) .hero--uni .btn--ghost-light:hover { color: var(--gray-900); border-color: var(--gray-900); }
html:not(.theme-dark) .hero--uni :focus-visible { outline-color: var(--blue-700); }
/* Light theme: the packets can't glow additively against a light wash, so the
   renderer switches to source-over and takes the darker azure from here. */
html:not(.theme-dark) .uni-rig {
  --flow-ink: #075a7a;
  --flow-glow: 4, 151, 202;
  --flow-trace: #0497ca;
}
/* Same selector shape as the dark rule so it wins on specificity, not force. */
html:not(.theme-dark).uni-live .uni-rig.is-tracing .uni-tile.is-traced {
  background: rgba(4, 151, 202, .1);
}
html:not(.theme-dark) .uni-u-line { stroke: rgba(10,111,149,.6); }
html:not(.theme-dark) .uni-u-pulse { stroke: var(--blue-500); filter: drop-shadow(0 0 5px rgba(4,151,202,.6)); }
html:not(.theme-dark) .uni-core {
  background: var(--wash); border-color: rgba(4,151,202,.5);
  box-shadow: 0 0 26px rgba(4,151,202,.18);
}
html:not(.theme-dark) .uni-core-arc {
  stroke: var(--blue-500); filter: drop-shadow(0 0 4px rgba(4,151,202,.6));
}
html:not(.theme-dark) .uni-tile {
  --edge: rgba(4,151,202,.45);
  --edge-loose: rgba(10,111,149,.18);
  background: rgba(255,255,255,.86);
  color: var(--gray-900);
}
html:not(.theme-dark) .uni-tile::after { background: rgba(10,111,149,.65); }

/* ---------- Short viewports ----------
   The assembly is the whole point, so on a laptop that is wide but not tall
   the copy yields rather than crowding the bottom bar. Width-gated so it
   never fights the narrow-screen chip layout below. */
@media (min-width: 1201px) and (max-height: 860px) {
  .hero--uni > .inner { padding-top: 60px; padding-bottom: 60px; }
  .hero--uni .hero-mast { gap: 8px; margin-bottom: 18px; }
  .hero--uni .hero-logo { height: clamp(36px, 3.4vw, 52px); }
  .hero--uni h1 { margin-top: 12px; font-size: clamp(2rem, 3.6vw, 3.2rem); }
  .hero--uni .hero-foot { margin-top: 15px; }
  .hero--uni .hero-ctas { margin-top: 15px; }
  .uni-rig { --gap: 26px; }
}

/* ---------- Narrow screens ----------
   Same list, reflowed. The tiles stop being positioned and become a centred
   chip field; the U, hub and connectors drop out. No duplicate markup, so
   there is no aria-hidden trap and nothing to keep in sync.

   The threshold is 1200px rather than a phone width because the inner channel
   is proportional (a share of the viewport) while the tiles are a fixed text
   size: below about 1200px the inside tiles no longer clear the copy. The U is
   a wide-canvas composition, so it hands over to chips rather than cramming. */
@media (max-width: 1200px) {
  .hero--uni {
    display: flex; flex-direction: column; justify-content: center;
    gap: clamp(22px, 4vh, 44px);
  }
  .hero--uni > .inner {
    padding-top: calc(76px + clamp(14px, 2.4vh, 34px)); padding-bottom: 0;
  }
  .uni-copy { max-width: 100%; }
  .hero--uni h1 { font-size: clamp(2.4rem, 5.4vw, 4.2rem); }
  .hero--uni .lead { max-width: 66ch; }
  .uni-rig { position: static; z-index: auto; }
  /* The frame must rejoin the flow too, or the chip list stays absolutely
     positioned and overlays the copy. */
  .uni-frame {
    position: static; margin: 0;
    max-width: none; max-height: none;
  }
  .uni-u, .uni-core { display: none; }
  .uni-bank {
    display: flex; flex-wrap: wrap; justify-content: center;
    gap: 9px; padding: 0 var(--gutter);
  }
  .uni-tile {
    position: static; left: auto; top: auto;
    transform: none; --s: 1;
  }
  .uni-tile::after { display: none; }
  /* The positioned snap has no meaning once the tiles are in flow, so the
     wave becomes a staggered fade of the same elements. */
  @media (prefers-reduced-motion: no-preference) {
    html.js .uni-tile {
      animation: uniChip .6s var(--ease-rise) both;
      animation-delay: calc(1s + var(--i) * .09s);
    }
  }
  @keyframes uniChip {
    from { opacity: 0; transform: translateY(9px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}
@media (max-width: 560px) {
  .uni-tile { font-size: .625rem; padding: 7px 10px 8px; letter-spacing: .08em; }
}

/* Masked line intro (Cinema load choreography) */
/* The mask needs overflow:hidden so the line can slide up from below, but the
   h1 sets line-height:1, giving a 1em line box while Barlow Condensed's own
   metrics need 1.146em (86 ascender + 24 descender per 96). The glyphs centre
   in the shorter box, so descenders on y, g and the comma were being shaved
   flat at the mask edge: 2.9px at 96px, 1.6px at the hero's 63px.

   0.08em of bottom padding clears the font's full declared descent (worst case
   0.073em), and the matching negative margin keeps the line spacing and the
   layout exactly as they were. em-based, so it holds at every clamp step.

   The reveal offset still hides the line: it translates 112% (1.12em) while the
   clip edge is now at 1.08em. */
.intro-mask {
  display: block; overflow: hidden;
  padding-bottom: .08em; margin-bottom: -.08em;
}
html.js .intro-line {
  display: block; transform: translateY(112%);
  animation: lineRise .85s var(--ease-rise) both;
  animation-delay: var(--d, 0s);
}
html.js .intro-fade { opacity: 0; animation: introFade .8s ease both; animation-delay: var(--d, 0s); }
@keyframes lineRise { to { transform: translateY(0); } }
@keyframes introFade { to { opacity: 1; } }

/* ---------- Who we are ----------
   Asymmetric split: the statement and its spec sheet carry the weight, the
   photograph anchors it. Deliberately not a card grid and deliberately no
   eyebrow; the dl is the substance, in the same mono-label + hairline grammar
   the rest of the site uses. */
.who-grid {
  display: grid; grid-template-columns: 1.02fr .98fr;
  gap: clamp(32px, 5vw, 68px); align-items: center;
}
.who-grid > * { min-width: 0; }
/* 20ch broke the longer heading into four lines; 26ch lets it set in three
   without exceeding the copy column. */
.who .h2 { max-width: 26ch; }
.who .lead { margin-top: 20px; max-width: 60ch; }

.who-spec { margin: 30px 0 0; padding: 0; border-top: 1px solid var(--rule); }
.who-spec > div {
  display: grid; grid-template-columns: minmax(0, 13em) minmax(0, 1fr);
  gap: clamp(12px, 2vw, 28px);
  padding: 13px 0; border-bottom: 1px solid var(--rule);
}
.who-spec dt {
  font-family: var(--f-mono); font-size: .72rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--blue-700); padding-top: .18em;
}
.who-spec dd { margin: 0; color: var(--text-body); line-height: 1.55; }

.who-more {
  display: inline-flex; align-items: center; gap: .5em; margin-top: 26px;
  font-weight: 600; color: var(--blue-700); border-bottom: 1px solid transparent;
  transition: color .18s ease, border-color .18s ease;
}
.who-more:hover { color: var(--text-strong); border-bottom-color: var(--gold-600); }
.who-more .arr { transition: transform .18s ease; }
.who-more:hover .arr { transform: translateX(3px); }

/* The source is already 4:3, so the frame matches it and object-fit has
   nothing to crop; cover is kept only as a guard if the asset is ever
   replaced at a different ratio. */
.who-shot { margin: 0; }
.who-shot img {
  width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover;
  border: 1px solid var(--rule);
}

@media (max-width: 900px) {
  .who-grid { grid-template-columns: 1fr; gap: clamp(26px, 5vw, 40px); }
  /* Statement first, photograph after: the words are the point. */
  .who-shot { order: 2; }
  .who-shot img { aspect-ratio: 16 / 10; object-position: 50% 34%; }
  .who-spec > div { grid-template-columns: 1fr; gap: 5px; }
}

/* ---------- Trust band (navy frame, rotating banner) ----------
   Part of the Cinema navy frame — stays navy in both themes, like the
   header and footer, rather than following the light/dark content tokens.
   Continuous marquee: two identical .t-set groups, track slides -50%.
   Pauses on hover; reduced motion falls back to a static wrapped list. */
.trust { background: var(--frame-900); border-top: 1px solid rgba(255,255,255,.1); }
.trust .inner { display: flex; align-items: center; gap: 28px; padding-top: 32px; padding-bottom: 32px; }
.trust .t-label { font-family: var(--f-mono); font-size: .78rem; letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.58); white-space: nowrap; }
.t-marquee {
  flex: 1; min-width: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.t-track { display: flex; width: max-content; }
.t-set { display: flex; align-items: center; gap: 24px; padding-right: 24px; flex: none; }

/* Client logo chips: real seals/wordmarks on a uniform white plate (both
   themes, see dark.css) so wildly different source assets (full-color seals
   vs. flat ink-only marks) read as one consistent strip. */
.t-logo {
  display: inline-flex; align-items: center; justify-content: center;
  height: 152px; padding: 20px 38px; border-radius: 12px;
  background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.1);
  flex: none; transition: background .25s ease;
}
.t-logo img { height: 100%; max-height: 112px; width: auto; display: block; }
.t-logo--transparent { background: #fff !important; }
/* Wordmark-shaped marks (very wide aspect ratio vs. the circular seals) would
   otherwise blow out to 8-10x the width of a seal at the same height; cap by
   width instead so they carry the same visual weight as the rest of the strip. */
.t-logo--wide img { height: 62px; width: auto; max-width: 260px; }

@media (prefers-reduced-motion: no-preference) {
  .t-track { animation: trustScroll 68s linear infinite; }
  .t-marquee:hover .t-track { animation-play-state: paused; }
}
@keyframes trustScroll { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .t-marquee { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .t-track { width: 100%; }
  .t-set { flex-wrap: wrap; width: 100%; gap: 14px; padding-right: 0; }
  .t-set[aria-hidden] { display: none; }
}
@media (max-width: 640px) {
  .trust .inner { flex-direction: column; align-items: flex-start; gap: 14px; }
  .t-marquee { width: 100%; }
  .t-logo { height: 108px; padding: 14px 26px; }
  .t-logo img { max-height: 80px; }
  .t-logo--wide img { height: 44px; max-width: 180px; }
}

/* ---------- Ledger rows (numbered hairline rows) ---------- */
.ledger { margin-top: 56px; border-top: 1px solid var(--rule); }
.ledger-row {
  display: grid; grid-template-columns: 72px minmax(200px, .8fr) 1.15fr 44px;
  gap: clamp(16px, 3vw, 36px); align-items: baseline;
  padding: 32px 0; border-bottom: 1px solid var(--rule);
  color: inherit; text-decoration: none;
  transition: background .18s ease;
}
a.ledger-row:hover { background: var(--gray-50); }
a.ledger-row:hover .go { opacity: 1; transform: translateX(0); }
a.ledger-row:hover h3 { color: var(--blue-700); }
.ledger-row .idx { font-family: var(--f-mono); font-size: .85rem; color: var(--text-subtle); padding-left: 4px; }
.ledger-row h3 { font-size: clamp(1.35rem, 2vw, 1.75rem); letter-spacing: -0.008em; font-weight: 800; transition: color .18s ease; }
.ledger-row p { color: var(--text-muted); font-size: .98rem; line-height: 1.6; max-width: 560px; }
.ledger-row .go {
  opacity: 0; transform: translateX(-6px); transition: opacity .18s ease, transform .18s ease;
  color: var(--blue-700); align-self: center; justify-self: end; padding-right: 4px;
}
@media (max-width: 800px) {
  .ledger-row { grid-template-columns: 44px 1fr; }
  .ledger-row p { grid-column: 2; }
  .ledger-row .go { display: none; }
}

/* ---------- Solutions staircase (desloppification-style) ----------
   Six columns ascending to the right; hatched ghost cards trail
   below each column and fade into the section floor. */
.sol-stairs { background: var(--wash); padding-bottom: 0; overflow: hidden; }

/* ---------- Solutions index: ten lines, one format ----------
   Every service line gets the same panel and the same display-scale heading.
   An earlier pass split them into a lead six and a quieter four; ranking them
   visually made the bottom of the list read as an afterthought, so the
   bench-depth point stays in the lead sentence where it belongs.
   Accepted live at default columns and heading scale, so no knob values needed
   baking in. */
.sol-panels {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: clamp(2px, .4vw, 6px);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
}
.sol-panel {
  padding: clamp(20px, 2.2vw, 32px) clamp(18px, 2vw, 28px);
  border: 1px solid var(--rule-faint);
  background: color-mix(in srgb, var(--text-strong) 3%, transparent);
}
.sol-panel h3 { font-size: clamp(1.5rem, 2.4vw, 2.15rem); line-height: 1.06; margin: 0 0 10px; }
.sol-panel p { color: var(--text-muted); margin: 0; max-width: 42ch; }

.stair-links { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 26px; }
.stair-more {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--blue-700); font-weight: 600; font-size: .95rem;
  border-bottom: 1px solid var(--gold-500); padding-bottom: 3px;
}
.stair-more:hover { color: var(--text-strong); border-bottom-color: var(--gold-600); }
.stair-more svg { transition: transform .18s ease; }
.stair-more:hover svg { transform: translateX(3px); }
.stair-chip {
  font-family: var(--f-mono); font-size: .75rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-subtle); border: 1px solid var(--gray-300); border-radius: 6px; padding: 6px 12px;
}

.stairs {
  position: relative; display: grid; grid-template-columns: repeat(6, 1fr);
  gap: clamp(10px, 1.2vw, 16px);
  margin-top: 64px; height: clamp(460px, 40vw, 600px); overflow: hidden;
  --step: clamp(36px, 4.1vw, 58px);
  padding-top: 8px; /* headroom so the top card's hover lift isn't clipped */
  /* The staircase stands on a ground line instead of dissolving away */
  border-bottom: 3px solid var(--blue-500);
}
.stair-col { padding-top: calc(var(--lift, 0) * var(--step)); min-width: 0; display: flex; flex-direction: column; }

.stair-card {
  display: block; background: var(--white); border: 1px solid #aed7ea;
  border-radius: 12px; padding: clamp(16px, 1.7vw, 24px);
  color: inherit; text-decoration: none;
  box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 16px 36px rgba(4,90,120,.14);
  transition: transform .25s var(--ease-rise), border-color .2s ease, box-shadow .2s ease;
}
.stair-card:hover {
  transform: translateY(-5px); border-color: var(--blue-500); color: inherit;
  box-shadow: 0 2px 4px rgba(0,0,0,.05), 0 18px 40px rgba(0,0,0,.12);
}
.stair-ic {
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: 10px;
  background: var(--blue-50); color: var(--blue-700);
  margin-bottom: clamp(16px, 2vw, 30px);
}
.stair-ic svg { width: 22px; height: 22px; }
.stair-card h3 {
  font-family: var(--f-display); font-weight: 700; font-size: clamp(.92rem, 1.15vw, 1.12rem);
  letter-spacing: -0.005em; line-height: 1.2; color: var(--text-strong);
}
.stair-card p { margin-top: 10px; font-size: clamp(.78rem, .95vw, .875rem); line-height: 1.55; color: var(--text-muted); }

/* One continuous hatched panel filling the column below each card */
/* Solid stair treads: filled azure columns the cards stand on,
   running flush to the ground line */
.stair-ghost {
  flex: 1; min-height: 90px; border-radius: 10px 10px 0 0; margin-top: clamp(10px, 1.2vw, 16px);
  border: 1px solid rgba(4,151,202,.28); border-bottom: 0;
  border-top: 2px solid var(--blue-500);
  background:
    repeating-linear-gradient(135deg, transparent 0 9px, rgba(4,151,202,.14) 9px 10px),
    linear-gradient(180deg, rgba(4,151,202,.2), rgba(4,151,202,.05));
}

/* Columns rise farther than standard reveals, staggered per column;
   each icon draws itself in once its column lands */
@media (prefers-reduced-motion: no-preference) {
  html.rise .stair-col[data-rise] { transform: translateY(70px); }
  html.rise .stair-col[data-rise].in { transform: none; }
  .stair-col:nth-child(1) { --icd: .4s; }
  .stair-col:nth-child(2) { transition-delay: .07s; --icd: .47s; }
  .stair-col:nth-child(3) { transition-delay: .14s; --icd: .54s; }
  .stair-col:nth-child(4) { transition-delay: .21s; --icd: .61s; }
  .stair-col:nth-child(5) { transition-delay: .28s; --icd: .68s; }
  .stair-col:nth-child(6) { transition-delay: .35s; --icd: .75s; }

  html.rise .stair-col[data-rise] .stair-ic :is(path, circle, rect) {
    stroke-dasharray: 1; stroke-dashoffset: 1;
  }
  html.rise .stair-col[data-rise].in .stair-ic :is(path, circle, rect) {
    animation: icDraw .8s var(--ease-rise) forwards;
    animation-delay: var(--icd, .4s);
  }
  .stair-ic { transform-origin: 50% 50%; }
  .stair-card:hover .stair-ic { animation: icPop .45s var(--ease-rise); }
}
@keyframes icDraw { to { stroke-dashoffset: 0; } }
@keyframes icPop {
  0% { transform: scale(1); }
  40% { transform: scale(1.16) rotate(-5deg); }
  100% { transform: scale(1); }
}

@media (max-width: 1060px) {
  /* All six cards stay; ghosts and the clip go, staircase softens */
  .stairs { grid-template-columns: repeat(3, 1fr); height: auto; overflow: visible; --step: 26px; row-gap: 26px; border-bottom: 0; padding-bottom: 0; }
  .stair-ghost { display: none; }
  .sol-stairs { padding-bottom: var(--section); }
  .stair-col:nth-child(1), .stair-col:nth-child(4) { --lift: 2; }
  .stair-col:nth-child(2), .stair-col:nth-child(5) { --lift: 1; }
  .stair-col:nth-child(3), .stair-col:nth-child(6) { --lift: 0; }
}
@media (max-width: 640px) {
  .stairs { grid-template-columns: 1fr 1fr; --step: 16px; margin-top: 48px; }
  .stair-col:nth-child(odd) { --lift: 1 !important; }
  .stair-col:nth-child(even) { --lift: 0 !important; }
}
/* ============================================================
   Demo screen — high-fidelity product surface.
   .lx = Lightning console (SLDS-flavored: #0176d3 brand,
         #f3f2f2 canvas, chevron Path, highlights panel)
   ============================================================ */
.lx {
  border: 1px solid #d8dde3; border-radius: 10px; overflow: hidden;
  background: #f3f2f2; color: #181818; text-align: left;
  box-shadow: var(--shadow-frame);
  font-family: var(--f-body);
  /* One knob controls the whole screen's scale — everything inside is em */
  font-size: clamp(13px, 1.15vw, 16.5px);
}


/* ---- Lightning console ---- */
.lx-global {
  display: flex; align-items: center; gap: .65em;
  background: #fff; border-bottom: 1px solid #e5e5e5; padding: .55em .9em;
}
.lx-waffle { display: grid; grid-template-columns: repeat(3, .25em); gap: .17em; flex: none; }
.lx-waffle i { width: .25em; height: .25em; border-radius: 1px; background: #747474; }
.lx-app { font-weight: 700; font-size: .92em; color: #032d60; white-space: nowrap; }
.lx-search {
  flex: 1; display: flex; align-items: center; gap: .5em; min-width: 0;
  background: #f3f2f2; border: 1px solid #dddbda; border-radius: 1.1em;
  padding: .32em .85em; font-size: .8em; color: #747474; white-space: nowrap; overflow: hidden;
}
.lx-gicons { display: flex; align-items: center; gap: .65em; color: #747474; flex: none; }
.lx-avatar { width: 2em; height: 2em; border-radius: 50%; background: #7f8de1; color: #fff; font-size: .7em; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; }

.lx-tabs { display: flex; align-items: flex-end; gap: 2px; background: #032d60; padding: .45em .7em 0; }
.lx-tab {
  display: inline-flex; align-items: center; gap: .5em;
  font-size: .8em; color: rgba(255,255,255,.85); padding: .4em .85em .45em;
  border-radius: .5em .5em 0 0; white-space: nowrap;
}
.lx-tab.on { background: #f3f2f2; color: #181818; font-weight: 600; }
.lx-tab .x { color: #747474; margin-left: .15em; }
.lx-tab-i { width: .6em; height: .6em; border-radius: 2px; background: #ff5d2d; flex: none; }
.lx-tab .ct { background: rgba(255,255,255,.22); border-radius: .7em; padding: 0 .45em; font-size: .9em; }

.lx-body { padding: .75em; }
.lx-highlights {
  display: flex; align-items: center; gap: .65em;
  background: #fff; border: 1px solid #e5e5e5; border-radius: .55em .55em 0 0; padding: .75em .9em;
}
.lx-icon { width: 2.1em; height: 2.1em; border-radius: .4em; background: #ff5d2d; display: inline-flex; align-items: center; justify-content: center; flex: none; }
.lx-icon svg { width: 1.15em; height: 1.15em; }
.lx-title { min-width: 0; }
.lx-title .lx-entity { display: block; font-size: .75em; color: #747474; }
.lx-title b { display: block; font-size: 1.05em; font-weight: 700; color: #181818; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lx-hactions { margin-left: auto; display: flex; gap: 0; flex: none; }
.lx-btn {
  font-size: .8em; font-weight: 600; color: #0b5cab; background: #fff;
  border: 1px solid #c9c9c9; padding: .32em .75em;
}
.lx-btn:first-child { border-radius: .3em 0 0 .3em; }
.lx-btn + .lx-btn { border-left: 0; }
.lx-btn:last-child { border-radius: 0 .3em .3em 0; }
.lx-btn--caret { padding: .32em .55em; }
.lx-hfields {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: .7em;
  background: #fff; border: 1px solid #e5e5e5; border-top: 0; border-radius: 0 0 .55em .55em; padding: .6em .9em .75em;
}
.lx-hfields div { min-width: 0; }
.lx-hfields span { display: block; font-size: .72em; color: #747474; }
.lx-hfields b { display: block; font-size: .85em; font-weight: 600; color: #181818; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Lightning Path — chevrons */
.lx-pathrow { display: flex; align-items: center; gap: .6em; background: #fff; border: 1px solid #e5e5e5; border-radius: .55em; padding: .6em .75em; margin-top: .6em; }
.lx-path { display: flex; flex: 1; min-width: 0; }
.lx-path button {
  flex: 1; min-width: 0; border: 0; cursor: pointer; margin: 0 -6px 0 0; padding: 0;
  background: #ecebea; color: #514f4d;
  font-family: var(--f-body); font-size: .78em; font-weight: 600;
  height: 2.1em; line-height: 2.1em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%, 10px 50%);
  padding-left: 1.1em; padding-right: .8em;
}
.lx-path button:first-child { clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%); border-radius: 1.05em 0 0 1.05em; padding-left: .9em; }
.lx-path button:last-child { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 10px 50%); border-radius: 0 1.05em 1.05em 0; margin-right: 0; }
.lx-path button.done { background: #014486; color: #fff; }
.lx-path button.now { background: #0176d3; color: #fff; }
.lx-path button:hover { background: #0b5cab; color: #fff; }
.lx-mark {
  flex: none; font-family: var(--f-body); font-size: .78em; font-weight: 600; cursor: pointer;
  background: #0176d3; color: #fff; border: 1px solid #0176d3; border-radius: .3em; padding: .42em .8em;
  white-space: nowrap; transition: background .15s ease;
}
.lx-mark:hover { background: #014486; }
.lx-mark[disabled] { background: #ecebea; color: #747474; border-color: #ecebea; cursor: default; }

.lx-cols { display: grid; grid-template-columns: 1.55fr 1fr; gap: .6em; margin-top: .6em; }
.lx-main { background: #fff; border: 1px solid #e5e5e5; border-radius: .55em; min-width: 0; }
.lx-tabbar { display: flex; gap: 2px; border-bottom: 1px solid #e5e5e5; padding: 0 .75em; }
.lx-tabbar span { font-size: .82em; font-weight: 600; color: #444; padding: .65em .7em .55em; }
.lx-tabbar .on { color: #0b5cab; box-shadow: inset 0 -2px 0 #0176d3; }
.lx-feed { padding: .45em .9em .75em; }
.lx-feed .f { display: flex; gap: .65em; padding: .5em 0; border-bottom: 1px solid #f1f0ef; font-size: .85em; line-height: 1.45; color: #444; }
.lx-feed .f:last-child { border-bottom: 0; }
.lx-feed .f .t { font-family: var(--f-mono); font-size: .78em; color: #747474; flex: none; width: 3.4em; padding-top: .15em; }
.lx-side { min-width: 0; }
.lx-panel { background: #fff; border: 1px solid #e5e5e5; border-radius: .55em; padding: .7em .85em; }
.lx-panel + .lx-panel { margin-top: .6em; }
.lx-panel .lxh { display: flex; align-items: center; gap: .4em; margin: 0 0 .35em; font-family: var(--f-body); font-size: .82em; font-weight: 700; color: #181818; }
.lx-panel p { font-size: .8em; line-height: 1.5; color: #5c6b74; }
.lx-panel .ct { background: #ecebea; border-radius: .7em; padding: 0 .45em; font-size: .85em; color: #514f4d; }
.lx-panel--ai { background: linear-gradient(180deg, #f6f2fb, #fff 70%); border-color: #e3d8f2; }
.lx-panel--ai .lxh { color: #5a1ba9; }

@media (max-width: 1240px) {
  .lx-search { display: none; }
}
@media (max-width: 1000px) {
  .lx-side { display: none; }
  .lx-cols { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
  .lx-hfields { grid-template-columns: 1fr 1fr; }
  .lx-pathrow { flex-wrap: wrap; }
}

/* ---------- Feature grids ---------- */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(24px, 3vw, 40px); margin-top: 60px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(24px, 3vw, 40px); margin-top: 60px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 3vw, 40px); margin-top: 60px; }
@media (max-width: 980px) { .grid-4 { grid-template-columns: 1fr 1fr; } .grid-3 { grid-template-columns: 1fr; } }
@media (max-width: 620px) { .grid-4 { grid-template-columns: 1fr; } .grid-2 { grid-template-columns: 1fr; } }

/* 2px azure top-rule cells — the Cinema "why" treatment, in brand blue */
.fcell { border-top: 2px solid var(--blue-500); padding-top: 20px; }
.fcell .h3 { font-size: 1.15rem; }
.fcell p { margin-top: 10px; font-size: .92rem; color: var(--text-muted); }
.fcell .fk { font-family: var(--f-mono); font-size: .75rem; letter-spacing: .1em; color: var(--text-subtle); display: block; margin-bottom: 14px; }
.on-dark .fcell { border-top-color: var(--gold-500); }
.on-dark .fcell p { color: var(--on-frame-muted); }
.on-dark .fcell .fk { color: rgba(255,255,255,.6); }

/* Category grid: 5 items, no lopsided wrap at any width */
.grid-cat { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: clamp(24px, 3vw, 40px); margin-top: 60px; }

/* ============================================================
   Technology — receding data plane. Six systems sit at ONE equal radius
   around the record and orbit slowly, so each passes nearer to and further
   from the viewer over time. Depth is recomputed from orbit position every
   frame and drives scale, brightness, ground-pool size and paint order —
   the cues a flat chart can't fake. No pointer interaction: the scene moves
   on its own and ignores the cursor.
   ============================================================ */
.schematic-section .hero-glow { display: none; }

/* This section sits on page black, not on the raised --frame-800 band that
   .on-dark gives every other dark surface. The band was the only thing telling
   you a new section had started, so an azure horizon bled up behind the head
   does that job instead — same light source as the plane's own horizon, one
   tier further out. */
.schematic-section {
  background: var(--frame-900);
  position: relative;
}
.schematic-section::before {
  content: "";
  position: absolute; inset: 0 0 auto; height: 58%;
  background: radial-gradient(128% 100% at 50% -6%,
    rgba(76,189,227,.096) 0%,
    rgba(76,189,227,.03) 34%,
    rgba(76,189,227,0) 72%);
  pointer-events: none;
}
.schematic-section > .wrap { position: relative; }

.dplane {
  position: relative; margin-top: 60px; aspect-ratio: 1000 / 542;
  max-width: 1000px; margin-left: auto; margin-right: auto;
  border-radius: 18px; overflow: hidden;
  /* Pulled down toward true black now that the section behind it is black —
     against page black the old #15151a top stop read as a lit grey card. */
  background: linear-gradient(180deg, #08080b 0%, #030305 42%, #000000 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04), 0 30px 70px rgba(0,0,0,.6);
}
.dp-space { position: absolute; inset: 0; width: 100%; height: 100%; }

/* Floor: a polar plane — concentric ground rings and spokes centred on the
   record, in the same projection the orbit uses (ry = rx * 0.52), so the
   geometry itself says everything resolves there. It replaced a Cartesian grid
   with eight tracked wires laid over it. Brightest underfoot, dissolving into
   the haze near the horizon. */
.dp-ring, .dp-spoke {
  fill: none; stroke: rgba(76,189,227,.3); stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.dp-spoke { stroke: rgba(76,189,227,.16); }
.dp-floor {
  -webkit-mask-image: linear-gradient(180deg, transparent 27%, #000 44%, #000 100%);
  mask-image: linear-gradient(180deg, transparent 27%, #000 44%, #000 100%);
}
/* The #dpHaze stops in the markup must track the .dplane gradient's own base
   colour. Tuned against a lighter panel they sit *above* the surface and ghost
   a visible rectangle over the floor instead of veiling it. */
.dp-haze, .dp-glow, .dp-rec-halo { pointer-events: none; }

/* Grounding pools — what makes an object feel placed in space rather than
   pasted on top of it */
.dp-pool { fill: #7fd4f0; opacity: var(--po, .3); filter: blur(5px); }
.dp-pool--rec { fill: #9fe4ff; filter: blur(7px); }

.dp-tether { fill: none; stroke: rgba(159,228,255,.42); stroke-width: 1; vector-effect: non-scaling-stroke; }
.dp-tether--rec { stroke: rgba(159,228,255,.75); }

/* Convergence field: 72 hairlines falling inward to the record through a
   radial reach mask. One rate drives both the brightness and the fall time so
   the two can't drift apart.

   Composed state is the default: full lines at low opacity, so a headless
   renderer or a reduced-motion visitor gets a finished convergence rather than
   72 stub ticks. The dash only exists while the reveal has run.

   Deliberately NOT non-scaling-stroke. That measures the dash in screen px
   while the path is in user units, so the inflow would drift as the panel
   scales below 1000px — the same trap noted at the top of this file. The 456
   period clears the longest spoke (430) so every line reaches the record
   instead of some stopping short of it. */
.dp-field { --flow-rate: .55; }
.dp-flow {
  fill: none; stroke: #9fe4ff; stroke-width: 1.1; stroke-linecap: round;
  opacity: .2;
}

.dp-mark {
  position: absolute; left: var(--x); top: var(--y);
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  /* No transition: the orbit rewrites this every frame and a transition
     would damp and smear it. */
  transform: translate(-50%, -50%) scale(var(--s, 1));
  /* Depth drives paint order, so a system orbiting to the front correctly
     occludes one further back — recomputed each frame as --dp changes */
  z-index: calc(var(--dp) * 100);
  /* Explicit width: auto-width SVG logos need a sized containing block or
     they collapse to nothing inside a shrink-to-fit absolute box. */
  width: 140px; text-align: center;
}
.dp-logo { height: 24px; width: auto; filter: drop-shadow(0 4px 10px rgba(0,0,0,.95)); }
.dp-logo--rec { height: 54px; }
.dp-glyph { display: flex; color: var(--ic); filter: drop-shadow(0 3px 8px rgba(0,0,0,.9)); }
.dp-glyph svg { width: 24px; height: 24px; }
.dp-label { font-family: var(--f-display); font-weight: 700; font-size: 12px; color: #eaf6fb; white-space: nowrap; }
.dp-cap { font-family: var(--f-mono); font-weight: 500; font-size: 10.5px; letter-spacing: .13em; text-transform: uppercase; color: #4cbde3; white-space: nowrap; }
/* Caption above the record: below it collides with the field convergence */
.dp-mark--rec { flex-direction: column-reverse; gap: 8px; }

@media (prefers-reduced-motion: no-preference) {
  /* Gated on html.rise (set by the reveal script itself) so the scene stays
     visible if main.js never runs */
  html.rise .dplane[data-rise] .dp-mark { opacity: 0; }
  html.rise .dplane[data-rise].in .dp-mark { animation: dpArrive .55s var(--ease-rise) both; animation-delay: var(--d, 0s); }
  @keyframes dpArrive { from { opacity: 0; } to { opacity: 1; } }

  html.rise .dplane[data-rise] .dp-pool,
  html.rise .dplane[data-rise] .dp-tether { opacity: 0; }
  html.rise .dplane[data-rise].in .dp-pool { animation: dpPool .6s ease both .3s; }
  html.rise .dplane[data-rise].in .dp-tether { animation: introFade .6s ease both .5s; }
  @keyframes dpPool { from { opacity: 0; } to { opacity: var(--po, .2); } }

  html.rise .dplane[data-rise].in .dp-flow {
    stroke-dasharray: 26 430;
    animation: dpFlow calc(1.4s + (1 - var(--flow-rate)) * 6s) linear infinite;
    animation-delay: calc(var(--i) * -.07s);
  }
  @keyframes dpFlow {
    0%   { stroke-dashoffset: 456; opacity: 0; }
    12%  { opacity: calc(.18 + .5 * var(--flow-rate)); }
    72%  { opacity: calc(.18 + .5 * var(--flow-rate)); }
    100% { stroke-dashoffset: 0; opacity: 0; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .dp-mark, .dp-tether { opacity: 1; }
  .dp-pool { opacity: var(--po, .2); }
}

@media (max-width: 760px) { .dplane { display: none; } }

.schem-mobile-list { display: none; }

@media (max-width: 760px) {
  .schem-mobile-list {
    display: block; list-style: none; margin: 48px 0 0; padding: 0;
    position: relative; max-width: 320px; margin-left: auto; margin-right: auto;
  }
  .schem-mobile-list::before {
    content: ""; position: absolute; top: 78px; bottom: 30px; left: 50%;
    width: 0; border-left: 1px dashed rgba(76,189,227,.5);
  }
  /* Plateless, matching the desktop board: marks sit on the navy page with a
     dark halo separating them from the connector spine behind. Brand-hue
     glyph strokes are legible here (5.8–10.7:1) exactly as they are on the board. */
  .schem-mobile-list li {
    position: relative; z-index: 1; margin: 0 auto 18px; width: 190px;
    display: flex; align-items: center; gap: 12px;
    color: #f2f6f8;
    font-family: var(--f-display); font-weight: 700; font-size: .95rem;
    padding: 8px 14px;
  }
  /* Two entries carry real marks rather than a .schem-icon glyph, because no
     generic glyph exists for them; sized to match the glyphs beside them. */
  .schem-mark { width: 20px; height: 20px; object-fit: contain; flex: none; }
  .schem-mobile-list li::before {
    content: ""; position: absolute; inset: -6% -4%; z-index: -1; border-radius: 40px;
    background: radial-gradient(closest-side, rgba(0,0,0,.92), rgba(0,0,0,.5) 60%, transparent 100%);
  }
  /* Self-contained: the plane replaced the desktop board that used to define
     these glyphs, so the mobile list now owns their size and colour outright. */
  .schem-mobile-list .schem-icon {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; color: var(--ic, var(--blue-500));
  }
  .schem-mobile-list .schem-icon svg { width: 22px; height: 22px; }
  .schem-hub-m {
    width: 210px !important; font-weight: 800 !important;
    justify-content: center; gap: 12px !important;
  }
  .schem-hub-m img { height: 30px; width: auto; }
  .schem-hub-m span { font-family: var(--f-mono); font-weight: 500; font-size: .68rem; letter-spacing: .1em; color: #4cbde3; }
}

/* ============================================================
   Photography. The site was carrying its entire visual weight in
   typography and CSS scenery; these are the places a real photograph
   does the job better. Full-bleed bands double as the rhythm break —
   they interrupt the uniform --section cadence.
   ============================================================ */
.photoband {
  position: relative; margin: 0; overflow: hidden;
  /* escapes .wrap to run edge to edge */
  width: 100vw; margin-left: calc(50% - 50vw);
  height: clamp(280px, 34vw, 460px);
}
.photoband img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Navy scrim so overlaid type keeps its contrast against any photo */
.photoband::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, rgba(0,0,0,.94) 0%, rgba(0,0,0,.86) 38%, rgba(0,0,0,.72) 56%, rgba(0,0,0,.28) 100%);
}
.photoband-copy {
  position: absolute; inset: 0; z-index: 1; display: flex; align-items: center;
}
.photoband-copy .inner { max-width: 620px; }
.photoband-copy h2 {
  font-family: var(--f-display); font-weight: 700; color: #fff;
  font-size: clamp(1.9rem, 3.4vw, 3rem); letter-spacing: -0.01em; line-height: 1.06;
  text-wrap: balance;
}
.photoband-copy p { margin-top: 14px; color: rgba(255,255,255,.86); font-size: 1.02rem; line-height: 1.65; max-width: 52ch; }
.photo-credit {
  position: absolute; right: 12px; bottom: 8px; z-index: 2;
  font-family: var(--f-mono); font-size: .62rem; letter-spacing: .06em;
  color: rgba(255,255,255,.42);
}

/* Inline figure: a photograph sitting beside prose rather than full-bleed */
.photo-figure { margin: 0; }
.photo-figure img {
  width: 100%; height: auto; display: block; border-radius: 12px;
  border: 1px solid var(--rule);
}
.photo-figure figcaption {
  margin-top: 12px; font-family: var(--f-mono); font-size: .72rem;
  letter-spacing: .05em; color: var(--text-subtle); line-height: 1.6;
}
@media (max-width: 900px) { .photoband { height: clamp(240px, 52vw, 340px); } }

/* ---------- Big CTA ---------- */
.cta-final h2 { font-size: clamp(2.9rem, 6vw, 5.6rem); max-width: 980px; letter-spacing: -0.012em; }
.cta-final .row { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 44px; }

/* ---------- Footer (black frame) ---------- */
.site-foot {
  background: var(--frame-900); color: var(--on-frame-muted); font-size: .9rem;
  /* Dark theme puts a black footer on a black page; the hairline is the only
     thing separating them, as with .trust above. */
  border-top: 1px solid rgba(255,255,255,.1);
}
.site-foot .cols {
  display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 40px;
  padding-top: 72px; padding-bottom: 56px;
}
.site-foot .brand img { height: 28px; margin-bottom: 18px; }
.site-foot .brand p { max-width: 300px; line-height: 1.68; font-size: .88rem; }
.site-foot h3 {
  color: #fff; font-family: var(--f-mono); font-weight: 500; font-size: .75rem;
  letter-spacing: .18em; text-transform: uppercase; margin-bottom: 18px;
}
.site-foot .col a { display: block; color: var(--on-frame-muted); padding: 5px 0; font-size: .9rem; }
.site-foot .col a:hover { color: #fff; }
.site-foot .contact-line { display: flex; gap: 10px; margin-top: 16px; font-size: .85rem; align-items: baseline; }
.site-foot .contact-line a { color: rgba(255,255,255,.82); }
.site-foot .contact-line a:hover { color: #fff; }
.site-foot .legal { border-top: 1px solid rgba(255,255,255,.12); }
.site-foot .legal .inner {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px;
  padding-top: 20px; padding-bottom: 22px;
  font-family: var(--f-mono); font-size: .75rem; letter-spacing: .05em; color: rgba(255,255,255,.58);
}
@media (max-width: 900px) { .site-foot .cols { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .site-foot .cols { grid-template-columns: 1fr; } }

/* ---------- Page hero (subpages, navy) ---------- */
.page-hero { position: relative; overflow: hidden; background: var(--frame-900); }
.page-hero .hero-glow { opacity: .8; }

/* ---------- Solutions hero: lit console, no corner glow ----------
   The azure radial that hung off the top-right corner was atmosphere over
   empty space. This variant drops it and moves the same light onto the permit
   console, so the glow points at the product instead of the margin.
   Chosen live at glow 0.7; the multiplier is baked in rather than left as a
   knob, and the "raise console" and density knobs resolved to no change. */
.page-hero--lit .morph {
  position: relative;
  box-shadow:
    0 0 0 1px rgba(76, 189, 227, .238),
    0 18px 60px rgba(4, 151, 202, .21);
}
/* z-index -1 keeps the bloom behind the console but above the hero's own
   background, which is why .page-hero--lit needs no stacking context of its own. */
.page-hero--lit .morph::before {
  content: ""; position: absolute; inset: -34% -22%;
  pointer-events: none; z-index: -1; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(4, 151, 202, .21), transparent 70%);
}
.page-hero .inner { position: relative; padding-top: clamp(64px, 9vh, 104px); padding-bottom: clamp(56px, 8vh, 96px); }
.page-hero h1 {
  font-size: clamp(2.7rem, 5vw, 4.1rem); font-weight: 800;
  letter-spacing: -0.011em; line-height: 1.04; color: #fff;
  margin-top: 24px; max-width: 900px; text-wrap: balance;
}
.page-hero .lead { color: var(--on-frame-body); line-height: 1.68; margin-top: 22px; max-width: 680px; }

/* ---------- Cards / misc ---------- */
.card {
  background: #fff; border: 1px solid var(--gray-200); border-radius: var(--radius-lg);
  padding: clamp(24px, 3vw, 36px); box-shadow: var(--shadow-card);
}
.tick-list { list-style: none; margin: 0; padding: 0; }
.tick-list li {
  padding: 11px 0 11px 30px; border-top: 1px solid var(--rule);
  position: relative; font-size: .95rem; font-weight: 600; color: var(--text-strong);
}
.tick-list li::before {
  content: ""; position: absolute; left: 2px; top: 18px;
  width: 12px; height: 7px; border-left: 2px solid var(--blue-600); border-bottom: 2px solid var(--blue-600);
  transform: rotate(-45deg);
}

/* ---------- Compare bars (implementation) ---------- */
.compare { margin-top: 56px; display: grid; grid-template-columns: minmax(0, 1fr); gap: 28px; }
.compare .lane { min-width: 0; }
.compare .lane .lane-h { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; margin-bottom: 12px; }
.compare .lane .lane-h .n { font-family: var(--f-display); font-weight: 800; font-size: 1.05rem; color: var(--text-strong); letter-spacing: -0.01em; }
.compare .lane .lane-h .d { font-family: var(--f-mono); font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-subtle); }
.compare .bars { display: flex; gap: 4px; }
.compare .seg {
  height: 44px; border-radius: 6px; display: flex; align-items: center; padding: 0 14px;
  font-family: var(--f-mono); font-size: .72rem; letter-spacing: .05em; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.compare .lane--trad .seg { background: var(--gray-200); color: var(--text-muted); }
.compare .lane--vivid .seg { background: var(--blue-500); color: #fff; }
.compare .lane--vivid .seg:first-child { background: var(--frame-800); }
.compare .lane--vivid .seg:last-child { background: var(--blue-700); }

/* ---------- Team ---------- */
.team-grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 3vw, 40px); margin-top: 60px; }
.team-card { border: 1px solid var(--gray-200); border-radius: var(--radius-lg); overflow: hidden; background: #fff; box-shadow: var(--shadow-card); }
.team-card img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; object-position: top; background: var(--gray-100); }
.team-card .tb { padding: clamp(22px, 2.6vw, 32px); }
.team-card h3 { font-size: 1.4rem; letter-spacing: -0.006em; font-weight: 800; }
.team-card .role { font-family: var(--f-mono); font-size: .75rem; letter-spacing: .12em; text-transform: uppercase; color: var(--blue-700); margin-top: 8px; }
.team-card .bio { margin-top: 16px; font-size: .92rem; line-height: 1.7; color: var(--text-muted); }
@media (max-width: 800px) { .team-grid { grid-template-columns: 1fr; } }

/* ---------- Pull quote ---------- */
.pull-quote { max-width: 900px; }
.pull-quote blockquote {
  margin: 0; font-family: var(--f-display); font-weight: 700; font-size: clamp(1.4rem, 2.6vw, 2.1rem);
  letter-spacing: -0.008em; line-height: 1.34; color: var(--text-strong);
}
.pull-quote blockquote::before { content: "“"; color: var(--blue-500); }
.pull-quote blockquote::after { content: "”"; color: var(--blue-500); }
.pull-quote cite { display: block; margin-top: 22px; font-style: normal; font-family: var(--f-mono); font-size: .76rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-subtle); }
.on-dark .pull-quote blockquote { color: #fff; }
.on-dark .pull-quote blockquote::before, .on-dark .pull-quote blockquote::after { color: var(--gold-500); }
.on-dark .pull-quote cite { color: rgba(255,255,255,.5); }

/* ---------- Contact ---------- */
.contact-grid { display: grid; grid-template-columns: 1fr .85fr; gap: clamp(32px, 5vw, 72px); align-items: start; margin-top: 8px; }
@media (max-width: 900px) { .contact-grid { grid-template-columns: 1fr; } }
.field { margin-bottom: 20px; }
.field label {
  display: block; font-family: var(--f-mono); font-size: .75rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-muted); margin-bottom: 8px;
}
.field input, .field textarea, .field select {
  width: 100%; font-family: var(--f-body); font-size: .98rem; color: var(--text-strong);
  background: #fff; border: 1px solid var(--gray-300); border-radius: 8px; padding: 13px 14px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--blue-500); box-shadow: 0 0 0 3px var(--blue-50);
}
.field textarea { resize: vertical; min-height: 130px; }
.field ::placeholder { color: var(--text-subtle); opacity: 1; }

/* Inline validation states */
.field--error input, .field--error textarea, .field--error select {
  border-color: #b3261e;
}
.field--error input:focus, .field--error textarea:focus, .field--error select:focus {
  border-color: #b3261e; box-shadow: 0 0 0 3px rgba(179,38,30,.12);
}
.field-err { font-size: .85rem; color: #9a231c; margin-top: 7px; }
html.theme-dark .field-err { color: #ff9d97; }
html.theme-dark .field--error input, html.theme-dark .field--error textarea, html.theme-dark .field--error select { border-color: #e2483d; }

/* Message preserved after submit — survives a mail app that never opens */
.form-keep { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--rule); }
.form-keep .form-copy {
  width: 100%; margin-top: 10px; min-height: 140px; resize: vertical;
  font-family: var(--f-mono); font-size: .8rem; line-height: 1.55; color: var(--text-body);
  background: var(--gray-50); border: 1px solid var(--gray-300); border-radius: 8px; padding: 12px 13px;
}
.form-keep .row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.contact-aside .card + .card { margin-top: 20px; }
.contact-aside h3 { font-size: 1.05rem; letter-spacing: -0.01em; font-weight: 800; }
.contact-aside p, .contact-aside a { font-size: .92rem; }
.form-note { font-size: .85rem; color: var(--text-subtle); margin-top: 14px; }

/* ---------- Logo walls ---------- */
.logo-wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; margin-top: 56px; }
.logo-cell {
  background: #fff; border: 1px solid var(--gray-200); border-radius: var(--radius);
  min-height: 86px; display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 14px 16px; font-family: var(--f-display); font-weight: 700; font-size: .92rem;
  letter-spacing: -0.01em; color: var(--text-body);
}
.logo-cell .sub { display: block; font-family: var(--f-mono); font-weight: 400; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text-subtle); margin-top: 4px; }

/* ---------- Partner cards ---------- */
/* minmax(0, 1fr) rather than a bare 1fr so a long unbroken string can't push a
   column past its share. 24px gutter: at 16px the space between two cards was
   half the space inside them, which read as a seam rather than a gap. */
.pcards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-top: 56px; }
.pcard { border: 1px solid var(--gray-200); border-radius: var(--radius-lg); background: #fff; padding: clamp(22px, 2.6vw, 32px); }
/* h3, not h2: these sit under the section's own h2, and eight sibling h2s made
   nine same-level headings out of one group. */
.pcard h3 { font-size: 1.3rem; letter-spacing: -0.006em; font-weight: 800; }
/* One label per card, not two. A .tag chip sat beside the title repeating what
   .role already said on half of them — GIS/Geospatial, Content/Documents,
   Payments/Payments, ePlan/Plan review — so the chip and the .ph flex wrapper
   that existed to hold it are both gone. */
.pcard .role { font-family: var(--f-mono); font-size: .75rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-subtle); margin-top: 8px; }
.pcard p { margin-top: 16px; font-size: .93rem; color: var(--text-muted); max-width: 62ch; text-wrap: pretty; }
/* Same size as the paragraph above it. The hairline and the step up to
   --text-body carry the distinction; a 5% size drop only fought them, and the
   rule was dotted where every other divider in the system is solid. */
.pcard .detail { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rule); font-size: .93rem; color: var(--text-body); }
@media (max-width: 800px) { .pcards { grid-template-columns: 1fr; } }
/* 12px uppercase mono is a fine chip on a desktop and too small on a phone.
   560px is where the rest of the system steps its labels up. */
@media (max-width: 560px) { .pcard .role { font-size: .82rem; } }

/* ============================================================
   Solutions "desloppification" — legacy system morphs into the
   modern portal, cycling in a browser frame (impeccable-style)
   ============================================================ */
.morph {
  border: 1px solid rgba(255,255,255,.14); border-radius: 12px; overflow: hidden;
  background: #121215; box-shadow: 0 28px 64px rgba(0,0,0,.6);
  font-size: clamp(11px, .95vw, 14px);
}
.morph-bar {
  display: flex; align-items: center; gap: .9em;
  background: rgba(255,255,255,.06); border-bottom: 1px solid rgba(255,255,255,.1);
  padding: .7em 1em;
}
.morph-bar .dots { display: flex; gap: .45em; }
.morph-bar .dots i { width: .7em; height: .7em; border-radius: 50%; background: rgba(255,255,255,.22); }
.morph-bar .addr { flex: 1; text-align: center; font-family: var(--f-mono); font-size: .78em; color: rgba(255,255,255,.55); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.morph-tag {
  font-family: var(--f-mono); font-size: .78em; letter-spacing: .1em; text-transform: uppercase;
  padding: .35em .9em; border-radius: 999px; white-space: nowrap; transition: all .4s ease;
}
.morph-tag--old { background: rgba(216,58,52,.18); color: #ff9d97; border: 1px solid rgba(216,58,52,.45); }

/* Height in em, not an aspect ratio. Both mockups scale with .morph's clamped
   font-size, so an em height tracks their content at every width; a fixed ratio
   left roughly 550px of dead grey below both states at desktop widths. 30em
   covers the taller of the two (the console, ~27em) with slack for wrapping. */
.morph-stage { position: relative; height: 30em; }
.m-before, .m-after { position: absolute; inset: 0; overflow: hidden; }
.m-after {
  opacity: 0; clip-path: inset(0 0 100% 0);
  transition: clip-path .7s var(--ease-wipe), opacity .45s ease;
}
.m-before { transition: opacity .5s ease .15s, filter .5s ease .15s; }
.morph.show-after .m-after { opacity: 1; clip-path: inset(0); }
.morph.show-after .m-before { opacity: 0; filter: saturate(.4); transition-delay: .3s; }

/* The legacy terminal */
.oldsys { height: 100%; background: #d4d0c8; color: #1a1a1a; font-family: 'Tahoma', 'Segoe UI', system-ui, sans-serif; display: flex; flex-direction: column; }
.os-title {
  display: flex; justify-content: space-between; gap: 1em;
  background: linear-gradient(90deg, #0a246a, #a6caf0); color: #fff;
  font-size: .82em; font-weight: 700; padding: .45em .8em; letter-spacing: .02em;
}
.os-title .os-x { opacity: .9; letter-spacing: .3em; }
.os-menu { font-size: .78em; padding: .4em .9em; border-bottom: 1px solid #a9a49a; color: #333; letter-spacing: .25em; background: #ece9e2; }
.os-body { padding: 1em 1.1em; position: relative; flex: 1; }
.os-row { display: grid; grid-template-columns: 1.1fr .8fr 1.4fr; gap: .8em; font-family: var(--f-mono); font-size: .8em; padding: .5em .6em; background: #fff; border: 1px solid #a9a49a; border-top: 0; }
.os-row.os-head { background: #bfbab0; font-weight: 700; border-top: 1px solid #a9a49a; }
.os-red { color: #b3261e; font-weight: 700; }
.os-note { position: relative; margin-top: 1.1em; font-family: var(--f-mono); font-size: .78em; line-height: 1.5; color: #333; padding: .6em .7em; background: #f6f3ea; border: 1px dashed #a9a49a; }
.os-alert { margin-top: .8em; font-family: var(--f-mono); font-size: .74em; color: #7a5b00; background: #fff3bf; border: 1px solid #d8bd4d; padding: .5em .7em; }
.os-ann { position: absolute; inset: -12% -4% -14% -4%; width: 108%; height: 126%; pointer-events: none; overflow: visible; }
.os-ann path { fill: none; stroke: #e2483d; stroke-width: 2.5; stroke-linecap: round; vector-effect: non-scaling-stroke; }
@media (prefers-reduced-motion: no-preference) {
  /* Same non-scaling-stroke vs pathLength conflict as the hero pipe, and this
     one is stretched too (preserveAspectRatio=none), so exact coverage can
     fall short. The small overshoot guarantees the strike-through completes;
     offset starts at the same figure so it still begins undrawn. */
  html.js .os-ann path { stroke-dasharray: 1.4; stroke-dashoffset: 1.4; }
  html.js .os-ann path.go { animation: drawStroke .7s ease 1.3s forwards; }
}
/* Owned here rather than borrowed from the old blueprint hero, which no
   longer exists. Without it the annotation stays at dashoffset 1 and the
   red strike-through never appears. */
@keyframes drawStroke { to { stroke-dashoffset: 0; } }

/* The after state reuses the Lightning console (.lx) at frame scale, at
   slightly smaller type because it carries a header, tab bar, highlights
   panel, path and two columns in the same box. */
.morph .lx { border: 0; border-radius: 0; box-shadow: none; height: 100%; font-size: 1em; }
.morph .lx { font-size: .92em; }

/* ---------- Curtain load (home) ----------
   Black wipe with the wordmark. Hidden without JS (html.js gate)
   and under reduced motion. */
.curtain { display: none; }
html.js .curtain {
  display: flex; align-items: center; justify-content: center;
  position: fixed; inset: 0; z-index: 999; background: var(--frame-900);
  animation: curtainUp .75s var(--ease-wipe) 1.15s both;
}
html.js .curtain img { height: 40px; width: auto; opacity: 0; animation: curtainLogoIn .95s var(--ease-rise) .12s both; }
@keyframes curtainUp { to { transform: translateY(-101%); } }
@keyframes curtainLogoIn {
  0% { opacity: 0; transform: translateY(18px); }
  45% { opacity: 1; transform: none; }
  100% { opacity: 1; }
}


/* ---------- Reveal motion ---------- */
@media (prefers-reduced-motion: no-preference) {
  /* Gated on .rise (added by the reveal script itself, not the pre-paint
     .js class) so content stays visible if main.js ever fails to load. */
  html.rise [data-rise] {
    opacity: 0; transform: translateY(26px);
    transition: opacity .75s var(--ease-rise), transform .75s var(--ease-rise);
    transition-delay: var(--rd, 0s);
  }
  html.rise [data-rise].in { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.js .curtain { display: none; }
  html.js .intro-line, html.js .intro-fade { animation: none; transform: none; opacity: 1; }
}
