/* ============================================================================
   ZOSIMO MARKETING — pattern + block layer
   Cairn · 2026-08-29

   Ordered: chrome → panel shell → shared blocks → per-pattern.
   A block appears ONCE. If two patterns need it, they both use it; neither
   owns a copy. That rule is what makes this handable to Grout.
   ========================================================================== */

/* ===== chrome/header-fixed ============================================== */
.hdr{
  position:fixed; inset:0 0 auto 0; height:var(--hdr-h); z-index:100;
  display:flex; align-items:center; gap:1.5rem;
  padding-inline:clamp(1rem, .5rem + 2vw, 2.5rem);
  background:rgba(0,0,0,.60);
  backdrop-filter:blur(7.5px); -webkit-backdrop-filter:blur(7.5px);
  border-bottom:1px solid rgba(255,255,255,.30);
}
.wordmark{ display:flex; align-items:center; line-height:0 }
.wordmark svg{ height:30px; width:auto }

/* THE DOT. The page's one piece of continuous state: it follows the accent of
   whichever panel owns viewport centre. Driven from JS by setting --accent on
   the header element, so the transition is a colour interpolation, not a swap. */
#logo-dot{ fill:var(--accent); transition:fill 420ms var(--ease) }
@media (prefers-reduced-motion:reduce){ #logo-dot{ transition:none } }

/* Truly centred on the FRAME, not centred in the space left over between the
   wordmark and the CTA — those two have different widths, so `margin:auto`
   puts the nav off-centre by half their difference. Absolute + translate is
   the only version that survives a longer CTA label. */
.hdr nav{
  position:absolute; left:50%; transform:translateX(-50%);
  display:flex; gap:30px;
}
.hdr > .btn{ margin-left:auto }
.hdr nav a{
  font-family:var(--mono); font-size:var(--fs-control); line-height:25px;
  letter-spacing:var(--ls-control); text-transform:uppercase;
  color:var(--ink-nav); text-decoration:none;
  border-bottom:1px solid transparent; padding-bottom:1px;
  transition:color .2s, border-color .2s;
}
.hdr nav a:hover, .hdr nav a:focus-visible{ color:var(--ink); border-bottom-color:var(--accent) }
@media (max-width:820px){ .hdr nav{ display:none } }

/* ===== control (button) ================================================= */
.btn{
  display:inline-flex; align-items:center; justify-content:center;
  font-family:var(--mono); font-size:var(--fs-control); line-height:1.74;
  letter-spacing:var(--ls-control); text-transform:uppercase;
  padding:var(--ctl-pad-y) var(--ctl-pad-x);
  border:1px solid var(--ctl-stroke); border-radius:var(--ctl-radius);
  color:#fff; text-decoration:none; cursor:pointer;
  background:rgba(255,255,255,.10);
  transition:background 180ms var(--ease), border-color 180ms var(--ease),
             box-shadow 180ms var(--ease), transform 180ms var(--ease);
}
.btn:where(:hover, .is-hover){ box-shadow:0 2px 18px rgba(0,0,0,.35) }
.btn:active{ transform:translateY(1px) }
@media (prefers-reduced-motion:reduce){ .btn{ transition:none } .btn:active{ transform:none } }
/* HOVER — Lisa, 2026-08-29. The $deficit is closed: the accent gets MORE
   present on hover, not less. Primary 50% -> 75%, secondary 10% -> 25%.
   `.is-hover` is not a duplicate rule, it is how the harness reaches a state
   a screenshot cannot: :hover needs a live pointer, and an unmeasured state
   is an unpublished one. Any state we define, we can measure. */
.btn:where(:hover, .is-hover){ background:rgba(255,255,255,.25) }
.btn--primary{ background:color-mix(in srgb, var(--accent) 50%, transparent) }
.btn--primary:where(:hover, .is-hover){ background:color-mix(in srgb, var(--accent) 75%, transparent) }
.btn--solid{ background:var(--accent-fill); border-color:transparent }
.btn--solid:where(:hover, .is-hover){ background:color-mix(in srgb, var(--accent-fill) 100%, #fff 12%) }
.btn--block{ width:100% }

/* ===== panel shell ====================================================== */
.shell{ position:relative }
.panel{
  position:relative;
  scroll-margin-top:var(--hdr-h);
  min-height:100svh;
  display:flex; align-items:center;
  padding-block:calc(var(--hdr-h) + 3rem) 4rem;
  isolation:isolate;
}
.panel[data-fit="false"]{ min-height:auto }
.panel > .ground{ position:absolute; inset:0; z-index:0; overflow:hidden }
/* CONTENT width is --col-max; the gutter is outside it. At 1440 that is
   120 + 1200 + 120, which is the Figma frame exactly. Getting this wrong
   narrows every column on the page and makes every headline wrap early. */
.panel > .body{
  position:relative; z-index:3;
  width:100%; max-width:calc(var(--col-max) + 2 * var(--gutter));
  margin-inline:auto; padding-inline:var(--gutter);
}

/* ground kinds */
.ground--void{ background:var(--void) }
.ground--gradient{ background:linear-gradient(var(--g-angle,160deg), var(--g-from), var(--g-to)) }
.ground video, .ground > img.plate:not(.plate--edge){ width:100%; height:100%; object-fit:cover }
/* The photo hugs the RIGHT EDGE and bleeds off it; the phone mock overlaps
   its left side, exactly as the comp has it. The mock is z-index 3 (inside
   .body) so it sits over the plate without any extra stacking rules. */
.ground > img.plate--edge{
  position:absolute; top:0; right:0; bottom:0; left:auto;
  height:100%; width:min(38vw,493px); max-width:none;
  object-fit:cover; object-position:center;
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, #000 22%);
          mask-image:linear-gradient(90deg, transparent 0%, #000 22%);
}
@media (max-width:900px){ .ground .plate--edge{ width:100%; opacity:.28 } }
.split[data-media-side="end"] .split__media{ justify-self:end; width:100%; max-width:340px; margin-right:clamp(0px,4vw,60px) }

.plate-slot{
  position:absolute; inset:0 0 0 50%; width:50%;
  display:flex; align-items:center; justify-content:center; text-align:center;
  background:rgba(0,0,0,.22); border-left:1px solid rgba(255,255,255,.12);
  font-family:var(--mono); font-size:.75rem; letter-spacing:.10em; text-transform:uppercase;
  color:rgba(255,255,255,.55); padding:1rem;
}

/* legibility-panel — the copy-protecting gradient. Its BOX is the section
   (inset:0); its GEOMETRY is derived from the copy it protects, at runtime.
   Sibling behind .body, never wrapped around it. */
/* TWO derived layers, not one. A single ellipse sized from the whole copy
   union (heading + lead + actions + cue) is broad and shallow exactly where
   the display sits, which is where the biggest type meets the brightest
   footage. The tight layer is derived from the HEADING extent alone and
   concentrates its alpha there; the wide layer still protects everything
   else. Both are clamped so neither can reach the section crop.
   Fixing the anatomy, not the numbers. */
.legibility{ position:absolute; inset:0; z-index:2; pointer-events:none }

/* ambient field — layer 1.5: above the ground, BELOW the legibility panel.
   L2 of the ambient law. Never between copy and its own protection. */
/* NOTHING LOAD-BEARING FOR THE AMBIENT LAYER LIVES HERE. position, inset,
   size, z-index, pointer-events and the `region` inset are all set by
   js/ambient.js on the canvas itself — see the L5 note in that file. A law
   that depends on a consumer remembering a stylesheet is not a law. This rule
   is a visual backstop only. */
canvas.ambient{ display:block }

/* ===== motion: envelope (layer 1) ======================================= */
.shell{ opacity:var(--env,1); transition:opacity .18s linear; will-change:opacity }
.shell > .panel{ transform:translateY(calc((1 - var(--env,1)) * 18px)); transition:transform .18s linear }

/* ===== motion: build (layer 2) ========================================== */
/* CONTENT-VISIBILITY FAIL-SAFE. `.b{opacity:0}` is only honest while something
   is GUARANTEED to add `.is-built`. Unconditionally it is a blank page for any
   visitor whose JavaScript is off, blocked or broken — on a site sold on ADA
   compliance that is the worst failure available, and it was passing L2 on the
   letter while failing it on the spirit.

   So the hidden state is gated on scripting being ACTUALLY AVAILABLE:
     scripting: enabled  the block matches; the page builds in as designed.
     scripting: none     JS is off; the block does not match, content visible.
     feature unknown     the whole @media is invalid and the browser drops it,
                         so the declaration never lands and content is visible.
   Every failure direction is "content visible, entrance animation does not
   play", which is the only acceptable one. That third case is why this is a
   media feature and not a class: a browser too old to know `scripting` fails
   SAFE by construction. (Baseline since Dec 2023 — Firefox 113, Safari 17,
   Chrome/Edge 120.)

   The matching guard for "scripting IS enabled and the script threw part-way"
   is rescue()/the watchdog in js/page.js. Neither guard covers js/page.js
   failing to LOAD at all — that needs an inline head script emitted by
   build.js, which is filed, not fixed here. */
@media (scripting: enabled){
  .b{ opacity:0; transform:translateY(var(--build-rise)); }
}
.is-built .b{
  opacity:1; transform:none;
  transition:opacity var(--build-dur) var(--ease), transform var(--build-dur) var(--ease);
  transition-delay:calc(var(--i,0) * var(--build-step));
}

@media (prefers-reduced-motion:reduce){
  .shell{ opacity:1 !important }
  .shell > .panel{ transform:none !important }
  .b{ opacity:1 !important; transform:none !important; transition:none !important }
}

/* ===== shared blocks ==================================================== */

/* eyebrow */
.eyebrow{
  font-family:var(--mono); font-size:var(--fs-control); line-height:1.55;
  letter-spacing:var(--ls-eyebrow); text-transform:uppercase;
  color:var(--ink-eyebrow);
}
/* MEASURED, not a preference. rgba(255,255,255,.78) clears 4.5:1 comfortably
   on the void panels and misses it on every ground that carries its own
   colour — 4.07 on the sms gradient, 4.46 on the hero video at 390. The ink
   is the lever; the eyebrow goes to full white wherever the ground is not
   the void. */
[data-ground="gradient"] .eyebrow,
[data-ground="gradient-photo"] .eyebrow,
[data-ground="video"] .eyebrow{ color:#fff }

/* display heading, with the accent-emphasis span */
.display{
  font-size:var(--fs-display); line-height:var(--lh-display);
  color:var(--ink); text-shadow:var(--sh-heading);
  margin-top:20px; max-width:34ch; text-wrap:balance;
}
.display em{ font-style:normal; color:var(--accent) }
.display em[data-accent-name="sun"]{ color:var(--sun) }
.display em[data-accent-name="mint"]{ color:var(--mint) }
.display em[data-accent-name="sky"]{ color:var(--sky) }
.display em[data-accent-name="pink"]{ color:var(--pink) }

.lead{
  font-size:var(--fs-lead); line-height:var(--lh-lead);
  color:var(--ink-lead); text-shadow:var(--sh-lead);
  margin-top:20px; max-width:62ch;
}

.actions{ display:flex; flex-wrap:wrap; gap:15px; margin-top:2.5rem }

/* surface/card — the single most reused object on the page */
.card{
  background:
    linear-gradient(var(--card-veil), var(--card-veil)),
    linear-gradient(var(--card-ink), var(--card-ink));
  border:1px solid var(--card-edge);
  backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px);
  padding:var(--card-pad);
}
.card__title{
  font-size:var(--fs-card-title); line-height:1.2;
  color:var(--ink); text-shadow:var(--sh-heading);
}
.card__helper{ font-size:var(--fs-helper); line-height:1.5; color:var(--ink-muted) }

/* field-row — the proposed block. Axis Fields = 1 | 2. Cap 2. */
/* The row bottom-aligns, but `align-items:flex-end` alone does not make the
   CONTROLS line up — a field is label + input and the button is just a button,
   so the boxes end at different heights only if something stretches. Stretch
   the items and push the control to the bottom of each: now the input and the
   button share a baseline whatever the label does. */
.field-row{ display:flex; gap:20px; align-items:stretch; margin-top:10px }
.field{ flex:1 1 0; min-width:0; display:flex; flex-direction:column; gap:10px; justify-content:flex-end }
.field-row > .btn{ align-self:flex-end }
.field label{
  font-family:var(--mono); font-size:var(--fs-control); line-height:1.55;
  letter-spacing:var(--ls-label); text-transform:uppercase;
  color:rgba(255,255,255,.80);
}
.field input{
  width:100%; font-family:var(--mono); font-size:var(--fs-control); line-height:1.74;
  letter-spacing:var(--ls-control); text-transform:uppercase;
  background:var(--field-fill); border:1px solid var(--field-stroke);
  border-radius:var(--ctl-radius); color:#fff;
  padding:var(--ctl-pad-y) 14px;
  transition:border-color 180ms var(--ease), background 180ms var(--ease);
}
/* ACTIVE FIELD — Lisa, 2026-08-29: stroke opacity to 65%.
   :focus-visible would drop the state for a mouse user clicking into the
   field, which is the commonest way anyone reaches it. Use :focus. The
   accent focus ring still comes from :focus-visible and is a separate thing —
   this is the field saying "you are in me", that one is the keyboard cue. */
.field input:where(:focus, .is-active){
  border-color:var(--field-stroke-active);
  background:rgba(255,255,255,.14);
  outline:none;
}
.field input:focus-visible{ outline:2px solid var(--accent); outline-offset:2px }
@media (prefers-reduced-motion:reduce){ .field input{ transition:none } }
.field input::placeholder{ color:rgba(255,255,255,.46) }
/* Tablet: 2 + 1 — the fields hold the row, the button drops full-width. */
@media (max-width:900px){
  .field-row{ flex-wrap:wrap }
  .field{ flex:1 1 calc(50% - 10px) }
  .field-row > .btn{ flex:1 0 100% }
}
@media (max-width:560px){ .field{ flex:1 0 100% } }

/* ===== hero/photo-immersive ============================================ */
.hero .display{ max-width:32ch }
.hero .lead{ max-width:52ch }
/* The cue is a link with a label sitting low in the frame, past the reach of
   a panel derived from the copy above it. It gets its own plate — the same
   rule the buttons on this hero already follow. A control on media carries
   its own background; it does not borrow one. */
.scroll-cue{
  display:inline-flex; align-items:center; gap:11px; margin-top:2.6rem;
  /* Lisa 2026-08-29: remove the plate, or take it to the lowest that clears
     4.5:1. MEASURED, once the derived coverage rule fires at tablet: the cue
     reads 6.47:1 with NO FILL on the worst frame at 810, and 6.89 / 10.44 at
     1440 / 390. So the fill comes off, and the border with it.
     The blur stays. With no fill it is invisible as a chip, but it flattens
     the specular highlights the cue was failing against in the first place —
     it is doing the legibility work that the box used to get credit for. */
  padding:.35rem .7rem .35rem .5rem; margin-left:-.5rem;
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  font-family:var(--mono); font-size:var(--fs-control);
  letter-spacing:var(--ls-eyebrow); text-transform:uppercase; color:var(--ink-eyebrow);
  text-decoration:none;
}
.scroll-cue i{ display:block; width:1px; height:26px; background:currentColor; opacity:.7 }
@media (prefers-reduced-motion:no-preference){
  .scroll-cue i{ animation:cue 2.4s var(--ease) infinite }
  @keyframes cue{ 0%,100%{ transform:scaleY(.4); transform-origin:top } 50%{ transform:scaleY(1); transform-origin:top } }
}

/* ===== content/signup ================================================== */
.signup .card{ margin-top:55px; padding:20px }
.signup__head{ display:flex; align-items:flex-end; justify-content:space-between; gap:2rem; padding-bottom:26px }
.signup__head .card__title{ flex:1 1 0 }
.signup__head .card__helper{ flex:0 1 auto; text-align:right }
@media (max-width:700px){
  .signup__head{ flex-direction:column; align-items:flex-start; gap:.6rem }
  .signup__head .card__helper{ text-align:left }
}

/* ===== content/split-media ============================================= */
.split .body{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(2rem,5vw,5.5rem); align-items:center }
.split[data-media-side="start"] .split__copy{ order:2 }
.split[data-media-side="start"] .split__media{ order:1 }
.split .display{ max-width:17ch }
.split .lead{ max-width:52ch }
@media (max-width:900px){
  .split .body{ grid-template-columns:1fr }
  .split[data-media-side="start"] .split__copy,
  .split[data-media-side="start"] .split__media{ order:0 }
}

/* ui-mock — ONE recipe for both mocks, read off Figma 317:18192 and 317:18250.
   They are the same object: a translucent frame with a soft ambient shadow,
   holding a near-white pane washed with the PANEL'S ACCENT from the bottom.
   The wash is var(--accent), not two hardcoded gradients — that is what makes
   this a block rather than two drawings that happen to rhyme. */
.mock{ position:relative }
.mock__frame{
  background:rgba(255,255,255,.10);
  border:1px solid rgba(255,255,255,.10);
  border-radius:6px;
  box-shadow:0 0 42.9px 0 rgba(0,0,0,.25);
  padding:10px;
  overflow:hidden;
}
/* The wash is NOT the panel accent. Checked against Figma: the menu panel
   runs sky as its accent and washes its mock MINT; the sms panel runs pink and
   washes SKY. I had inferred a relationship that isn't there — it is a per-mock
   decision, so it gets its own address and defaults to the accent only when
   nobody chooses. */
.mock__pane{
  position:relative;
  border:1px solid rgba(255,247,247,.10);
  border-radius:3px;
  overflow:hidden;
  background:
    linear-gradient(180deg, rgba(0,0,0,0) 0%, color-mix(in srgb, var(--mock-wash, var(--accent)) 36%, transparent) 100%),
    linear-gradient(rgba(255,255,255,.84), rgba(255,255,255,.84));
  color:#07121e;
}

/* ai-search */
.mock--search .mock__pane{ padding:2.4rem 1.6rem 3.2rem; min-height:26rem; display:flex; flex-direction:column; align-items:center }
.mock--search .q{ font-size:clamp(1.3rem,1.0rem + 1.3vw,1.94rem); line-height:1.1; text-align:center; max-width:19ch; color:#07121e }
.mock--search .searchbox{
  margin-top:2.1rem; align-self:stretch; display:flex; align-items:center; gap:.7rem;
  background:rgba(255,255,255,.2); border:1px solid rgba(0,0,0,.4);
  padding:.85rem .9rem; font-size:1.125rem; line-height:1.22; color:#111113;
}
.mock--search .thinking{
  margin-top:2.6rem; font-family:var(--mono); font-size:1.125rem; line-height:1.22; color:#111113;
  display:flex; align-items:center; gap:.6rem;
}
@media (prefers-reduced-motion:no-preference){
  .mock--search .spark{ animation:spin 5.5s linear infinite }
  @keyframes spin{ to{ transform:rotate(360deg) } }
}

/* sms-thread */
.mock--sms{ max-width:301px; margin-inline:auto }
.mock--sms .mock__pane{ padding:16px; display:flex; flex-direction:column; gap:13px; align-items:center }
.bubble{ width:100%; max-width:210px; padding:12px 20px 12px 16px; display:flex; flex-direction:column; gap:2px }
.bubble b{
  font-family:var(--mono); font-weight:600; font-size:.72rem; letter-spacing:.02em;
  text-transform:uppercase; opacity:1;   /* was .70 — measured 2.44:1, a clear fail */
}
.bubble p{ font-size:.95rem; line-height:1.4 }
.bubble--you{
  align-self:flex-end; color:#fff;
  /* MEASURED FAIL IN THE FIGMA VALUES, and this is Lisa's call to keep or revert.
     White on #ff327d is 3.49:1, and the label at 70% opacity was 2.44:1 — real
     text in the DOM, so WCAG applies whatever the bubble is imitating. These are
     the darkest-preserving pair that clears 4.5:1 (5.24 and 5.06); the hue and
     the left-to-right warm-to-hot move are unchanged. Revert to #f75451/#ff327d
     if the look matters more than the ratio — but then the mock has to become an
     image with alt text, because the text cannot stay live and failing. */
  background:linear-gradient(90deg,#cc3330,#d61f5f);
  border-radius:18px 18px 5px 18px;
}
.bubble--zosimo{
  align-self:flex-start; background:#fff; color:#25252d;
  border-radius:18px 18px 18px 5px;
  filter:drop-shadow(0 2px 4px rgba(0,0,0,.06));
}

/* ===== content/plan-pair =============================================== */
[data-pattern="content/plan-pair"] .display,
[data-pattern="content/capability-matrix"] .display{ max-width:28ch }
.plans{ display:grid; grid-template-columns:1fr 1fr; gap:35px; margin-top:60px; align-items:stretch }
@media (max-width:900px){ .plans{ grid-template-columns:1fr } }
.plan{ display:flex; flex-direction:column; gap:14px; height:100% }
.plan__card{ padding:22px 22px 18px; display:flex; flex-direction:column; flex:1 }
.plan__head{ border-bottom:1px solid var(--hair); padding-bottom:18px; display:flex; flex-direction:column; gap:14px }
.plan__blurb{ font-size:var(--fs-body-sm); line-height:1.6; color:rgba(255,255,255,.62) }
.plan ul{ list-style:none; margin:18px 0 0; padding:0 0 0 24px }
.plan li{
  position:relative; font-size:var(--fs-list); line-height:1.55; font-weight:500;
  letter-spacing:-.01em; color:var(--ink-list); padding-block:10px;
}
.plan li::before{
  content:""; position:absolute; left:-23px; top:1.05em;
  width:8px; height:8px; border-radius:4px; background:var(--accent);
}
.plan__note{ margin-top:auto; padding-top:18px; padding-left:24px; font-size:var(--fs-body-sm); line-height:1.6; color:rgba(255,255,255,.62) }
.plan__price{ display:flex; align-items:center; justify-content:space-between; gap:1rem; padding:32px 20px }
.plan__price span{
  font-family:var(--mono); font-size:var(--fs-control); line-height:1.55;
  letter-spacing:var(--ls-label); text-transform:uppercase; color:var(--ink-muted);
}
.plan__price b{ font-weight:300; font-size:var(--fs-price); line-height:1; color:var(--accent) }
.plan-pair .actions{ justify-content:center; margin-top:60px }

/* ===== content/capability-matrix ======================================= */
.matrix-wrap{ margin-top:60px; overflow-x:auto; -webkit-overflow-scrolling:touch }
table.matrix{ width:100%; min-width:760px; border-collapse:collapse; text-align:left }
table.matrix th, table.matrix td{ padding:14px 18px; border-bottom:1px solid var(--hair) }
table.matrix thead th{
  font-family:var(--mono); font-size:var(--fs-control); line-height:1.55;
  letter-spacing:var(--ls-label); text-transform:uppercase; font-weight:400; color:var(--ink-muted);
  white-space:nowrap;
}
table.matrix thead th:first-child{ color:var(--ink-muted) }
table.matrix thead th.own{ color:var(--accent) }
table.matrix tbody th{
  font-weight:400; font-size:var(--fs-body-sm); line-height:1.5; color:var(--ink-body);
}
table.matrix tbody tr.accent th{ color:var(--accent) }
table.matrix td{ text-align:center; width:11.5% }
.dot{ display:inline-block; width:10px; height:10px; border-radius:50%; background:var(--accent) }
.dot--half{ background:linear-gradient(90deg, var(--accent) 50%, rgba(255,255,255,.20) 50%) }
.dot--none{ width:6px; height:1px; border-radius:0; background:rgba(255,255,255,.28) }
.matrix-wrap + .actions{ justify-content:center; margin-top:60px }

/* ===== content/work-showcase =========================================== */
.showcase{ padding-block:3rem }
.showcase .body{ max-width:calc(1356px + 2 * var(--gutter)) }
.shots{ display:grid; grid-template-columns:repeat(3,1fr); gap:clamp(1rem,3vw,3rem); align-items:end }
@media (max-width:900px){ .shots{ grid-template-columns:1fr; max-width:520px; margin-inline:auto } }
/* Lisa 2026-08-29: the shots float on black. No container fill, no stroke —
   the artwork already carries its own device chrome and a box around it just
   fights that. A soft ambient shadow is the only thing under them, and it is
   there to seat them on the ground, not to draw a card. */
.shot{ position:relative; display:flex; align-items:flex-end; justify-content:center }
.shot img{ width:100%; height:auto; filter:drop-shadow(0 24px 56px rgba(0,0,0,.55)) }
.shot .ph{
  display:flex; align-items:center; justify-content:center; text-align:center; height:100%;
  font-family:var(--mono); font-size:.75rem; letter-spacing:.10em; text-transform:uppercase;
  color:var(--ink-muted); padding:1rem;
}

/* ===== content/closing-cta ============================================= */
.closing .body{ display:grid; grid-template-columns:1.05fr 1fr; gap:clamp(2rem,4vw,4rem); align-items:start }
.closing .display{ max-width:17ch }
.closing__card{ padding:29px; margin-top:2.5rem }
.closing__card .card__title{ margin-bottom:29px }
.chips{ grid-column:1/-1; display:grid; grid-template-columns:repeat(4,1fr); gap:27px; margin-top:2rem }
.chip{
  background:rgba(255,255,255,.14); border:1px solid rgba(255,255,255,.22);
  padding:42px 29px; text-align:center;
  font-family:var(--mono); font-size:var(--fs-control); line-height:1.6; color:#fff;
  text-shadow:0 1px 2px rgba(3,6,14,.55);
}
@media (max-width:1000px){ .closing .body{ grid-template-columns:1fr } .chips{ grid-template-columns:repeat(2,1fr) } }
@media (max-width:560px){ .chips{ grid-template-columns:1fr } }

/* ===== chrome/footer-marketing ========================================= */
.ftr{ background:var(--void); padding:53px var(--gutter) 56px }
.ftr__in{ max-width:var(--col-max); margin-inline:auto }
.ftr__top{ display:flex; align-items:center; justify-content:space-between; gap:2rem; flex-wrap:wrap }
.ftr__top nav{ display:flex; gap:1.6rem; flex-wrap:wrap }
.ftr__top a{ font-size:var(--fs-body-sm); color:var(--ink-body); text-decoration:none }
.ftr__top a:hover{ text-decoration:underline }
.ftr hr{ border:0; border-top:1px solid var(--hair); margin:20px 0 }
.ftr__legal{ display:flex; gap:1.6rem; flex-wrap:wrap }
.ftr__legal a{ font-size:var(--fs-body-sm); color:var(--ink-body); text-decoration:none }
.ftr__legal a:hover{ text-decoration:underline }
.ftr__base{ display:flex; align-items:center; justify-content:space-between; gap:2rem; flex-wrap:wrap; margin-top:20px }
.ftr__base p{ font-size:var(--fs-body-sm); color:var(--ink-body) }
