/* ==========================================================================
   home-v3.css — homepage design system, ported from the approved standalone
   reference (sp3-homepage-v3_4.html).

   This file is loaded ONLY by layouts/home.njk. The rest of the site still
   loads main.css, so these tokens (Archivo/Inter/JetBrains Mono, cream
   surface, #8C191C red) cannot leak into pages that have not been migrated
   yet. When the other templates move over, fold these tokens into base.css.

   Changed from the reference:
     - .brand img replaces .brand svg: the site's existing logo asset is used
       rather than the reference's inline SVG, with width/height reserved so
       the header cannot shift as it loads.
   ========================================================================== */

/* Tokens, the .wrap/.btn primitives, and the shared site header + footer all
   come from chrome.css, which every other page loads too — so the homepage and
   the inner pages cannot drift apart. */
@import url("chrome.css?v=20260902");

/* The warm tonal palette now lives in chrome.css so the shared header and
   footer match on every page. What stays homepage-only is below: the page
   background ramp and the SVG palette remap. */

/* Inline SVG drawings predate the token system and carry the old palette as
   presentation attributes (var() is invalid there), so the remap lives here:
   one central attribute→token map instead of hex scattered per component.
   primary data / small marks → --red · secondary → --clay · tertiary →
   --dusty · axes + faint linework → --line · large red fills → --wine. */
[stroke="#8C191C"] {
  stroke: var(--red);
}

[fill="#8C191C"] {
  fill: var(--red);
}

rect[fill="#8C191C"] {
  fill: var(--wine);
} /* bars & fills step down */
path[fill="#8C191C"] {
  fill: var(--clay);
} /* stacked chart bands */
[stroke="#A8393B"] {
  stroke: var(--clay);
}

[fill="#A8393B"] {
  fill: var(--clay);
}

[stroke="#B96866"] {
  stroke: var(--clay);
}

[fill="#B96866"] {
  fill: var(--clay);
}

[stroke="#D8B3B2"] {
  stroke: var(--dusty);
}

[fill="#D8B3B2"] {
  fill: var(--dusty);
}

[fill="#E7E4DF"] {
  fill: var(--rose);
}

[stroke="#B9B2A8"] {
  stroke: var(--dusty);
}

[stroke="#D5CFC6"] {
  stroke: var(--line);
}

/* the products scenes were drawn for the old dark stage; on the light stage
   their dark panel fills become light surfaces */
[fill="#221D1B"] {
  fill: var(--blush);
}

[stroke="#4A443F"] {
  stroke: var(--line);
}

[fill="#4A443F"] {
  fill: var(--dusty);
}

[fill="#CFC8BF"] {
  fill: var(--clay);
}

[fill="#5A4442"] {
  fill: var(--dusty);
}

[fill="#C9C13A"] {
  fill: var(--dusty);
} /* OUT-03 dominant band joins the ramp */

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

html {
  scroll-behavior: smooth;

  /* Clip sideways overflow on the ROOT, like base.css does for inner pages.
     An overflow-x set on <body> propagates to the viewport in a way iOS
     Safari does not honour for touch scrolling, so any element poking past
     the viewport (a marquee slide mid-drift, a bleeding negative margin)
     let phones scroll into a blank strip beside the page. `clip`, not
     `hidden`, so no scroll container is created. */
  overflow-x: clip;
}

body {
  font-family: var(--font-body);
  background: var(--cream);
  color: var(--ink);
  font-size: 17px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizelegibility;
}

::selection {
  background: var(--red);
  color: var(--white);
}

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

a {
  color: inherit;
}

button {
  font-family: inherit;
  cursor: pointer;
}

:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 3px;
  border-radius: 2px;
}

/* The landing page runs to --content-width (wider than the rest of the site,
   matching the header's own measure) so the product deck has room to draw.
   width:100% matters because .hero is a flex container — without it the wrap
   is a flex item that sizes to its content and never reaches its max. */
.wrap {
  width: 100%;
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 0 36px;
}

.mono {
  font-family: var(--font-mono);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--white);
  padding: 10px 18px;
  z-index: 999;
  border-radius: 0 0 6px;
}

.skip:focus {
  left: 0;
}

/* One continuous ramp painted on <main> — sections are transparent over it,
   so there are no seams to mismatch. It never plateaus: white-warm paper at
   the top deepens continuously through blush into rose at the bottom, where
   the final CTA picks it up and carries it into brand red → wine, and the
   newsletter into bedrock. The hero photo sits opaque at the very top and
   dissolves into it.

   The ramp lives on <main> and not on the sections because sections carry the
   .sec-fade settle, which rotates and scales them by a fraction of a degree on
   entry — enough to expose a sliver of whatever sits behind a full-bleed
   section background. main never animates.

   Stops are calibrated to where the eight sections actually fall (measured
   with the spotlights carousel restored): the hero ends near 13%, trust runs
   to 18%, the workflow to 31%, the platform slideshow to 50%, the product
   strip to 61%, the spotlights to 77%, and the final CTA opens at 77%.
   Re-check them when a section is added or removed. */
main {
  background: linear-gradient(
    180deg,
    var(--paper) 0%,
    var(--paper) 15%,
    var(--ramp-1) 33%,
    var(--blush) 50%,
    var(--ramp-2) 64%,
    var(--rose) 77%,
    var(--rose) 100%
  );
}

/* The paper hold (0–15%) outlasts the hero's solid base on any viewport, and
   the --rose hold (77–100%) guarantees the final CTA's opaque gradient — which
   opens on --rose — always meets a matching ground, wherever its top edge
   falls. Both seams are exact by construction: if .final's first stop changes,
   change the ramp's last stop with it. */

h1,
h2,
h3 {
  font-family: var(--font-head);
  letter-spacing: -0.02em;
}

.eyebrow {
  font-family: var(--font-mono);

  /* Large enough to read as a heading in its own right, not a whisper above
     one. Tracking comes down as the size goes up — wide letter-spacing is a
     small-type device, and at this size 0.17em started to fall apart into
     separate letters. */
  font-size: 19px;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--red);
  display: block;
  margin-bottom: 22px;
}

.sec-title {
  font-size: clamp(30px, 3.6vw, 46px);
  font-weight: 600;
  line-height: 1.14;
  max-width: 24ch;
  letter-spacing: -0.018em;
}

.sec-lede {
  margin-top: 18px;
  color: var(--muted);
  font-size: 20px;
  line-height: 1.7;
  max-width: 60ch;
}

section {
  padding: 120px 0;
  position: relative;
}

/* Buttons live in chrome.css (shared). The duplicate block that used to sit
   here re-declared .btn after chrome was imported, which silently beat
   chrome's .btn--small on this page only — the header CTA rendered larger here
   than everywhere else. */

/* ============ nav ============ */

/* The header lives in chrome.css now (shared with every other page), so the
   reference's standalone nav rules were removed from here. */

/* ============ hero ============ */
.hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: 92px 0 54px;
  overflow: hidden;
  position: relative;

  /* Skyline photo under two paper washes: a horizontal one that keeps the
     left column (headline, lede) on a near-solid ground for contrast, and a
     vertical one that dissolves the photo into the trust strip's paper→blush
     gradient so the descent stays seamless. */
  background:
    linear-gradient(
      90deg,
      rgb(250 246 242 / 96%) 0%,
      rgb(250 246 242 / 84%) 36%,
      rgb(250 246 242 / 44%) 66%,
      rgb(250 246 242 / 20%) 100%
    ),
    linear-gradient(
      180deg,
      rgb(250 246 242 / 55%) 0%,
      rgb(250 246 242 / 12%) 42%,
      rgb(250 246 242 / 90%) 90%,
      #faf6f2 100%
    ),
    url("/assets/images/2026/08/sf-transamerica.jpg") center top/cover no-repeat,
    var(--paper);
}

/* The deck sits beside the headline. It is the tighter of the two jobs — four
   cards behind one open panel in half a row — which is why the readouts now
   put their chart on its own full-width line (see .deck .metric): the figures
   grow even though the card itself is narrower than a full-width deck. */
.hero .wrap {
  max-width: 1400px;
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.76fr) minmax(0, 1.24fr);
  gap: 44px;
  align-items: center;
}

/* The differentiator claim, pinned above everything else in the hero. */
.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 13.5px;
  letter-spacing: 0.04em;
  line-height: 1.4;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 10px 20px;
  margin-bottom: 26px;
  box-shadow: 0 1px 2px rgb(90 20 20 / 6%);
  text-decoration: none;
  transition:
    border-color 0.25s var(--ease),
    box-shadow 0.25s var(--ease);
}

.hero-badge b {
  color: var(--red);
  font-weight: 600;
}

.hero-badge__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--red);
  flex: none;
}

.hero-badge .arr {
  color: var(--red);
  transition: transform 0.25s var(--ease);
}

.hero-badge:hover {
  border-color: var(--red-soft);
  box-shadow: 0 2px 8px rgb(140 25 28 / 12%);
}

.hero-badge:hover .arr {
  transform: translateX(3px);
}

.hero h1 {
  font-size: clamp(42px, 5.4vw, 72px);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.028em;
}

.hero h1 .l2 {
  color: var(--red);
  display: block;
}

.hero .lede {
  margin-top: 26px;
  font-size: 18px;
  color: var(--muted);
  max-width: 52ch;
  line-height: 1.7;
}

.hero-actions {
  margin-top: 38px;
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

.hero-meta {
  margin-top: 32px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--muted);
}

.hero-meta b {
  color: var(--red);
  font-weight: 500;
}

/* hero visualization */
.hero-viz {
  position: relative;
}

.hero-viz svg {
  display: block;
  width: 100%;
  height: auto;
}

.deck__panel {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--white);
  padding: 22px 22px 16px;
  box-shadow: 0 30px 70px -40px rgb(90 20 20 / 25%);
}

.viz-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 14px;
}

.viz-head b {
  color: var(--ink);
  font-weight: 500;
}

.chip-row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 18px;
}

.chip {
  font-family: var(--font-mono);
  font-size: 15px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text);
  border: 1px solid var(--dusty);
  border-radius: 100px;
  padding: 9px 18px;
  transition: all 0.35s var(--ease);
}

.chip.on {
  color: var(--white);
  background: var(--red);
  border-color: var(--red);
}

/* Compact inline cue ("SCROLL ˅"), pinned inside the hero's bottom padding.
   It must stay short: the deck's bottom edge reaches to within ~55px of the
   hero's bottom on laptop heights, and the old stacked label-over-line cue
   (~60px tall at bottom:26) climbed up into the deck's stage-tab chips. */
.scroll-cue {
  position: absolute;
  bottom: 18px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}

.scroll-cue .chev {
  width: 7px;
  height: 7px;
  margin-top: -3px;
  border-right: 1.5px solid var(--red);
  border-bottom: 1.5px solid var(--red);
  transform: rotate(45deg);
  animation: cue-bob 2.4s var(--ease) infinite;
}

@keyframes cue-bob {
  0%,
  100% {
    transform: translateY(-1px) rotate(45deg);
    opacity: 0.45;
  }

  50% {
    transform: translateY(3px) rotate(45deg);
    opacity: 1;
  }
}

/* SVG shared drawing styles */
.st-line {
  stroke: var(--ink);
  stroke-width: 1.4;
  fill: none;
}

.st-thin {
  stroke: var(--line);
  stroke-width: 1;
  fill: none;
}

.st-red {
  stroke: var(--red);
  stroke-width: 1.6;
  fill: none;
}

.st-dim {
  opacity: 0.2;
  transition: opacity 0.5s var(--ease);
}

.st-dim.on {
  opacity: 1;
}

.mono-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  fill: var(--muted);
  text-transform: uppercase;
}

.mono-label.red {
  fill: var(--red);
}

/* ============ trust strip ============ */

/* First step of the descent: a long vertical blend carries paper into blush,
   so the seam under the hero has no hard edge. */
.trust {
  padding: 76px 0 64px;
}

/* "Backed by research. Trusted by practice." is a statement, not a caption:
   full display type with the research half in brand red. */
.trust p {
  text-align: center;
  font-family: var(--font-head);
  font-size: clamp(26px, 3.2vw, 42px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--espresso);
  margin-bottom: 44px;
}

.trust p b {
  color: var(--red);
  font-weight: 700;
}

.firms {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 36px;
  font-family: var(--font-head);
  font-size: 16.5px;
  font-weight: 500;
  color: var(--muted);
}

.firms span {
  opacity: 0.72;
  transition:
    opacity 0.2s,
    color 0.2s;
}

.firms span:hover {
  opacity: 1;
  color: var(--ink);
}

/* ============ scales section ============ */

/* Each tile section carries soft dusty/clay washes drifting behind its
   cards — the atmosphere the glass tiles frost. Kept off the text columns
   and under ~.2 alpha so every AA pair keeps its margin. */
.scales {
  background:
    radial-gradient(
      48% 32% at 80% 42%,
      rgb(201 154 147 / 20%),
      transparent 100%
    ),
    radial-gradient(40% 26% at 60% 66%, rgb(168 91 85 / 9%), transparent 100%);
}

.scales-grid {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: 70px;
  align-items: center;
}

.scale-list {
  margin-top: 44px;
  display: flex;
  flex-direction: column;
}

/* Each scale is a button: clicking it jumps the diagram to that layer and stops
   the autoplay. Button chrome is reset so it still reads as editorial text. */
.scale-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  padding: 24px 4px;
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition:
    opacity 0.45s var(--ease),
    background 0.25s var(--ease);
}

.scale-item:last-child {
  border-bottom: 1px solid var(--line);
}

.scale-item:hover {
  background: rgb(140 25 28 / 3%);
}

.scale-item__h {
  display: flex;
  align-items: baseline;
  gap: 14px;
  font-family: var(--font-head);
  font-size: 21px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.scale-item__h .n {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--red);
  font-weight: 400;
}

.scale-item__p {
  display: block;
  margin-top: 8px;
  color: var(--muted);
  font-size: 15.5px;
  line-height: 1.65;
  max-width: 48ch;
}

/* The selected scale carries a red rule so the active state survives even if
   the opacity difference is subtle on a bright screen. */
.scale-item[aria-selected="true"] {
  border-top-color: var(--red);
}

.scale-item.dim {
  opacity: 0.42;
}

.scales-viz {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  padding: 18px;
  box-shadow: 0 30px 70px -45px rgb(90 20 20 / 22%);
}

.scales-viz svg {
  display: block;
  width: 100%;
  height: auto;
}

.sc-layer {
  transition:
    opacity 0.6s var(--ease),
    transform 0.6s var(--ease);
  transform-origin: 310px 230px;
}

/* ============ products ============ */

/* No longer the dark island: the section carries the descent from blush into
   rose, with the technical drawings redrawn in clay/dusty on a card stage. */
.products {
  background: radial-gradient(
    46% 30% at 76% 52%,
    rgb(201 154 147 / 18%),
    transparent 100%
  );
  color: var(--espresso);
}

.products .sec-lede {
  color: var(--muted);
}

.prod-shell {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 56px;
  margin-top: 60px;
  align-items: start;
}

.prod-tabs {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.prod-tab {
  text-align: left;
  background: none;
  border: none;
  border-left: 2px solid var(--line);
  padding: 18px 22px;
  color: var(--muted);
  transition: all 0.3s var(--ease);
}

.prod-tab .t {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 17px;
  display: block;
  color: inherit;
}

.prod-tab .d {
  font-size: 13.5px;
  margin-top: 5px;
  display: block;
  line-height: 1.55;
  max-width: 34ch;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: all 0.4s var(--ease);
}

.prod-tab[aria-selected="true"] {
  border-left-color: var(--red);
  color: var(--espresso);
}

.prod-tab[aria-selected="true"] .d {
  max-height: 80px;
  opacity: 1;
}

.prod-tab:hover {
  color: var(--espresso);
}

.prod-stage {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  padding: 26px;
  min-height: 440px;
  position: relative;
  overflow: hidden;
  box-shadow: 0 20px 50px -24px rgb(90 20 20 / 12%);
}

.prod-stage .stage-head {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}

.scene {
  position: absolute;
  inset: 64px 26px 26px;
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 0.5s var(--ease),
    transform 0.5s var(--ease);
  pointer-events: none;
}

.scene.active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.scene svg {
  width: 100%;
  height: 100%;
}

.sc-ink {
  stroke: var(--clay);
  stroke-width: 1.3;
  fill: none;
}

.sc-faint {
  stroke: var(--dusty);
  stroke-width: 1;
  fill: none;
}

.sc-red {
  stroke: var(--red);
  stroke-width: 1.6;
  fill: none;
}

.sc-fillred {
  fill: var(--red);
}

.sc-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  fill: var(--muted);
  text-transform: uppercase;
}

.sc-label.red {
  fill: var(--clay);
}

/* ============ outputs ============ */

/* Sits directly under the logo carousel now, so it opens on blush. */
.outputs {
  background:
    radial-gradient(
      46% 30% at 18% 38%,
      rgb(201 154 147 / 18%),
      transparent 100%
    ),
    radial-gradient(40% 26% at 84% 64%, rgb(168 91 85 / 9%), transparent 100%);
}

.out-flow {
  margin-top: 64px;
  position: relative;
}

/* Five cards as a 3 + 2 arrangement: three column-span-2 cards on the first
   row, two span-3 cards on the second, so nothing is squeezed five-across. */
.out-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 24px;
  position: relative;
  z-index: 1;
}

.out-card {
  grid-column: span 2;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 26px 24px 22px;
  box-shadow: 0 20px 50px -24px rgb(90 20 20 / 10%);
}

.out-card:nth-child(4),
.out-card:nth-child(5) {
  grid-column: span 3;
}

.out-card__head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.out-card__idx {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(140 25 28 / 8%);
  color: var(--red);
  font-size: 12.5px;
  font-weight: 600;
}

.out-card h3 {
  font-size: 19px;
  font-weight: 600;
  line-height: 1.25;
  margin: 0;
}

/* the plain-English answer to "what does this output tell me?" */
.out-card__what {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 18px;
}

/* the chart sits in its own inset panel so it reads as an exhibit */
.out-card__chart {
  margin-top: auto;
  background: rgb(244 234 228 / 55%);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px 12px 10px;
}

.out-card__chart svg {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  margin: 0 auto;
}

/* Headline figure from the documented test-model run — the number a
   decision-maker would quote. */
.out-card__foot {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.out-card__stat {
  flex: none;
  font-family: var(--font-mono);
  font-size: 23px;
  font-weight: 600;
  color: var(--wine);
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.out-card__statlabel {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

.out-note {
  margin-top: 26px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.7;
  color: var(--muted);
  letter-spacing: 0.08em;
  text-align: center;
}

.out-spine {
  position: absolute;
  left: 0;
  right: 0;
  top: -26px;
  height: 1px;
}

.out-spine svg {
  width: 100%;
  height: 30px;
  display: block;
}

/* ============ workflow ============ */

/* Now the first section under the trust strip, which is plain paper — without
   a wash of its own the page opened with a tall flat stretch and the descent
   read as stalled. Placed right of centre because .outputs directly below
   carries its wash at 18% from the left, so the two alternate instead of
   pooling in the same column. Same alpha band as every other tile section. */
.workflow {
  background: radial-gradient(
    46% 30% at 72% 44%,
    rgb(201 154 147 / 16%),
    transparent 100%
  );
}

.wf-grid {
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 30px;
  position: relative;
  z-index: 1;
}

.wf-step h3 {
  font-size: 22px;
  font-weight: 600;
  line-height: 1.25;
  margin-top: 10px;
}

.wf-step__n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--red);
  color: var(--white);
  font-size: 12.5px;
  letter-spacing: 0.04em;
}

.wf-step p {
  margin-top: 8px;
  color: var(--muted);
  font-size: 17px;
  line-height: 1.6;
}

.wf-line {
  margin: 54px 0 10px;
}

.wf-line svg {
  display: block;
  width: 100%;
  height: 120px;
}

/* stylelint-disable-next-line selector-id-pattern -- id is referenced from home-v3.js */
#wfPath {
  stroke: var(--red);
  stroke-width: 2;
  fill: none;
}

/* ============ use cases ============ */

/* Washes mirrored left/right from what they were: this section now follows
   .products, which carries its main wash at 76% from the left, and two
   right-hand washes in a row pooled into one band across the section seam.
   The clay accent moves to the right to stay opposite the main one. */
.usecases {
  background:
    radial-gradient(
      46% 30% at 24% 40%,
      rgb(201 154 147 / 20%),
      transparent 100%
    ),
    radial-gradient(38% 26% at 74% 66%, rgb(168 91 85 / 9%), transparent 100%);
}

.uc-grid {
  display: grid;
  grid-template-columns: 380px 1fr;
  gap: 60px;
  margin-top: 56px;
  align-items: center;
}

.uc-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.uc-btn {
  text-align: left;
  background: none;
  border: none;
  border-radius: 8px;
  padding: 13px 18px;
  font-family: var(--font-head);
  font-size: 16px;
  font-weight: 500;
  color: var(--muted);
  transition: all 0.25s var(--ease);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.uc-btn:hover {
  color: var(--ink);
  background: var(--blush);
}

.uc-btn.active {
  color: var(--ink);
  background: var(--card);
  box-shadow: 0 8px 24px -16px rgb(90 20 20 / 30%);
}

.uc-btn.active::after {
  content: "→";
  color: var(--red);
}

.uc-stage {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  padding: 24px;
  box-shadow: 0 20px 50px -24px rgb(90 20 20 / 12%);
}

.uc-stage svg {
  display: block;
  width: 100%;
  height: auto;
}

.bldg {
  transition: all 0.45s var(--ease);
}

.bldg .body {
  fill: var(--blush);
  stroke: var(--dusty);
  stroke-width: 1.2;
}

.bldg .grid-l {
  stroke: var(--line);
  stroke-width: 0.8;
}

.bldg.hl .body {
  fill: rgb(140 25 28 / 8%);
  stroke: var(--red);
}

.bldg.hl .grid-l {
  stroke: rgb(140 25 28 / 35%);
}

.bldg.faded {
  opacity: 0.32;
}

/* ============ credibility ============ */
.cred {
  background: var(--rose);
}

.cred-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 70px;
  margin-top: 14px;
}

.cred-points {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin-top: 30px;
}

.cred-point {
  border-top: 1px solid var(--line);
  padding: 18px 4px;
  display: flex;
  gap: 16px;
  align-items: baseline;
}

.cred-point:last-child {
  border-bottom: 1px solid var(--line);
}

.cred-point .tick {
  color: var(--red);
  font-family: var(--font-mono);
  font-size: 12px;
}

.cred-point p {
  font-size: 15.5px;
}

.slotbox {
  border: 1.5px dashed var(--line);
  border-radius: 14px;
  padding: 26px;
  margin-top: 22px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.05em;
  line-height: 1.8;
  background: var(--cream);
}

.slotbox b {
  color: var(--ink);
  font-weight: 500;
  display: block;
  margin-bottom: 4px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-size: 11px;
}

/* ============ interface preview ============ */

/* Third section down: carries the blush→rose step of the descent. */
.preview {
  background: radial-gradient(
    48% 30% at 50% 42%,
    rgb(201 154 147 / 16%),
    transparent 100%
  );

  /* tighter than the shared section rhythm: see the slider cap below — the
     slide, its caption and the controls need to share one laptop viewport */
  padding-top: 88px;
  padding-bottom: 88px;
}

/* The perspective and the rotateX tilt that used to live here belonged to the
   drawn mock: the frame leaned back and flattened as it came into view. A 3D
   transform under a perspective puts the frame on its own GPU layer for good,
   and a screenshot rendered through that layer is resampled rather than drawn
   at native resolution — which read as blur. Nothing tilts now, so the whole
   mechanism is gone and the captures paint directly. */
.frame-wrap {
  margin-top: 60px;
}

.frame {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 50px 110px -60px rgb(90 20 20 / 40%);
}

.frame-body {
  display: grid;
  grid-template-columns: 200px 1fr;
  min-height: 430px;
}

.ph-slot {
  width: 100%;
  height: 100%;
  min-height: 340px;
  border: 1.5px dashed var(--dusty);
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
  letter-spacing: 0.08em;
  text-align: center;
  padding: 20px;
  background: repeating-linear-gradient(
    45deg,
    transparent,
    transparent 14px,
    rgb(90 20 20 / 2%) 14px,
    rgb(90 20 20 / 2%) 28px
  );
}

.ph-slot b {
  color: var(--ink);
  letter-spacing: 0.14em;
  font-weight: 500;
}

/* ============ final CTA ============ */

/* The bottom of the descent: it picks the ramp up from --rose, the exact
   colour .spots hands over at its last pixel, and carries it through --dusty
   into red and then wine for the footer. The join is invisible because both
   sides meet on --rose AND .spots is already moving toward it well before the
   boundary; if the first stop here ever stops matching that handover, the hard
   rule across the page comes straight back. */
.final {
  /* The descent into brand red, evened out: the old ramp went dusty -> red
     across just a quarter of the section, which read as an abrupt band.
     Clay sits between them now, so each step down is a similar distance and
     the darkening reads as one continuous slide into the red. */
  background: linear-gradient(
    180deg,
    var(--rose) 0%,
    var(--dusty) 26%,
    var(--clay) 48%,
    var(--red) 74%,
    var(--wine) 100%
  );
  color: var(--linen);
  padding: 210px 0 150px;
  overflow: hidden;
}

.final-inner {
  text-align: center;
  max-width: 760px;
  margin: 0 auto;
  position: relative;
}

.final h2 {
  font-size: clamp(36px, 4.6vw, 60px);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.05;
}

.final .sec-lede {
  margin: 20px auto 0;
  color: rgb(242 228 223 / 82%);
}

.final .eyebrow {
  color: rgb(242 228 223 / 85%);
}

/* on the red field the primary button inverts to a light chip */
.final .btn-red {
  background: var(--card);
  color: var(--red);
}

.final .btn-red:hover {
  background: var(--linen);
  color: var(--wine);
  box-shadow: 0 10px 24px -12px rgb(36 17 16 / 50%);
}

.final .btn-line {
  border-color: rgb(242 228 223 / 40%);
  color: var(--linen);
}

.final .btn-line:hover {
  border-color: var(--linen);
  color: var(--linen);
}

/* the decorative sketch layers re-ink for the red field */
.final [stroke="#B9B2A8"] {
  stroke: rgb(242 228 223 / 30%);
}

.final [stroke="#8C191C"] {
  stroke: rgb(242 228 223 / 45%);
}

.final [fill="#8C191C"] {
  fill: rgb(242 228 223 / 50%);
}

.final-actions {
  margin-top: 42px;
  display: flex;
  gap: 14px;
  justify-content: center;
  flex-wrap: wrap;
}

.final-logo {
  margin: 70px auto 0;
  position: relative;
  width: min(480px, 86%);
}

.final-logo .logo-center {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
}

.final-logo .final-logo-img {
  width: 190px;
  height: auto;
}

.layer-svg {
  position: absolute;
  inset: -70px -120px;
  z-index: 1;
  pointer-events: none;
}

.layer-svg svg {
  width: 100%;
  height: 100%;
}

.lyr {
  transition:
    transform 1.1s var(--ease),
    opacity 1.1s var(--ease);
  opacity: 0;
}

.final.in .lyr {
  opacity: 1;
  transform: none !important;
}

/* ============ footer ============ */

/* The footer lives in chrome.css now (shared with every other page). */

/* ============ motion utilities ============ */

/* Sections enter with a damped structural settle — the motion of a frame
   coming to rest after ground shaking: rise past level, sway a fraction of
   a degree about the base, and damp out. Amplitudes stay tiny (≤0.3deg,
   ≤10px) so it reads as engineering, not bounce. Class added by home-v3.js;
   reduced-motion never gets either class. */
.sec-fade {
  opacity: 0;
  transform: translateY(44px);
  transform-origin: 50% 100%;
  will-change: transform, opacity;
}

.sec-fade.sec-in {
  /* `backwards` rather than `both`, plus an explicit resting state: with
     `both` the 100% keyframe's transform stuck to the element for the life of
     the page, and a section left holding a transform (with will-change still
     on it) stays on a GPU layer. Browsers rasterise that layer once and then
     scale it to the display, which is what was softening the screenshots on a
     retina screen — the type the browser draws itself survives it, a bitmap
     does not. Landing on transform:none lets the layer go and the images
     paint at native resolution. */
  opacity: 1;
  transform: none;
  will-change: auto;
  animation: seismic-settle 1.5s cubic-bezier(0.3, 0.9, 0.4, 1) backwards;
}

/* transform-only (GPU-composited) so the settle stays 60fps even over the
   glass tiles; the faint scale adds depth without any paint cost */
@keyframes seismic-settle {
  0% {
    opacity: 0;
    transform: translateY(44px) rotate(0.3deg) scale(0.985);
  }

  42% {
    opacity: 1;
    transform: translateY(-9px) rotate(-0.18deg) scale(1.004);
  }

  64% {
    transform: translateY(5px) rotate(0.1deg) scale(0.999);
  }

  80% {
    transform: translateY(-3px) rotate(-0.05deg) scale(1.001);
  }

  91% {
    transform: translateY(1px) rotate(0.02deg) scale(1);
  }

  100% {
    opacity: 1;
    transform: translateY(0) rotate(0) scale(1);
  }
}

/* Softened for the descent: slower, with a focus-pull blur that resolves. */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(4px);
  transition:
    opacity 1.1s var(--ease),
    transform 1.1s var(--ease),
    filter 1.1s var(--ease);
}

.reveal.in {
  opacity: 1;
  transform: none;

  /* `none`, not blur(0): a zero-radius blur is still a filter, and a filter
     keeps the whole subtree on a rasterised layer for the life of the page.
     On a 2x screen that layer gets rasterised once and scaled, which is what
     was softening the screenshots inside .frame-wrap. Interpolating from
     blur(4px) to none animates identically. */
  filter: none;
}

.stagger > * {
  opacity: 0;
  transform: translateY(14px);
  filter: blur(4px);
  transition:
    opacity 0.9s var(--ease),
    transform 0.9s var(--ease),
    filter 0.9s var(--ease);
}

.stagger.in > * {
  opacity: 1;
  transform: none;
  filter: none; /* see .reveal.in */
}

.stagger.in > *:nth-child(1) {
  transition-delay: 0.06s;
}

.stagger.in > *:nth-child(2) {
  transition-delay: 0.14s;
}

.stagger.in > *:nth-child(3) {
  transition-delay: 0.22s;
}

.stagger.in > *:nth-child(4) {
  transition-delay: 0.3s;
}

.stagger.in > *:nth-child(5) {
  transition-delay: 0.38s;
}

.stagger.in > *:nth-child(6) {
  transition-delay: 0.46s;
}

.hero-seq {
  opacity: 0;
  transform: translateY(16px);
  animation: seq-in 1s var(--ease) forwards;
}

.hs1 {
  animation-delay: 0.08s;
}

.hs2 {
  animation-delay: 0.2s;
}

.hs3 {
  animation-delay: 0.34s;
}

.hs4 {
  animation-delay: 0.48s;
}

.hs5 {
  animation-delay: 0.6s;
}

.hs6 {
  animation-delay: 0.35s;
}

@keyframes seq-in {
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .reveal,
  .stagger > *,
  .hero-seq,
  .sec-fade {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
    animation: none;
  }

  .lyr {
    opacity: 1 !important;
    transform: none !important;
    transition: none;
  }

  .frame {
    transform: none;
  }

  .scroll-cue .chev {
    animation: none;
  }

  .st-dim {
    opacity: 1;
  }

  /* stylelint-disable-next-line selector-id-pattern -- id is referenced from home-v3.js */
  #wfPath {
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
  }
}

/* ============ responsive ============ */

/* Below this the row cannot hold the copy and the deck at once, so the hero
   stacks and the deck takes the full width. */
@media (max-width: 1180px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 44px;
    align-items: start;
  }

  .hero-grid > :first-child {
    max-width: 860px;
  }

  .hero {
    min-height: auto;
    padding-top: 126px;
  }
}

@media (max-width: 1080px) {
  .scales-grid,
  .uc-grid,
  .cred-grid {
    grid-template-columns: 1fr;
    gap: 48px;
  }

  .prod-shell {
    grid-template-columns: 1fr;
    gap: 30px;
  }

  .prod-tabs {
    flex-direction: row;
    overflow-x: auto;
    gap: 8px;
    padding-bottom: 6px;
  }

  .prod-tab {
    border-left: none;
    border-bottom: 2px solid var(--line);
    white-space: nowrap;
    padding: 12px 16px;
  }

  .prod-tab[aria-selected="true"] {
    border-bottom-color: var(--red);
  }

  .prod-tab .d {
    display: none;
  }

  .out-grid {
    grid-template-columns: 1fr 1fr;
  }

  .out-card,
  .out-card:nth-child(4),
  .out-card:nth-child(5) {
    grid-column: auto;
  }

  .out-spine {
    display: none;
  }

  .wf-grid {
    grid-template-columns: 1fr;
    gap: 30px;
  }

  .wf-line {
    display: none;
  }

  .nav-links {
    display: none;
  }

  .foot-grid {
    grid-template-columns: 1fr 1fr;
  }

  .scroll-cue {
    display: none;
  }
}

@media (max-width: 620px) {
  .wrap {
    padding: 0 22px;
  }

  section {
    padding: 80px 0;
  }

  .hero-badge {
    font-size: 11.5px;
    padding: 8px 14px;
    gap: 8px;
  }

  .eyebrow {
    font-size: 13px;
  }

  .out-grid {
    grid-template-columns: 1fr;
  }

  .frame-body {
    grid-template-columns: 1fr;
  }

  .foot-grid {
    grid-template-columns: 1fr;
  }

  .nav-cta .btn-line {
    display: none;
  }

  .final-logo .final-logo-img {
    width: 150px;
  }
}

/* ============ use cases — richer stage ============
   The stage now has a head row, per-building annotations that only appear on
   highlight, and a readout that changes with the selected decision. */
.uc-stage__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 14px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
}

/* stylelint-disable-next-line selector-id-pattern -- id is referenced from home-v3.js */
.uc-stage__head #ucCaption {
  color: var(--ink);
}

/* Outputs matrix: one row per SP3 output. Rows the selected decision uses are
   lit; the rest recede but stay readable, so the contrast shows what a decision
   draws on without hiding the full catalogue. */
.uc-outs {
  list-style: none;
  margin: 0;
  padding: 0;
}

.uc-out {
  display: grid;
  grid-template-columns: 118px 132px 1fr;
  gap: 16px;
  align-items: center;
  padding: 13px 10px;
  border-radius: 8px;
  border-bottom: 1px solid var(--line);
  transition:
    opacity 0.3s var(--ease),
    background 0.3s var(--ease);
}

.uc-out:last-child {
  border-bottom: none;
}

.uc-out__name {
  font-size: 12.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.uc-out__viz {
  display: block;
  width: 100%;
  height: 34px;
}

.uc-out__desc {
  font-size: 14.5px;
  color: var(--muted);
  line-height: 1.55;
}

/* selected state */
.uc-out.on {
  background: var(--cream-2);
}

.uc-out.on .uc-out__name {
  color: var(--red);
}

.uc-out.on .uc-out__desc {
  color: var(--ink);
}

.uc-out.off {
  opacity: 0.34;
}

@media (max-width: 620px) {
  .uc-out {
    grid-template-columns: 1fr 96px;
    gap: 8px 12px;
  }

  .uc-out__desc {
    grid-column: 1 / -1;
  }
}

.uc-meta {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.uc-meta dt {
  font-size: 11.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 5px;
}

.uc-meta dd {
  margin: 0;
  font-size: 14px;
  color: var(--ink);
  letter-spacing: 0.02em;
}

@media (max-width: 620px) {
  .uc-meta {
    grid-template-columns: 1fr;
    gap: 12px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bldg .anno,
  .bldg .body {
    transition: none;
  }
}

/* ============ partners marquee + webinars ============ */
.pw {
  background: radial-gradient(
    46% 30% at 76% 58%,
    rgb(201 154 147 / 18%),
    transparent 100%
  );
}

/* Marquees — each strip's content renders twice; assets/js/site.js drives
   the motion as a translate3d on the track (GPU-composited and sub-pixel,
   where scrollLeft renders in whole-pixel steps that stutter at drift
   speeds), wrapping at the halfway point for a seamless loop. Mouse and
   touch both grab the strip; touch-action keeps vertical page scrolling
   native while horizontal gestures drag the strip. Releasing a fast drag
   keeps its momentum and eases back into the drift. Reduced-motion users
   get no self-drift but keep dragging. */
.marquee-scroller {
  overflow: hidden;
  cursor: grab;
  touch-action: pan-y;
}

.marquee-scroller > * {
  will-change: transform;
}

.marquee-scroller.is-dragging {
  cursor: grabbing;
  user-select: none;
}

/* Keep a mid-drag grab from selecting text or lifting an image ghost. */
.marquee-scroller img {
  user-select: none;
  -webkit-user-drag: none;
}

/* Logo marquee. Slides render below the source logos' native 150px so they
   stay sharp on hi-DPI screens. No mask edge-fade here: a mask isolates the
   subtree as its own compositing group, which forced each slide to carry a
   solid stand-in background for the logos' multiply blend — and any drift
   between that stand-in and the page's descent gradient rendered every logo
   as a mismatched tile. Unmasked, the logos multiply straight onto the real
   backdrop and stay seamless wherever the section sits. */
.pw-marquee {
  margin-top: 22px;
  overflow: hidden;
}

.pw-marquee__track {
  display: flex;
  width: max-content;
}

/* Logos render in full colour. Slides sit below the source logos' native 150px
   so they stay sharp on hi-DPI screens; hover lifts a logo slightly so the
   strip still responds to the pointer without desaturating everything.

   Every logo is a JPEG with a baked-in white box, so the strip read as a row
   of white tiles floating on the warm page. Multiplying each logo against the
   real page backdrop maps that white exactly onto the background and leaves
   only the mark — no per-slide stand-in background (see .pw-marquee above). */
.pw-slide {
  flex: 0 0 112px;
  margin-right: 44px;
  display: flex;
  align-items: center;
}

.pw-slide img {
  width: 100%;
  height: auto;
  mix-blend-mode: multiply;
  transition:
    transform 0.3s var(--ease),
    filter 0.3s var(--ease);
}

.pw-slide:hover img {
  transform: scale(1.07);
  filter: saturate(1.1);
}

/* Webinars promo */
.pw-webinars {
  margin-top: 66px;
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  gap: 52px;
  align-items: center;
  padding-top: 56px;
  border-top: 1px solid var(--line);
}

/* When the promo opens its section there is nothing above it to divide from. */
.pw-webinars--lead {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.pw-webinars h3 {
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 600;
  line-height: 1.15;
}

.pw-webinars p {
  margin-top: 14px;
  color: var(--muted);
  font-size: 16px;
  max-width: 46ch;
}

.pw-webinars__actions {
  margin-top: 28px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.pw-sessions {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  box-shadow: 0 30px 70px -50px rgb(90 20 20 / 22%);
  overflow: hidden;
}

.pw-session {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 4px 14px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
  align-items: baseline;
}

.pw-session:last-child {
  border-bottom: none;
}

.pw-session__tag {
  grid-row: span 2;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 3px;
}

.pw-session--next .pw-session__tag {
  color: var(--red);
}

.pw-session a {
  font-family: var(--font-head);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.4;
  text-decoration: none;
  color: var(--ink);
  transition: color 0.2s;
}

.pw-session a:hover {
  color: var(--red);
}

.pw-session__date {
  font-size: 12.5px;
  letter-spacing: 0.06em;
  color: var(--muted);
}

@media (max-width: 1080px) {
  .pw-webinars {
    grid-template-columns: 1fr;
    gap: 34px;
  }
}

@media (max-width: 620px) {
  .pw-session {
    grid-template-columns: 1fr;
    gap: 6px;
  }

  .pw-session__tag {
    grid-row: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pw-marquee {
    overflow-x: auto;
  }

  .pw-marquee__track {
    animation: none;
  }

  .pw-slide img {
    transition: none;
  }
}

/* ============ client spotlights ============ */

/* This section is where the descent finally leaves paper, so it walks the
   palette's own next two steps: paper -> --blush -> --rose. That ramp is listed
   FIRST so it paints over the two washes below it — the colour at the bottom
   edge is then exactly --rose whatever the washes are doing there, and .final
   starts on the same value. Beginning the change well inside this section (58%
   down, below the cards) is what makes the join invisible: a ramp that starts
   at the boundary still reads as a line, because the rate of change jumps
   there even when the colour matches. */
.spots {
  background:
    linear-gradient(
      180deg,
      rgb(235 217 210 / 0%) 58%,
      var(--blush) 84%,
      var(--rose) 100%
    ),
    radial-gradient(
      46% 30% at 22% 46%,
      rgb(201 154 147 / 20%),
      transparent 100%
    ),
    radial-gradient(38% 26% at 84% 40%, rgb(168 91 85 / 9%), transparent 100%);
}

/* The spotlight strip: full-bleed drifting marquee of project cards. The
   wrapper owns the edge fade; the track is one long flex row and each card
   keeps a fixed width so the loop point is stable. */
.spot-marquee {
  margin-top: 56px;
  margin-bottom: -40px; /* returns the track's extra shadow padding */
  mask-image: linear-gradient(
    90deg,
    transparent,
    #000 5%,
    #000 95%,
    transparent
  );
}

.spot-marquee__track {
  display: flex;
  gap: 26px;
  width: max-content;

  /* The scroller clips (it must, for the loop), so the track pads far
     enough that the cards' ambient shadow (~52px past the box) fades out
     fully instead of being sheared into a hard line under the row; the
     wrapper's negative margin gives the extra space back to the layout. */
  padding: 8px 26px 68px;
}

.spot-card {
  display: flex;
  flex: 0 0 372px;
  width: 372px;
}

@media (max-width: 640px) {
  .spot-card {
    flex-basis: 300px;
    width: 300px;
  }
}

.spot-card__link {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-decoration: none;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  transition:
    transform 0.25s var(--ease),
    box-shadow 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.spot-card__link:hover {
  transform: translateY(-4px);
  border-color: var(--dusty);
  box-shadow: 0 24px 50px -30px rgb(90 20 20 / 34%);
}

.spot-card__media {
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--blush);
}

.spot-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s var(--ease);
}

.spot-card__link:hover .spot-card__media img {
  transform: scale(1.04);
}

.spot-card__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 22px 22px 24px;
  flex: 1;
}

.spot-card__tag {
  align-self: flex-start;
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--red);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 4px 11px;
  background: var(--card);
}

.spot-card__title {
  font-family: var(--font-head);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.28;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.spot-card__teaser {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--muted);
  flex: 1;
}

.spot-card__more {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--red);
  margin-top: 4px;
}

.spot-card__link:hover .arr {
  transform: translateX(4px);
}

.spot-card__more .arr {
  display: inline-block;
  transition: transform 0.25s var(--ease);
}

.spot-all {
  margin-top: 44px;
}

/* The shared .btn-line hover only darkens its border to ink, which is very
   quiet for the one button that carries this section. Scoped here rather than
   changed globally so the hero's "Explore SP3" and the nav keep the restrained
   version: this one fills with brand red and lifts. */
.spot-all .btn-line:hover,
.spot-all .btn-line:focus-visible {
  background: var(--red);
  border-color: var(--red);
  color: var(--card);
  box-shadow: 0 12px 26px -14px rgb(140 25 28 / 55%);
}

/* ============ newsletter band ============ */

/* Sits inside the bedrock field between the final CTA and the footer, so the
   bottom of the descent reads as one continuous dark ground. */
.home-newsletter {
  background: linear-gradient(180deg, var(--wine), var(--bedrock));
  color: var(--linen);
  padding: 90px 0;

  /* tuck 1px under the final CTA: at fractional layout positions on hi-DPI
     screens the renderer anti-aliases both dark edges against the light page
     ramp behind them, which reads as a thin white hairline */
  margin-top: -1px;
}

/* same guard for the newsletter→footer boundary (body ground is light) */
.site-footer {
  margin-top: -1px;
}

.home-newsletter .eyebrow {
  color: var(--dusty);
}

/* Horizontal band: the pitch on the left, the form breathing on the right. */
.home-newsletter-inner {
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: 64px;
  align-items: center;
}

.home-newsletter-title {
  font-size: clamp(24px, 2.8vw, 34px);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.018em;
  margin: 0;
}

/* The HubSpot form renders in an iframe with its own light styles — float it
   on a light card so it stays readable on the dark band. The wide card gives
   the embedded fields room to lay out inline instead of stacking. */
.home-newsletter .hs-form-frame {
  background: var(--card);
  border: 1px solid rgb(242 228 223 / 14%);
  border-radius: 16px;
  padding: 28px 30px 18px;
  margin: 0;
  box-shadow: 0 24px 60px -28px rgb(20 8 8 / 50%);
}

@media (max-width: 940px) {
  .home-newsletter-inner {
    grid-template-columns: 1fr;
    gap: 36px;
    text-align: center;
  }

  .home-newsletter-title {
    margin: 0 auto;
    max-width: 30ch;
  }

  .home-newsletter .hs-form-frame {
    max-width: 560px;
    margin: 0 auto;
  }
}

@media (max-width: 620px) {
  .home-newsletter {
    padding: 64px 0;
  }
}

/* (Contour dividers removed: the whole ramp is now one continuous gradient
   on <main>, so there are no seams to bridge.) */

/* ============ the descent — glass surfaces ============ */

/* Every light card tile is frosted glass. Glass only reads when the ground
   shows through it, so two things work together here: the tiles run much
   more transparent than a "white box" (down to ~40% in the sheen's low
   corner), and each tile section carries soft dusty/clay washes (see the
   section rules) that drift behind the cards for the blur to frost. A bright
   rim, an inner top highlight, and a faint dusty inner base line finish the
   pane read. */
.out-card,
.uc-stage,
.scales-viz,
.prod-stage,
.pw-sessions,
.spot-card__link,
.frame {
  background: linear-gradient(
    150deg,
    rgb(253 251 248 / 82%) 0%,
    rgb(253 251 248 / 46%) 58%,
    rgb(253 251 248 / 58%) 100%
  );
  border: 1px solid rgb(253 251 248 / 95%);

  /* three shadow distances (contact / mid / ambient) plus the rim highlights
     give each pane real depth against the page */
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 98%),
    inset 1px 0 0 rgb(253 251 248 / 40%),
    inset 0 -1px 0 rgb(201 154 147 / 24%),
    0 1px 2px rgb(90 20 20 / 12%),
    0 14px 28px -14px rgb(90 20 20 / 30%),
    0 44px 88px -36px rgb(90 20 20 / 38%);
}

/* the pane tilts up toward the cursor: a small lift + deepened shadows */
.out-card,
.uc-stage,
.scales-viz,
.prod-stage,
.pw-sessions {
  transition:
    transform 0.35s var(--ease),
    box-shadow 0.35s var(--ease);
}

.out-card:hover,
.uc-stage:hover,
.scales-viz:hover,
.prod-stage:hover,
.pw-sessions:hover {
  transform: translateY(-6px);
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 98%),
    inset 1px 0 0 rgb(253 251 248 / 40%),
    inset 0 -1px 0 rgb(201 154 147 / 24%),
    0 2px 4px rgb(90 20 20 / 12%),
    0 22px 40px -16px rgb(90 20 20 / 34%),
    0 60px 110px -40px rgb(90 20 20 / 44%);
}

/* the hero pair floats over the busy photo, so it stays a little denser to
   keep the small mono labels legible */
/* stylelint-disable-next-line no-duplicate-selectors -- hero-only glass variant overriding the base panel */
.deck__panel {
  background: linear-gradient(
    150deg,
    rgb(253 251 248 / 88%) 0%,
    rgb(253 251 248 / 62%) 100%
  );
  border: 1px solid rgb(253 251 248 / 95%);
  backdrop-filter: blur(16px) saturate(1.15);
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 98%),
    inset 0 -1px 0 rgb(201 154 147 / 18%),
    0 24px 60px -28px rgb(90 20 20 / 28%);
}

/* stylelint-disable-next-line no-duplicate-selectors -- glass variant overriding the base rule above */
.hero-badge {
  background: linear-gradient(
    150deg,
    rgb(253 251 248 / 90%),
    rgb(253 251 248 / 62%)
  );
  border: 1px solid rgb(253 251 248 / 95%);
  backdrop-filter: blur(10px) saturate(1.15);
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 98%),
    0 4px 14px -6px rgb(90 20 20 / 14%);
}

/* stylelint-disable-next-line no-duplicate-selectors -- glass variant overriding the base rule above */
.uc-btn.active {
  background: linear-gradient(
    150deg,
    rgb(253 251 248 / 90%),
    rgb(253 251 248 / 55%)
  );
  backdrop-filter: blur(10px);
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 98%),
    0 8px 24px -16px rgb(90 20 20 / 30%);
}

/* hover: the glass brightens and lifts a touch instead of changing colour */
/* stylelint-disable-next-line no-duplicate-selectors -- glass variant overriding the base rule above */
.spot-card__link:hover {
  background: linear-gradient(
    150deg,
    rgb(253 251 248 / 90%),
    rgb(253 251 248 / 60%)
  );
  border-color: rgb(253 251 248 / 100%);
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 98%),
    0 24px 50px -30px rgb(90 20 20 / 40%);
}

/* ============ mobile formatting pass (≤620px) ============ */

/* The inline SVG diagrams scale with the viewport, so at phone widths their
   labels rendered at 7–9px. Sizes below are in SVG user units — chosen per
   diagram so labels land at ~10px rendered without colliding. The .m-hide
   labels are long axis captions that cannot fit at a readable size. */
@media (max-width: 620px) {
  /* hero: text zone gets a near-solid wash (the desktop horizontal wash left
     phone-width text sitting on the photo); the skyline peeks through behind
     the analysis card, then dissolves into the ramp */
  .hero {
    background:
      linear-gradient(
        180deg,
        rgb(250 246 242 / 95%) 0%,
        rgb(250 246 242 / 90%) 52%,
        rgb(250 246 242 / 62%) 78%,
        rgb(250 246 242 / 96%) 97%,
        #faf6f2 100%
      ),
      url("/assets/images/2026/08/sf-transamerica.jpg") center top/cover
        no-repeat,
      var(--paper);
  }

  .m-hide {
    display: none;
  }

  .deck__panel {
    padding: 16px 14px 12px;
  }

  /* SVG text cannot wrap, and the stage renders near 1:1 here, so labels
     stay close to their design size or the longest one clips. */
  .deck__panel svg .mono-label {
    font-size: 12px;
  }

  /* product name + the illustrative marker on one line at this width */
  .deck .viz-head {
    flex-wrap: wrap;
    gap: 2px 10px;
    font-size: 11px;
    letter-spacing: 0.1em;
  }

  .chip-row {
    gap: 8px;
  }

  .chip {
    font-size: 10px;
    padding: 4px 8px;
    letter-spacing: 0.05em;
  }

  /* Same for the hazard list: every row is a button that swaps the map. */
  .hz-item {
    min-height: 44px;
    padding-top: 11px;
    padding-bottom: 11px;
  }

  .scales-viz {
    padding: 12px;
  }

  .scales-viz svg text {
    font-size: 16px;
  }

  .scene svg .sc-label {
    font-size: 15px;
  }

  /* "One connected platform" tabs: compact, and un-dim the unselected items
     so stacked mobile content doesn't read as disabled */
  .scale-item {
    padding: 18px 4px;
  }

  .scale-item__h {
    font-size: 18px;
  }

  .scale-item__p {
    font-size: 14.5px;
    margin-top: 6px;
  }

  .scale-item.dim {
    opacity: 0.62;
  }

  .prod-stage {
    padding: 20px 14px;
    min-height: 380px;
  }

  .scene {
    inset: 56px 14px 14px;
  }

  .uc-stage {
    padding: 18px 14px;
  }

  .uc-out {
    padding: 14px 8px;
  }

  .trust {
    padding: 60px 0 48px;
  }

  .trust p {
    margin-bottom: 32px;
  }

  .final {
    padding: 120px 0 90px;
  }

  .final-logo {
    margin-top: 52px;
  }

  .home-newsletter .hs-form-frame {
    padding: 22px 18px 12px;
  }
}

/* ============ hero visualization layout ============
   The frame is split into the animated structural model and the numbers it
   produces. The readouts are HTML so their labels wrap instead of colliding
   with their charts the way fixed-position SVG text did. */
.viz-body {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 26px;
  align-items: stretch;
}

.viz-stage {
  min-width: 0;
  display: flex;
  align-items: center;
}

.viz-stage svg {
  display: block;
  width: 100%;

  /* The panel spans the full deck now, so without a cap the drawing would
     scale up until its height drove the whole hero. The cap holds it at the
     size it was drawn for; height follows the viewBox, so the proportions
     are the drawing's own at every width — never stretched. */
  max-width: 400px;
  height: auto;
  margin: 0 auto;
}

.viz-readout {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.metric {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 14px;
  background: var(--cream);
  min-width: 0;
}

.metric__label {
  font-family: var(--font-mono);
  font-size: 12.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

.metric__row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 6px;
}

.metric__spark {
  flex: 1 1 auto;
  min-width: 0;
  height: 46px;
  display: block;
}

.metric__value {
  flex: 0 0 auto;
  font-family: var(--font-head);
  font-size: 26px;
  font-weight: 700;
  line-height: 1;
  color: var(--red);
  letter-spacing: -0.02em;
}

.metric__unit {
  font-size: 13px;
  font-weight: 600;
  margin-left: 4px;
  letter-spacing: 0.02em;
}

.metric__sub {
  margin: 6px 0 0;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--muted);
}

/* The animation reveals these three panels in turn. .st-dim drops SVG art to
   20% opacity, which is far too faint for real text, so the readouts keep a
   legible floor and the cycle supplies emphasis rather than legibility. */
.metric.st-dim {
  opacity: 0.62;
  transition:
    opacity 0.5s var(--ease),
    border-color 0.5s var(--ease);
}

.metric.st-dim.on {
  opacity: 1;
  border-color: var(--clay);
  background: var(--card);
}

/* Below the hero's own stacking point the frame is full width, so the model
   and the readouts sit side by side comfortably again. On phones they stack. */
@media (max-width: 620px) {
  .viz-body {
    grid-template-columns: 1fr;
    gap: 18px;
  }

  .metric__value {
    font-size: 23px;
  }
}

@media (max-width: 1080px) {
  .wf-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 26px;
  }
}

@media (max-width: 620px) {
  .wf-grid {
    grid-template-columns: 1fr;
    gap: 22px;
  }
}

/* ==========================================================================
   THE PRODUCT DECK — four product cards stacked like pages in a book.

   The deck is a grid whose column widths ARE the state: the open card takes
   the free space, the closed ones sit as narrow spines. Browsers that can
   interpolate grid-template-columns animate the page turn for free; the rest
   simply snap, which is a perfectly good fallback.

   3D comes from a perspective on the deck plus a small rotateY on the closed
   spines — those to the left of the open card lean one way, those to the
   right the other, so the stack reads as physical pages rather than tabs.

   On phones (≤1080px, where the hero is already one column) the same
   metaphor turns 90°: spines become full-width bars stacked above their
   panel, i.e. an accordion. The open card keeps its spine there, since the
   bar is the thing you tap to change cards.
   ========================================================================== */

/* Flex, not grid, and deliberately so: grid-template-columns only
   interpolates when every track keeps the same TYPE in the same position, and
   the open track is a flex unit while the closed ones are lengths. Browsers
   therefore snapped the columns and the page turn looked broken. flex-grow is
   a plain number, so 0 -> 1 animates cleanly on every engine.

   During the turn the open card clips its panel (home-v3.js sets the panel to
   its final width, so the content never reflows mid-animation — the card
   simply reveals it, the way a page opens). The clip is dropped once the turn
   settles so the panel's shadow is not cut off. */
:root {
  --ease-turn: cubic-bezier(0.4, 0, 0.2, 1);

  /* The landing page runs to the width the site header already uses, so the
     deck has room to draw and the page lines up with the nav above it. */
  --content-width: 1400px;

  /* Two intermediate stops for the page descent (see the ramp on <main>).
     They sit between the palette's paper/blush/rose so each step down is
     small enough that the ramp reads as one gradient, not a set of bands. */
  --ramp-1: #f7f0eb; /* between paper and blush */
  --ramp-2: #efe2db; /* between blush and rose */
}

.deck {
  /* Four tabs across the top, one full-width panel beneath. This replaced the
     book layout (three vertical spines beside the open card): the spines made
     three of the four product names read sideways, and the open panel paid
     for them in width. Each .deck__card is display:contents, so its spine and
     panel place themselves directly on this grid — the markup and the JS's
     card/spine/panel structure are unchanged. */
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: auto 1fr;
  gap: 12px;
}

.deck__card {
  display: contents;
}

/* Each card's tab takes its own column on the top row. */
.deck__card:nth-of-type(1) > .deck__spine {
  grid-column: 1;
}

.deck__card:nth-of-type(2) > .deck__spine {
  grid-column: 2;
}

.deck__card:nth-of-type(3) > .deck__spine {
  grid-column: 3;
}

.deck__card:nth-of-type(4) > .deck__spine {
  grid-column: 4;
}

/* ---- the spine: a closed card's clickable edge ----
   Red glass rather than the old near-white glass. A closed spine is a control,
   and on a pale page a pale tab reads as decoration — carrying the brand red
   makes the three of them obviously the things you press, and white type on
   red is the strongest pairing available in the palette. The glass is still
   glass: a translucent red over the backdrop blur, not a flat fill. */

/* ---- the tabs: a quiet segmented control ----
   Four pale pills; the selected one is the only solid red on the row, so
   "which am I reading" is instant and the row stays calm. The old design made
   all four heavy red-glass bars, which shouted. Interactivity is conveyed by
   the control itself — hover lift, cursor, and the auto-advance progress
   hairline on the active pill — instead of an instructional caption. */
.deck__spine {
  position: relative;
  grid-row: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 13px 16px;
  cursor: pointer;
  overflow: hidden;
  border: 1px solid rgb(140 25 28 / 20%);
  border-radius: 999px;
  background: rgb(253 251 248 / 62%);
  backdrop-filter: blur(10px) saturate(1.1);
  box-shadow: 0 10px 26px -20px rgb(90 20 20 / 40%);
  transition:
    background 0.3s var(--ease),
    border-color 0.3s var(--ease),
    box-shadow 0.3s var(--ease),
    transform 0.3s var(--ease);
}

.deck__spine:hover,
.deck__spine:focus-visible {
  background: rgb(253 246 242 / 92%);
  border-color: rgb(140 25 28 / 45%);
  box-shadow: 0 14px 30px -18px rgb(90 20 20 / 45%);
  transform: translateY(-2px);
}

/* Product names carry the house convention everywhere they appear: "SP3-"
   upright, the product italic, set in the display face and in brand red so
   the name is the thing you see first. */
.deck__spine-label,
.deck__name {
  font-family: var(--font-head);
  font-weight: 600;
  color: var(--red);
  letter-spacing: -0.005em;
  text-transform: none;
}

.deck__spine-label em,
.deck__name em {
  font-style: italic;
}

.deck__spine-label {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--espresso);
  white-space: nowrap;
  position: relative;
  z-index: 1;
  transition: color 0.3s var(--ease);
}

.deck__card.is-active .deck__spine-label {
  color: var(--card);
}

.deck__name {
  font-size: 20px;
  line-height: 1.2;
}

/* The active tab is the row's one solid red — selected, seated, not floating. */
.deck__card.is-active > .deck__spine {
  border-color: var(--red);
  background: linear-gradient(160deg, var(--red), var(--wine));
  box-shadow:
    inset 0 1px 0 rgb(253 251 248 / 22%),
    0 12px 28px -16px rgb(90 20 20 / 55%);
  transform: none;
  cursor: default;
}

.deck__card:not(.is-active) > .deck__panel {
  display: none;
}

/* stylelint-disable-next-line no-duplicate-selectors -- layout for the panel whose frame styling is set above */
.deck__panel {
  /* One cell under the whole tab row. All four panels share it; only the
     active one is displayed, so the width is constant and no JS sizing is
     needed for the switch. */
  grid-row: 2;
  grid-column: 1 / -1;
  min-width: 0;
}

/* Content settles in after the card has opened, so the two motions read as
   one gesture instead of competing. Re-displaying the panel restarts these
   automatically, so no JS is involved. */
.deck__panel > * {
  animation: deck-content-in 0.34s var(--ease) 0.28s both;
}

@keyframes deck-content-in {
  from {
    opacity: 0;
    transform: translateX(-8px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* One line under the header saying what this card is for. */
.deck__lede {
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
  margin: -4px 0 16px;
  max-width: 62ch;
}

/* Readouts that carry a word or a plain count rather than a chart. */
.metric--plain .metric__value {
  font-size: 23px;
}

.metric--plain .metric__row {
  margin-top: 4px;
}

/* ---- phones and tablets: the same deck, rotated into an accordion ---- */
@media (max-width: 1080px) {
  .deck {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }

  /* the cards come back as boxes here — each bar sits directly above its
     panel, accordion-style */
  .deck__card {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 10px;
  }

  .deck__panel {
    flex: 1 1 auto;
    width: auto;
  }

  .deck__spine {
    /* label left, chevron right across the bar */
    justify-content: flex-start;
    padding: 14px 18px;
  }

  .deck__spine:hover,
  .deck__spine:focus-visible {
    transform: none;
  }

  /* a chevron marks the bar as the control */
  .deck__spine::after {
    content: "";
    margin-left: auto;
    width: 8px;
    height: 8px;
    background: none;
    border-right: 1.6px solid rgb(51 32 28 / 65%);
    border-bottom: 1.6px solid rgb(51 32 28 / 65%);
    transform: rotate(-45deg);
  }

  .deck__card.is-active > .deck__spine::after {
    transform: rotate(45deg);
    border-color: var(--card);
  }
}

@media (prefers-reduced-motion: reduce) {
  .deck__spine {
    transition: none;
  }

  .deck__panel > * {
    animation: none;
  }
}

/* Inside the deck the readout column carries the wrapping text, so it gets
   the slightly larger share — that keeps the subs to two lines and leaves the
   stage tall enough for the frame to fill it. */
.deck .viz-body {
  grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
  gap: 22px;
  align-items: center;
}

/* The stage is capped in height so the frame is generous but never balloons
   when the deck has the full page width; the readouts centre beside it. */

/* The stage column stacks its HTML title over the drawing. */
.deck .viz-stage {
  flex-direction: column;
}

.deck .viz-stage svg {
  max-height: 430px;
}

/* Annotations inside the stage drawings, sized up from the shared 11px so
   the figures read without leaning in. Wins over the mobile bump on
   .deck__panel svg .mono-label by specificity, which is fine — 12.5px
   clears every stage caption at every width. */
.deck .viz-stage svg .mono-label {
  font-size: 15px;
}

/* The consulting card's branch labels sit inside drawn pills that were sized
   for 11px type — the shared annotation bump above would run them out of
   their boxes. */
.deck .viz-stage svg .cs-branch text {
  font-size: 11px;
}

.deck .viz-readout {
  justify-content: center;
}

.cs-branch rect {
  fill: rgb(140 25 28 / 4%);
  stroke: var(--dusty);
}

/* On phones the stage and its readouts stack. This repeats the shared
   .viz-body rule at .deck specificity, which the deck's own two-column
   rule above would otherwise win against inside this breakpoint. */
@media (max-width: 620px) {
  .deck .viz-body {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }

  .deck .metric__value {
    font-size: 24px;
  }

  .deck .metric--plain .metric__value {
    font-size: 21px;
  }
}

/* A gloss on the closed pages: one bright edge down the top and a soft
   highlight falling away from it, so a spine catches light like a real page
   edge rather than reading as a flat tab. Much fainter than it was when the
   spine was near-white — over red glass the old 62% white read as a smear
   rather than a highlight. */

/* The auto-advance progress hairline. The deck rotates to the next product
   when the open card's animation finishes its cycle; this thin line filling
   along the active pill is what says so — it replaced an instructional
   caption above the deck. home-v3.js drives --deck-progress (0 → 1) and the
   first manual click sets .is-locked on the deck, which retires both the
   rotation and the line. Desktop only: the phone accordion has chevrons. */
@media (min-width: 1081px) {
  .deck__card.is-active > .deck__spine::after {
    content: "";
    position: absolute;
    left: 18px;
    bottom: 6px;
    height: 2.5px;
    width: calc((100% - 36px) * var(--deck-progress, 0));
    border-radius: 2px;
    background: rgb(253 251 248 / 72%);
    pointer-events: none;
  }

  .deck.is-locked .deck__card.is-active > .deck__spine::after {
    display: none;
  }
}

/* The consulting card has no SP3- prefix to hold upright. */
.deck__name--plain,
.deck__spine-label--plain {
  font-style: italic;
}

@media (max-width: 1080px) {
  .deck__spine-label {
    font-size: 17px;
  }
}

@media (max-width: 620px) {
  .deck__name {
    font-size: 17px;
  }

  .deck__spine-label {
    font-size: 16px;
  }
}

/* .viz-head b paints the head's default label in ink at weight 500, and it
   outranks a lone .deck__name — the product name has to win here. */
.viz-head .deck__name {
  color: var(--red);
  font-weight: 600;
}

/* ---- deck proportions at full width ----
   The landing page runs to 1400, but a card that wide leaves the drawing
   stranded in the middle of its column: the stage is height-capped, so extra
   column width becomes empty space rather than a bigger figure. The deck is
   therefore capped just under the page width and the stage column is sized to
   what the drawing actually occupies at that cap (300:400 at 540 tall is
   405 wide), so nothing is wasted and the readouts take the rest. */

/* A readout reads left to right: the label on top, the figure on the left,
   and the words — value, then its context — beside the figure rather than
   underneath it. The row wrapper becomes display:contents so its children
   can take grid slots without changing the markup. */
.deck .metric {
  display: grid;
  grid-template:
    "label label" auto "spark value" auto "spark sub" 1fr / minmax(0, 1.3fr)
    minmax(0, 1fr);
  align-items: start;
  gap: 2px 14px;
  padding: 11px 14px 10px;
}

.deck .metric__label {
  grid-area: label;
}

.deck .metric__row {
  display: contents;
}

.deck .metric__value {
  grid-area: value;
  font-size: 25px;
  margin-top: 2px;
}

.deck .metric--plain .metric__value {
  font-size: 22px;
}

.deck .metric__spark {
  grid-area: spark;
  align-self: center;
  width: 100%;
  height: 66px;
  margin-top: 4px;
}

.deck .metric__sub {
  grid-area: sub;
  font-size: 13.5px;
  margin-top: 3px;
}

@media (min-width: 1181px) {
  .deck .viz-body {
    grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
    gap: 22px;
  }

  .deck__lede {
    font-size: 16px;
    margin-bottom: 18px;
  }

  .deck__name {
    font-size: 22px;
  }

  .deck__panel {
    padding: 22px 22px 18px;
  }
}

/* A closing card's spine arrives as its panel leaves. Fading it in stops the
   swap reading as a flicker at the moment the pages change. */
.deck__card:not(.is-active) > .deck__spine {
  animation: deck-spine-in 0.34s var(--ease) 0.12s both;
}

@keyframes deck-spine-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .deck__card:not(.is-active) > .deck__spine {
    animation: none;
  }
}

/* The deck holds one height for every card, so a shorter card would leave
   dead space at the foot of its panel. Letting the figure take the slack
   centres it instead, and the chip row still sits on the bottom edge. */
/* stylelint-disable-next-line no-duplicate-selectors -- column layout for the panel styled above */
.deck__panel {
  display: flex;
  flex-direction: column;
}

.deck__panel > .viz-body {
  flex: 1 1 auto;
}

/* On desktop the skyline earns more presence. The base washes were tuned for
   the narrow case, where the copy runs the full width and needs a near-solid
   ground; at this size the headline occupies only the left third and the deck
   brings its own opaque panel, so the middle and right of the frame can carry
   far more of the photograph. The left stop stays dense — that is what keeps
   the headline and lede legible — and only the stops under the photo open up.

   Scoped to the desktop range on purpose: narrow layouts keep the base rule
   and the phone keeps its own, both of which already read well. */
@media (min-width: 1181px) {
  /* Anchored to the top of the photograph rather than its foot. The image
     is 3:2 and a desktop hero is wider than that, so `cover` crops a few
     hundred pixels vertically — anchored at the bottom, what it cropped was
     the Empire State Building's spire, which is the reason this photo is
     here. From the top the whole tower falls inside the frame, and what
     goes instead is foreground the vertical wash was already fading into
     paper. */
  .hero {
    background:
      linear-gradient(
        90deg,
        rgb(250 246 242 / 94%) 0%,
        rgb(250 246 242 / 72%) 34%,
        rgb(250 246 242 / 22%) 62%,
        rgb(250 246 242 / 4%) 100%
      ),
      linear-gradient(
        180deg,
        rgb(250 246 242 / 34%) 0%,
        rgb(250 246 242 / 4%) 44%,
        rgb(250 246 242 / 82%) 92%,
        #faf6f2 100%
      ),
      url("/assets/images/2026/08/sf-transamerica.jpg") center top/cover
        no-repeat,
      var(--paper);
  }
}

/* The phase chips label the animation, so they stay legible: sized so the
   longest row still holds one line at the deck's 1440-width proportions, and
   a step larger once the hero scales up on big displays.

   They sit directly under the card's header rather than at the foot of the
   panel. At the foot they were the last thing on a tall card and easy to
   lose; here they read as the card's controls, in the same place on all four
   cards, with a hairline separating them from the figure below. */
.deck .chip-row {
  gap: 7px;
  margin: 0 0 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}

.deck .chip {
  font-size: 12px;
  letter-spacing: 0.05em;
  padding: 6px 11px;
}

/* On a phone the deck's chips are the card's controls and the only way to
   move between outputs, so they get a real tap target and type that reads at
   arm's length. This sits after the rule above on purpose: a media query adds
   no specificity, so order is what settles it. */
@media (max-width: 620px) {
  .deck .chip {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    font-size: 13px;
    padding: 0 14px;
  }
}

@media (min-width: 1560px) {
  .deck .chip {
    font-size: 13.5px;
    padding: 7px 15px;
  }
}

/* ---- readout figures on the SiteHazards, Portfolio and Consulting cards ----
   Every card now carries a figure in each readout, so all four fill the deck's
   shared height instead of leaving the column half empty behind RiskModel. */
.pf-dot {
  fill: var(--rose);
  transition: fill 0.4s var(--ease);
}

.pf-dot.is-on {
  fill: var(--clay);
}

.pf-rank {
  fill: var(--dusty);
}

.pf-rank.is-priority {
  fill: var(--red);
}

/* .metric--plain is gone from the deck now that every readout has a figure;
   the rule stays for any future readout that carries only a word. */

/* ============ inside-the-platform slider ============ */

/* The slideshow track: one transform moves it, matching the marquees. The
   window clips horizontally but pads vertically so the frame's long drop
   shadow isn't cut; the negative margins give that padding back. */
.pv-window {
  overflow: hidden;
  padding: 24px 0 80px;
  margin: -24px 0 -80px;
}

.pv-track {
  display: flex;
  gap: 60px; /* must match the 60px step in home-v3.js pvShow() */
  align-items: flex-start;
  transition:
    transform 0.65s var(--ease),
    height 0.45s var(--ease);
}

.pv-slide {
  flex: 0 0 100%;
  min-width: 100%;
  margin: 0;
}

.frame--shot img {
  display: block;
  width: 100%;
  height: auto;
}

.pv-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  margin-top: 26px;
}

.pv-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--espresso);
  cursor: pointer;
  transition:
    border-color 0.25s var(--ease),
    color 0.25s var(--ease),
    transform 0.25s var(--ease);
}

.pv-btn:hover {
  border-color: var(--red);
  color: var(--red);
  transform: translateY(-1px);
}

.pv-btn svg {
  width: 15px;
  height: 15px;
}

.pv-dots {
  display: flex;
  gap: 9px;
}

.pv-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--dusty);
  background: transparent;
  cursor: pointer;
  transition:
    background 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.pv-dot.on {
  background: var(--red);
  border-color: var(--red);
}

@media (max-width: 620px) {
  .pv-track {
    /* the drawn slides get tall when narrow; keep steps aligned anyway */
    align-items: flex-start;
  }
}

/* ============ product strip ============ */

/* One angled panel per product, the deep detail moved to the product pages
   and /sp3-product-overview/. Carries the tint on the right, opposite the
   centered preview wash above it. */
.prodstrip {
  background: radial-gradient(
    46% 30% at 76% 52%,
    rgb(201 154 147 / 18%),
    transparent 100%
  );
}

/* Five parallelogram panels in one slab, one per product. The slant is
   skewX(-12deg) on the panels themselves, so their left borders draw the
   diagonal dividers for free; the content is counter-skewed upright. At
   rest a panel shows only the product name, held dead-center by an
   invisible spacer that mirrors the description block below it. Hover (or
   keyboard focus) fades in a red-glass layer and the description — pure
   opacity: no widths animate and no text moves, which kept an earlier
   flex-grow version from feeling twitchy. The first and last panels bleed
   past the slab's edges: the skew would otherwise open triangular gaps of
   page background at the slab's corners. */
.ps-row {
  display: flex;
  height: 548px;
  margin-top: 56px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--card);
  box-shadow: 0 34px 70px -50px rgb(90 20 20 / 35%);
}

.ps-panel {
  position: relative;
  display: block;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  transform: skewX(-12deg);
  background: linear-gradient(
    180deg,
    var(--card) 0%,
    rgb(244 234 228 / 55%) 100%
  );
  border-left: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  outline: none;
}

.ps-panel:first-child {
  margin-left: -64px;
  border-left: 0;
}

.ps-panel:last-child {
  margin-right: -64px;
}

/* the red-glass layer, faded in under the content on hover/focus */
.ps-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      120% 90% at 22% 0%,
      rgb(255 235 228 / 18%),
      transparent 55%
    ),
    linear-gradient(165deg, #9c2023 0%, var(--red) 48%, #5c0f11 100%);
  opacity: 0;
  transition: opacity 0.45s var(--ease);
}

.ps-panel:hover::before,
.ps-panel:focus-visible::before {
  opacity: 1;
}

.ps-panel:focus-visible {
  box-shadow: inset 0 0 0 2px rgb(255 235 228 / 70%);
}

.ps-panel__in {
  position: relative; /* stacks the text above the ::before glass layer */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 30px;
  transform: skewX(12deg);
  text-align: center;
}

/* invisible spacer mirroring the description slot below the name, so the
   name sits dead-center and never moves when the description fades in.
   Both halves flex to the same share of the leftover height, so the slot
   grows with the row and the Explore pill can never be clipped at narrow
   desktop widths where the descriptions wrap onto more lines. */
.ps-panel__in::before {
  content: "";
  flex: 1 1 0;
}

/* the bleeding end panels are wider than they look — recenter their text
   over the visible portion */
.ps-panel:first-child .ps-panel__in {
  padding-left: 86px;
}

.ps-panel:last-child .ps-panel__in {
  padding-right: 86px;
}

/* Product names carry the house convention: "SP3-" upright, the product
   italic, display face, brand red. The fixed min-height (two lines) keeps
   panels with wrapping names aligned with the rest. */
.ps-panel__name {
  display: flex;
  align-items: center;
  min-height: 56px;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: clamp(17px, 1.45vw, 21px);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--red);
  text-wrap: balance;
  transition: color 0.4s var(--ease);
}

.ps-panel__name em {
  font-style: italic;
}

/* fixed-height slot matching the __in::before spacer; the description and
   link fade into it without displacing anything */
.ps-panel__reveal {
  display: block;
  flex: 1 1 0;
  width: 100%;
  min-height: 0;
}

/* The description resolves from a blur as it fades in — the same
   blur-to-clear language as the site's .reveal entrances. Still nothing
   moves: opacity and filter only. will-change keeps these five small
   blocks on their own compositor layers so the blur animates on the GPU;
   without it every frame re-rasterizes the text at a new radius, which
   reads as jitter. */
.ps-panel__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding-top: 14px;
  opacity: 0;
  filter: blur(6px);
  will-change: opacity, filter;
  transition:
    opacity 0.55s var(--ease),
    filter 0.55s var(--ease);
}

.ps-panel:hover .ps-panel__body,
.ps-panel:focus-visible .ps-panel__body {
  opacity: 1;
  filter: blur(0);
}

.ps-panel__desc {
  font-size: 16.5px;
  line-height: 1.6;
  color: rgb(255 240 235 / 92%);
}

.ps-panel__more {
  padding: 9px 18px;
  border: 1px solid rgb(255 235 228 / 45%);
  border-radius: 999px;
  font-size: 12.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cream);
}

.ps-panel__more .arr {
  display: inline-block;
  transition: transform 0.3s var(--ease);
}

.ps-panel:hover .ps-panel__more .arr,
.ps-panel:focus-visible .ps-panel__more .arr {
  transform: translateX(4px);
}

.ps-panel:hover .ps-panel__name,
.ps-panel:focus-visible .ps-panel__name {
  color: var(--cream);
}

.ps-note {
  margin-top: 34px;
  text-align: center;
  font-size: 13.5px;
  color: var(--muted);
}

.ps-note a {
  color: var(--red);
  text-decoration: none;
}

.ps-note a:hover {
  text-decoration: underline;
}

/* No hover below tablet width, so the slab turns vertical instead: the same
   single rounded slab, but the panels become full-width bands with slanted
   horizontal dividers (skewY in place of skewX) and everything visible.
   Alternating bands carry a faint tint so the stack reads as one designed
   object rather than repeated cards. The red-glass layer is dropped so taps
   don't flash it on the way to the product page. */
@media (max-width: 1080px) {
  .ps-row {
    display: block;
    height: auto;
    margin-top: 44px;
  }

  .ps-panel,
  .ps-panel:first-child,
  .ps-panel:last-child {
    margin: 0;
    transform: skewY(-3deg);
    background: var(--card);
    border-left: 0;
    border-top: 1px solid var(--line);
  }

  /* the vertical bleed, same job as the horizontal one on desktop */
  .ps-panel:first-child {
    margin-top: -34px;
    border-top: 0;
  }

  .ps-panel:last-child {
    margin-bottom: -34px;
  }

  .ps-panel:nth-child(even) {
    background: linear-gradient(
      100deg,
      rgb(244 234 228 / 70%) 0%,
      rgb(216 179 178 / 24%) 100%
    );
  }

  .ps-panel::before {
    display: none;
  }

  .ps-panel__in,
  .ps-panel:first-child .ps-panel__in,
  .ps-panel:last-child .ps-panel__in {
    align-items: flex-start;
    gap: 2px;
    height: auto;
    padding: 40px 26px;
    transform: skewY(3deg);
    text-align: left;
  }

  /* the slant eats more vertical room at the bleeding ends */
  .ps-panel:first-child .ps-panel__in {
    padding-top: 66px;
  }

  .ps-panel:last-child .ps-panel__in {
    padding-bottom: 66px;
  }

  .ps-panel__in::before {
    display: none;
  }

  .ps-panel__name {
    min-height: 0;
    font-size: 20px;
  }

  .ps-panel__reveal {
    /* flex: none, not just height auto — the desktop slot's flex: 1 1 0
       has basis 0, and in these auto-height bands that collapsed the slot
       to zero, shearing the descriptions under the next band's slanted
       edge. Sized by content here, the bands always fit their text. */
    flex: none;
    height: auto;
  }

  .ps-panel__body {
    align-items: flex-start;
    gap: 14px;
    padding-top: 8px;
    opacity: 1;
    filter: none; /* see .reveal.in — never leave an idle blur() layer on */
    will-change: auto; /* no hover here, so no layer to keep warm */
  }

  .ps-panel__desc {
    font-size: 15.5px;
    color: var(--muted);
  }

  .ps-panel__more {
    border-color: rgb(140 25 28 / 30%);
    color: var(--red);
  }

  .ps-panel:hover .ps-panel__name {
    color: var(--red);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ps-panel::before,
  .ps-panel__body,
  .ps-panel__name {
    transition: none;
  }
}

/* Screenshot slides fill their frame edge to edge, so the frame reads as the
   application window rather than a picture sitting on a card. */
.frame--shot picture,
.frame--shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* The captures are 1445px wide for a 722px-wide slice of the application (they
   are 2x retina), so 720px would render the app at exactly 1:1. That proved
   too small to read comfortably on a wide screen, and 1:1 is not actually the
   ceiling: at 940px the browser upscales 1.30x on a 2x display, which is
   indistinguishable from a Lanczos-prescaled file at the same size (compared
   side by side at true physical size — the prescaled version is marginally
   crisper on digit edges and costs 500KB extra, so it is not worth shipping).
   Past about 1.4x the softening does become visible, so 940px is the point
   where the type is largest without looking resampled. */
.pv-slider {
  max-width: 940px;
  margin: 0 auto;
}

/* With the slide down to ~640px tall, the section's own spacing was the rest
   of the problem: it ran to 1310px, so the slide and its controls could not
   share a laptop viewport. Tightening the wash padding and the gap under the
   heading brings the frame, caption, dots and arrows inside one screen. */
.preview .frame-wrap {
  margin-top: 36px;
}

/* Room for the frame's FULL cast shadow, sideways included: the ambient
   layer reaches ~55px below and ~50px beside the box, and clipping it at
   the window edge sheared the smudge into hard lines at the bottom
   corners. The negative margins give all the space back, and the
   inactive slides drop their shadows so the neighbors' smudges can't
   drift into the widened window while the track slides. */
.preview .pv-window {
  padding: 24px 64px 96px;
  margin: -24px -64px -96px;
}

/* On phones the 64px side bleed overshoots the viewport (the wrap only pads
   22px), and that overshoot was the homepage's horizontal scroll on iOS.
   Match the bleed to the wrap padding so the window ends at the screen edge. */
@media (max-width: 760px) {
  .preview .pv-window {
    padding-left: 22px;
    padding-right: 22px;
    margin-left: -22px;
    margin-right: -22px;
  }
}

.preview .pv-slide .frame {
  transition: box-shadow 0.45s var(--ease);
}

.preview .pv-slide:not(.is-active) .frame {
  box-shadow: none;
}

.preview .pv-nav,
.preview .pv-note {
  margin-top: 20px;
}

/* Sourcing note under the slideshow. */
.pv-note {
  max-width: 640px;
  margin: 26px auto 0;
  text-align: center;
  font-size: 13.5px;
  color: var(--muted);
}

.pv-note a {
  color: var(--red);
}

/* Large displays: the hero scales with the screen instead of pooling margin
   around a fixed row — the row tracks the viewport (94vw, capped) and the
   headline steps up with it. */
@media (min-width: 1560px) {
  .hero .wrap {
    max-width: min(1720px, 94vw);
  }

  .hero-grid {
    gap: 56px;
  }

  .hero h1 {
    font-size: clamp(72px, 4.6vw, 86px);
  }

  .hero .lede {
    font-size: 19px;
  }

  .deck__lede {
    font-size: 17px;
  }
}

/* Tall displays: the readout column stretches to use the extra height —
   deeper sparks and roomier metric tiles — so a 100vh hero fills the
   viewport instead of centering a short card in empty paper. Only for
   browsers WITHOUT `zoom`: under the uniform hero zoom (end of file) the
   scale itself spends the extra height, and letting this block enlarge the
   internals would give tall monitors a different composition again. */
@media (min-width: 1181px) and (min-height: 950px) {
  @supports not (zoom: 1) {
    .deck .metric__spark {
      height: 84px;
    }

    .metric {
      padding: 14px 16px;
    }

    .viz-readout {
      gap: 16px;
    }

    .deck__panel {
      padding: 26px 26px 20px;
    }

    .deck .viz-stage svg {
      max-height: 470px;
    }
  }
}

/* ==========================================================================
   Deck vision — larger one-at-a-time outputs (RiskModel), report-style
   hazard maps with a left flow rail (SiteHazards), and a building-inventory
   risk map (Portfolio/Batch).
   ========================================================================== */

/* ---- RiskModel: joints on every intersection, damage light-to-dark ---- */
.jt {
  fill: var(--card);
  stroke: var(--dusty);
  stroke-width: 1.1;
  transition:
    fill 0.35s var(--ease),
    stroke 0.35s var(--ease);
}

.jt.is-hit[data-sev="1"] {
  fill: var(--dusty);
  stroke: var(--clay);
}

/* Damage arrives with the shaking and the frame rests marked but unresolved;
   the damage phase then gives every joint its true severity. */
.jt.is-hit.is-light {
  fill: var(--dusty);
  stroke: var(--clay);
}

.jt.is-hit[data-sev="2"] {
  fill: var(--clay);
  stroke: var(--red);
}

.jt.is-hit[data-sev="3"] {
  fill: var(--red);
  stroke: var(--wine);
}

/* ---- RiskModel: the output rail ---- */
.out-rail {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}

.out-stage {
  position: relative;
  min-height: 472px;
}

.out {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--cream);
  padding: 14px 16px 12px;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.4s var(--ease),
    visibility 0.4s;
}

.out.is-on {
  opacity: 1;
  visibility: visible;
}

/* One header style for every figure on every card — a red-glass chip in the
   deck's own glass language: a translucent red-tinted plate with a bright
   inner rim and a pinned dot, so each title reads as anchored to the figure
   it names rather than floating beside it. Rounded-rect, not a pill, so a
   long title that wraps on a phone still looks like a plate. The titles are
   all HTML (.viz-title above each stage drawing, .out__label above each
   output, .hz-step heading each rail section) and share this one rule, so
   they are pixel-identical everywhere. */
.out__label,
.viz-title,
.hz-step {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  width: fit-content;
  gap: 10px;
  padding: 8px 14px;
  border: 1px solid rgb(140 25 28 / 16%);
  border-radius: 9px;
  background: linear-gradient(
    150deg,
    rgb(140 25 28 / 10%) 0%,
    rgb(140 25 28 / 5%) 55%,
    rgb(140 25 28 / 8%) 100%
  );
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 65%),
    0 1px 2px rgb(90 20 20 / 8%);
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--wine);
}

/* On RiskModel and Portfolio the chip centers over the figure it names —
   the building stage, each output chart, the inventory map — so title and
   figure share one axis. SiteHazards and Consulting keep theirs left: those
   are rail-section headings, and a centered heading over a left-aligned
   list would float. */
.deck__card:nth-of-type(1) .viz-title,
.deck__card:nth-of-type(1) .out__label,
.deck__card:nth-of-type(3) .viz-title {
  align-self: center;
}

/* the pin: a dot with a soft halo, anchoring the chip to its figure */
.out__label::before,
.viz-title::before,
.hz-step::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 0 3px rgb(140 25 28 / 14%);
}

.out__label {
  margin: 0 0 12px;
}

.viz-title {
  margin: 0 0 14px;
}

.out__chart {
  /* a fixed box, whatever the drawing's aspect: every output's chart area,
     axis line, and the takeaway row below it sit at identical positions, so
     cycling outputs swaps content without anything shifting */
  width: 100%;
  height: 300px;
  margin: auto 0;
}

/* The output's call to action: the chart shows the metric, this says in one
   line what it is and offers the way in. It replaces the stat readout, which
   at this size was more numbers than a first-time reader needs.

   The rail is aria-hidden decoration, so these links are removed from the tab
   order — the keyboard route to the same page is the "And more" chip, which
   is a real link in the flow. */
.out__cta {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 64px;
  margin: 12px 0 0;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  text-decoration: none;
  transition:
    border-color 0.25s var(--ease),
    background 0.25s var(--ease),
    transform 0.25s var(--ease);
}

.out__cta:hover {
  border-color: var(--red);
  background: rgb(140 25 28 / 3%);
  transform: translateY(-2px);
}

.out__cta-copy {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.out__cta-copy b {
  font-family: var(--font-head);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--ink);
}

.out__cta-copy > span {
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
}

.out__cta .arr {
  margin-left: auto;
  flex: none;
  color: var(--red);
  font-size: 17px;
  transition: transform 0.25s var(--ease);
}

.out__cta:hover .arr {
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .out__cta,
  .out__cta:hover,
  .out__cta:hover .arr {
    transform: none;
  }
}

/* The takeaway lives in its own card below the chart — the chart shows where
   each number happens, this box says what every one of them means. Each
   metric is its own highlighted stat (value in red, meaning right under it),
   so no number hides small inside a sentence. */
.out__take {
  display: flex;
  align-items: stretch;
  min-height: 64px;
  margin: 12px 0 0;
  padding: 10px 4px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
}

/* Top-aligned, not centered: labels wrap to different line counts, and the
   big values must share one baseline row across the stats. */
.out__stat {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 4px;
  padding: 2px 12px 0;
}

.out__stat + .out__stat {
  border-left: 1px solid var(--line);
}

.out__stat b {
  font-family: var(--font-head);
  font-size: 21px;
  font-weight: 700;
  line-height: 1;
  color: var(--red);
}

.out__stat b i {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  margin-left: 2px;
}

.out__stat > span {
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--muted);
}

.rm-pct {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  fill: var(--muted);
  opacity: 0;
}

/* On-chart metric callouts (the PGA at the wave's peak, the story-1 drift,
   a value atop every column) and the small name each curve carries. */
.rm-callout {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  fill: var(--red);
  paint-order: stroke;
  stroke: var(--cream);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* Curve names read over the lines they label: larger and bolder than a
   plain annotation, with a chart-background halo (paint-order stroke) so a
   label stays legible even where it crosses a curve or gridline. */
.rm-line-label {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  paint-order: stroke;
  stroke: var(--cream);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* ---- SiteHazards: flow rail + map ---- */
.deck .viz-body.viz-body--hz {
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.34fr);
  gap: 26px;
  align-items: center;
}

.hz-flow {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.hz-step {
  margin: 0 0 9px;
}

.hz-field {
  display: flex;
  align-items: center;
  min-height: 40px;
  font-family: var(--font-mono);
  font-size: 14px;
  color: var(--red);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 8px 10px;
  margin: 0 0 18px;
}

.hz-caret {
  width: 1.5px;
  height: 16px;
  flex: none;
  background: var(--red);
  margin-left: 2px;
  opacity: 0;
}

.hz-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
  margin: 0 0 18px;
}

.hz-item {
  position: relative;
  font-family: var(--font-head);
  font-size: 16px;
  font-weight: 500;
  color: var(--muted);
  text-align: left;
  text-decoration: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 9px 4px 9px 22px;
  cursor: pointer;
  transition:
    color 0.25s var(--ease),
    background 0.25s var(--ease);
}

.hz-item::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid var(--dusty);
  transform: translateY(-50%);
  transition:
    background 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.hz-item:hover {
  color: var(--ink);
  background: rgb(140 25 28 / 3%);
}

.hz-item.is-on {
  color: var(--red);
  font-weight: 600;
}

.hz-item.is-on::before {
  background: var(--red);
  border-color: var(--red);
}

.hz-item--out {
  color: var(--red);
  font-weight: 600;
}

.hz-item--out::before {
  border-color: var(--red);
  border-style: solid;
}

.hz-item--more {
  color: var(--red);
  font-size: 13.5px;
  padding-left: 4px;
}

.hz-item--more::before {
  display: none;
}

.hz-report {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--cream);
  padding: 12px 14px;
}

.hz-report svg {
  width: 30px;
  height: auto;
  flex: none;
}

.hz-report span {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}

.hz-report.st-dim {
  opacity: 0.55;
}

.hz-report.st-dim.on {
  opacity: 1;
}

.hz-report b {
  display: block;
  font-family: var(--font-head);
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
}

.hz-map {
  min-width: 0;
}

/* The map stage holds the report's actual figures — same address, same zoom,
   legend and site star baked in — stacked and crossfaded by home-v3.js. Before
   the screening starts (and between loops) every layer is hidden and the plate
   sits empty: just the figures' own water gray, so no map shows until one is
   meant to. */
.hz-stage {
  position: relative;
  aspect-ratio: 1516 / 1048;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  background: #e9e9e9; /* the figures' own water gray */
}

.hz-stage > img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* The seventh step is the deliverable itself: the report's real cover,
   centred on the plate like a document on a desk rather than stretched to
   fill a landscape frame. */
.hz-cover {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--cream);
}

.hz-cover img {
  width: auto;
  height: 88%;
  max-width: 90%;
  object-fit: contain;
  background: #fff;
  border: 1px solid var(--line);
  box-shadow:
    0 2px 4px rgb(36 17 16 / 10%),
    0 14px 30px rgb(36 17 16 / 18%);
}

.hz-stage .hz-layer {
  opacity: 0;
}

.hz-cap {
  margin: 10px 2px 0;
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  min-height: 18px;
}

.hz-layer {
  transition: opacity 0.5s var(--ease);
}

/* Portfolio leads with the map: with the exceedance chart gone the map IS
   the output, so it takes the wider column and drops the 400px cap the frame
   drawings need. Specificity beats the shared .deck .viz-body columns, the
   same way the SiteHazards variant does. */
.deck .viz-body.viz-body--pf {
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: 24px;
}

.viz-body--pf .viz-stage svg {
  max-width: none;
  max-height: 400px;
}

/* Portfolio's three outputs, said plainly. The readouts they replace showed
   a count, a rank and an event-set name — true, but at a glance they read as
   decoration. Each CTA lights as the run reaches it and matches its chip. */
.viz-readout--cta {
  /* the deck centres body columns; this one takes the full height so its
     cards can share it rather than floating in the middle of dead space */
  align-self: stretch;
  justify-content: stretch;
  gap: 14px;
}

/* The three cards share the column's height rather than stacking at their
   natural size and leaving dead space below — the panels are floored to a
   common height, so that space existed whether or not it was used. */
.viz-readout--cta .pf-cta {
  flex: 1 1 auto;
}

.pf-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  text-decoration: none;
  transition:
    border-color 0.25s var(--ease),
    background 0.25s var(--ease),
    transform 0.25s var(--ease),
    opacity 0.45s var(--ease);
}

.pf-cta:hover {
  border-color: var(--red);
  background: rgb(140 25 28 / 3%);
  transform: translateY(-2px);
}

.pf-cta__copy {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.pf-cta__copy b {
  font-family: var(--font-head);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--ink);
}

.pf-cta__copy > span {
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
}

.pf-cta .arr {
  margin-left: auto;
  flex: none;
  color: var(--red);
  font-size: 17px;
  transition: transform 0.25s var(--ease);
}

.pf-cta:hover .arr {
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .pf-cta,
  .pf-cta:hover,
  .pf-cta:hover .arr {
    transform: none;
  }
}

/* ---- Portfolio/Batch: the inventory risk map ---- */
.pfb {
  fill: var(--card);
  stroke: var(--dusty);
  stroke-width: 1;
  opacity: 0;
  transition:
    opacity 0.4s var(--ease),
    fill 0.45s var(--ease),
    stroke 0.45s var(--ease);
}

.pfb.is-in {
  opacity: 1;
}

/* The product's own loss scale, matching the SP3-Portfolio map pins:
   green < 5%, yellow 5–10%, orange 10–20%, red 20–40%, dark red > 40%. */
.pfb.is-sev[data-sev="1"] {
  fill: #43a047;
  stroke: #2e7d32;
}

.pfb.is-sev[data-sev="2"] {
  fill: #f2cc0c;
  stroke: #c7a400;
}

.pfb.is-sev[data-sev="3"] {
  fill: #ef8e1d;
  stroke: #c26f0a;
}

.pfb.is-sev[data-sev="4"] {
  fill: #e03a2f;
  stroke: #b02318;
}

.pfb.is-sev[data-sev="5"] {
  fill: #8c191c;
  stroke: #6d1114;
}

/* the legend row under the map, the product's own bins */
.pf-leg {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.03em;
  fill: var(--muted);
}

.pfb-ring {
  fill: none;
  stroke: var(--red);
  stroke-width: 2;
}

/* RiskModel's phase chips are buttons: tap to jump the pipeline there. */
button.chip {
  background: none;
  cursor: pointer;
}

button.chip:hover {
  border-color: var(--clay);
  color: var(--ink);
}

button.chip.on:hover {
  color: var(--white);
}

.chip--link {
  color: var(--red);
  text-decoration: none;
  font-weight: 600;
}

.chip--link:hover {
  border-color: var(--red);
}

@media (max-width: 620px) {
  .deck .viz-body.viz-body--hz,
  .deck .viz-body.viz-body--pf {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }

  /* At full width the map is the output again, and the CTAs stop sharing a
     narrow column's height — they sit at their natural size and read. */
  .viz-body--pf .viz-stage svg {
    max-height: none;
  }

  .viz-readout--cta {
    align-self: auto;
  }

  .viz-readout--cta .pf-cta {
    flex: 0 0 auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .jt,
  .hz-layer,
  .hz-item,
  .hz-item::before,
  .pfb,
  .out {
    transition: none;
  }
}

/* ============ get started: the three introduction videos ============
   Between the platform slideshow and the product strip. The header sits
   over a seismic trace that draws itself in on reveal, and the three video
   cards enter with a damped ground-motion settle: drop, oscillate like a
   responding structure, come to rest. Both are pure CSS keyed off the
   reveal observer's .in on the section (.gs-band). */
.get-started {
  padding: 96px 0 24px;
}

.gs-trace {
  max-width: 1160px;
  margin: 30px auto 40px;
}

.gs-trace svg {
  display: block;
  width: 100%;
  height: 60px;
}

.gs-trace path {
  fill: none;
  stroke: var(--red);
  stroke-width: 2;
  opacity: 0.75;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1.6s var(--ease) 0.15s;
}

.gs-band.in .gs-trace path {
  stroke-dashoffset: 0;
}

.gs-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 26px;
}

.gs-card {
  margin: 0;
  opacity: 0;
}

.gs-band.in .gs-card {
  animation: gs-settle 1.15s var(--ease) forwards;
}

.gs-band.in .gs-card:nth-child(2) {
  animation-delay: 0.14s;
}

.gs-band.in .gs-card:nth-child(3) {
  animation-delay: 0.28s;
}

@keyframes gs-settle {
  0% {
    opacity: 0;
    transform: translateY(30px);
  }

  38% {
    opacity: 1;
    transform: translateY(-4px) rotate(0.5deg);
  }

  55% {
    transform: translateY(2px) rotate(-0.35deg);
  }

  72% {
    transform: translateY(-1px) rotate(0.2deg);
  }

  86% {
    transform: translateY(0.5px) rotate(-0.1deg);
  }

  100% {
    opacity: 1;
    transform: none;
  }
}

/* the homepage loads home-v3.css only, so the elevated-player treatment
   from components.css is restated here for these three embeds */
.gs-card .video-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--card);
  box-shadow:
    0 1px 2px rgb(36 17 16 / 10%),
    0 12px 24px -12px rgb(36 17 16 / 18%),
    0 32px 64px -28px rgb(36 17 16 / 30%);
  transition:
    transform 0.3s var(--ease),
    box-shadow 0.3s var(--ease);
}

.gs-card .video-embed:hover,
.gs-card .video-embed:focus-within {
  transform: translateY(-4px);
  box-shadow:
    0 2px 4px rgb(36 17 16 / 10%),
    0 20px 36px -16px rgb(36 17 16 / 24%),
    0 44px 80px -32px rgb(36 17 16 / 38%);
}

.gs-card .video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.gs-cap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 14px 4px 0;
}

.gs-cap__title {
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink);
}

.gs-cap__time {
  flex: none;
  padding: 5px 12px;
  border: 1px solid var(--dusty);
  border-radius: 999px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--red);
}

@media (max-width: 1080px) {
  .gs-grid {
    grid-template-columns: 1fr;
    gap: 30px;
    max-width: 640px;
    margin: 0 auto;
  }

  .get-started {
    padding-top: 72px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gs-card,
  .gs-band.in .gs-card {
    opacity: 1;
    animation: none;
  }

  .gs-trace path {
    stroke-dashoffset: 0;
    transition: none;
  }
}

/* ============ hero at desktop: one composition, uniformly scaled ============
   Every desktop and laptop sees the SAME hero composition: the wrap is laid
   out once at a fixed 1720px reference width, and home-v3.js sets a single
   `zoom` on it — min(width fit, height fit) — so the whole hero scales as
   one object to any monitor. This replaces the old short-laptop tiers,
   whose hand-tuned compositions left gaps (e.g. ~1920x950 windows) where
   the full layout clipped. Nothing inside the hero ever changes size
   relative to anything else, so the aspect-locked drawings cannot
   letterbox — the failure that sank the earlier per-element attempt.
   Guarded by @supports: a browser without `zoom` keeps the fluid 1400px
   wrap it has today. Phones and tablets (<=1180px) are untouched. */
@media (min-width: 1181px) {
  @supports (zoom: 1) {
    .hero .wrap {
      width: 1720px;
      max-width: none;
    }

    /* Reference-fixed: the vw clamp tracks the real viewport and would then
       be multiplied by the zoom, drifting the composition per monitor. */
    .hero h1 {
      font-size: 72px;
    }
  }
}

/* ============ RiskModel on phones: one act at a time ============
   The stacked phone layout made readers scroll between the building
   animation and its readouts. Instead the two share one grid slot: the
   building plays the response and damage acts, then hands the slot to the
   readouts for the rest of the cycle. home-v3.js toggles
   .viz-body--outlive a beat after the joints resolve to their severities
   (mid-Damage, while the loss bars are still growing), on any chip tap,
   and back off when the loop restarts. The class has no styles above 620px,
   so tablet and desktop keep the side-by-side layout unchanged. Lives at
   the END of the file so it wins over the base stage rules. */
@media (max-width: 620px) {
  .deck__card:nth-of-type(1) .viz-stage,
  .deck__card:nth-of-type(1) .out-rail {
    grid-area: 1 / 1;
  }

  .deck__card:nth-of-type(1) .viz-stage {
    opacity: 1;
    visibility: visible;
    transition:
      opacity 0.45s var(--ease),
      visibility 0s;
  }

  .deck__card:nth-of-type(1) .out-rail {
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 0.45s var(--ease),
      visibility 0s 0.45s;
  }

  .deck__card:nth-of-type(1) .viz-body--outlive .viz-stage {
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 0.45s var(--ease),
      visibility 0s 0.45s;
  }

  .deck__card:nth-of-type(1) .viz-body--outlive .out-rail {
    opacity: 1;
    visibility: visible;
    transition:
      opacity 0.45s var(--ease) 0.1s,
      visibility 0s;
  }
}

@media (max-width: 620px) and (prefers-reduced-motion: reduce) {
  .deck__card:nth-of-type(1) .viz-stage,
  .deck__card:nth-of-type(1) .out-rail {
    transition: none;
  }
}
