/* ==========================================================================
   LocallyAI Rev 2 — dot-matrix headings, dock nav, bento feature grid.
   Loaded after site.css. Every block here is additive: remove the two
   <link>/<script> tags in templates/base.html and Rev 2 falls back to Rev 1.
   ========================================================================== */

/* ---------------- dot-matrix headings ----------------
   The real heading text stays in the DOM for search and screen readers; it is
   visually hidden once the grid is built, so nothing is lost if JS never runs. */
.dot-head, [data-dots] { position: relative; }
/* The header wordmark is artwork, not set text, so the <a> carries no glyphs -
   the alt on the image is what a reader and a crawler get. Height is set in rem
   so it tracks the header's own type scale, and the width follows the art. */
.brand--mark { line-height: 0; }
.brand__img {
  display: block;
  width: auto;
  height: 1.75rem;
  max-width: 100%;
}
.top .brand__img { height: 1.9rem; }
@media (max-width: 30rem) { .top .brand__img { height: 1.55rem; } }

.foot__mark {
  display: block;
  width: 7rem;
  height: auto;
  margin-bottom: 1rem;
}
.has-dots { font-size: 0; line-height: 0; margin: 0; }
.dots__sr {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.dots {
  display: grid;
  grid-template-columns: repeat(var(--dot-cols), minmax(0, 1fr));
  gap: var(--dot-gap, 2px);
  font-size: 0; line-height: 0;
  --dot-gap: 2px;
  /* The board's own four steps, kept off the site palette so brightening the
     letters never drags the links and buttons with them. */
  --dot-off: rgba(240, 228, 216, 0.095);
  --dot-1: #ad5c24;
  --dot-2: #e37831;
  --dot-3: #f4995c;
  width: 100%;
  padding: clamp(0.35rem, 0.8vw, 0.58rem);
  background: rgba(14, 13, 12, 0.69);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 18px 50px -22px rgba(0, 0, 0, 0.9);
}
/* Every cell is drawn. An unlit glyph cell is the same colour as the rest of
   the board, so the letters emerge from the grid rather than out of holes. */
.dots i {
  display: block; width: 100%; aspect-ratio: 1; height: auto;
  border-radius: 22%;
  background: var(--dot-off);
  transition: background 110ms linear;
}
.dots i.l1 { background: var(--dot-1); }
.dots i.l2 { background: var(--dot-2); }
.dots i.l3 { background: var(--dot-3); }
.dots i.flick { background: var(--dot-2); }
.dots i.flick-hi { background: var(--dot-3); }
.dots.is-scramble i { transition: background 0.1s linear; }
/* a slow shimmer keeps it alive without pulling focus off the copy */
@keyframes dot-glow { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.35); } }

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

/* ---------------- dock navigation ----------------
   Magnifying dock ported from the React original to plain JS + CSS vars.
   Clicking a bubble reveals that group's sub-bubbles. */
.dock-wrap {
  /* sits in the header bar, where the nav buttons used to be */
  margin-left: auto; display: flex; align-items: center;
  pointer-events: auto;
}
.dock {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: 0.55rem;
  padding: 0.3rem 0.45rem;
  background: rgba(28, 25, 23, 0.6);
  border: 1px solid var(--line); border-radius: 16px;
}
.dock__item {
  position: relative; flex: none;
  width: var(--w, 44px); height: var(--w, 44px);
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--muted);
  border: 1px solid var(--line); border-radius: 50%;
  cursor: pointer; transition: color 0.16s ease, border-color 0.16s ease, background 0.16s ease;
  will-change: width, height;
}
.dock__item:hover, .dock__item:focus-visible { color: var(--copper-hi); border-color: var(--copper); }
.dock__item[aria-expanded="true"] { background: var(--copper-btn); border-color: var(--copper-btn); color: #fff7f0; }
.dock__item svg { width: 46%; height: 46%; }
.dock__label {
  position: absolute; top: calc(100% + 0.55rem); left: 50%; transform: translateX(-50%);
  padding: 0.22rem 0.5rem; white-space: nowrap;
  background: #1a1816; border: 1px solid var(--line-strong); border-radius: 6px;
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.04em; color: var(--text);
  opacity: 0; pointer-events: none; transition: opacity 0.16s ease, transform 0.16s ease;
}
.dock__item:hover .dock__label, .dock__item:focus-visible .dock__label {
  opacity: 1; transform: translateX(-50%) translateY(2px);
}
/* Once a group is open its sub-bubbles say what it is, so the floating hover
   label would only sit underneath them. Desktop only: in the phone sheet the
   label is the row's text and has to stay. */
@media (min-width: 60.0625rem) {
  .dock__item[aria-expanded="true"] .dock__label { opacity: 0; }
}

/* sub-bubbles: the group's pages, revealed on click */
.dock__subs {
  position: absolute; top: calc(100% + 0.9rem); left: 50%;
  display: flex; flex-direction: column; align-items: center; gap: 0.45rem;
  transform: translateX(-50%); pointer-events: none;
}
.dock__sub {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.4rem 0.8rem 0.4rem 0.5rem;
  background: #1a1816; border: 1px solid var(--line-strong); border-radius: 999px;
  color: var(--muted); font-size: 0.84rem; white-space: nowrap;
  opacity: 0; transform: translateY(-14px) scale(0.9); pointer-events: none;
  transition: opacity 0.22s ease, transform 0.28s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.dock__sub::before {
  content: ""; width: 0.42rem; height: 0.42rem; border-radius: 50%;
  background: var(--copper); flex: none;
}
.dock__item[aria-expanded="true"] .dock__sub {
  opacity: 1; transform: translateY(0) scale(1); pointer-events: auto;
}
.dock__sub:hover { color: var(--text); border-color: var(--copper); text-decoration: none; }
/* site.css's `.top nav a[aria-current="page"]` (0,2,1) forces a transparent
   ground for the old nav links and outranks a bare `.dock__sub[aria-current]`,
   so the page you are on has to be matched more specifically than that. */
.top nav a.dock__sub[aria-current="page"] {
  background: var(--copper-btn); border-color: var(--copper-btn); color: #fff7f0;
  box-shadow: none;
}
.top nav a.dock__sub[aria-current="page"]::before { background: #fff7f0; }



/* ---------------- bento feature grid (features-8, ported) ---------------- */
.bento { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0.75rem; }
.bento > .bcard { grid-column: 1 / -1; }
@media (min-width: 40rem) {
  .bento > .bcard--half { grid-column: span 3; }
}
@media (min-width: 64rem) {
  .bento > .bcard--third { grid-column: span 2; }
  .bento > .bcard--half  { grid-column: span 3; }
}
.bcard {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: clamp(1.25rem, 2.4vw, 1.9rem);
}
.bcard__stat {
  position: relative; display: flex; align-items: center;
  height: 6rem; margin-inline: auto;
}
.bcard__stat svg { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--line-soft); }
.bcard__stat b {
  position: relative; margin-inline: auto;
  font-size: clamp(2.4rem, 4vw, 3.1rem); font-weight: 680; letter-spacing: -0.04em;
  color: var(--text);
}
.bcard__ring {
  position: relative; width: 8rem; height: 8rem; margin: 0 auto;
  display: flex; border: 1px solid var(--line); border-radius: 50%;
}
.bcard__ring::before {
  content: ""; position: absolute; inset: -0.5rem;
  border: 1px solid var(--line-soft); border-radius: 50%;
}
.bcard__ring svg { margin: auto; width: 60%; height: auto; color: var(--copper); }
.bcard__ico {
  position: relative; width: 3rem; height: 3rem;
  display: flex; border: 1px solid var(--line); border-radius: 50%;
}
.bcard__ico::before { content: ""; position: absolute; inset: -0.5rem; border: 1px solid var(--line-soft); border-radius: 50%; }
.bcard__ico svg { margin: auto; width: 1.25rem; height: 1.25rem; color: var(--copper-hi); }
.bcard h3 { margin: 1.15rem 0 0.45rem; font-size: 1.06rem; }
.bcard p { color: var(--muted); font-size: 0.93rem; max-width: none; }
.bcard--center { text-align: center; }
.bcard--split { display: grid; gap: 1.5rem; }
@media (min-width: 40rem) { .bcard--split { grid-template-columns: 1fr 1fr; align-items: center; } }
.bcard__panel {
  position: relative; margin: 0 -1.9rem -1.9rem 0; padding: 1.6rem 0 0 1.4rem;
  border-left: 1px solid var(--line); border-top: 1px solid var(--line);
  border-top-left-radius: 10px;
}
.bcard__panel::before {
  content: ""; position: absolute; left: 0.75rem; top: 0.6rem;
  width: 0.4rem; height: 0.4rem; border-radius: 50%;
  background: var(--line-strong);
  box-shadow: 0.6rem 0 0 var(--line-strong), 1.2rem 0 0 var(--line-strong);
}
.bcard__panel img { width: 100%; border-radius: 6px 0 0 0; border: 1px solid var(--line); }
.bcard__spark { width: 100%; height: auto; color: var(--copper); }
.bcard__people { position: relative; display: flex; flex-direction: column; gap: 0.85rem; padding: 0.5rem 0; }
.bcard__people::before { content: ""; position: absolute; inset: 0; left: 50%; width: 1px; background: var(--line); }
.bcard__chip {
  position: relative; display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.3rem 0.6rem; width: fit-content;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px;
  font-size: 0.76rem; color: var(--muted);
}
.bcard__chip--r { margin-left: auto; }

/* the dock lives in the header, so no extra page offset is needed */
.top { overflow: visible; }
.top__in { overflow: visible; }

/* --- bento polish ------------------------------------------------------- */
.bcard img {
  width: 100%; max-width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 8px;
  background: #131110; cursor: zoom-in;
}
.bcard > img { margin-bottom: 0.25rem; }
.bcard--split { align-items: start; }
.bcard--split > div:first-child { display: flex; flex-direction: column; gap: 0.5rem; }
.bcard--split h3 { margin-top: 0.9rem; }
.bcard--center { align-items: center; }
.bcard--center h3, .bcard--center p { text-align: center; }
.bcard__panel { align-self: stretch; }
.bcard__panel img { border-radius: 6px; }
.bcard__people { justify-content: center; min-height: 8rem; }
.bcard__stat { width: 100%; max-width: 16rem; }
/* keep the whole row on one baseline rather than ragged card bottoms */
.bento > .bcard { height: 100%; }

/* --- hero rhythm with the dot headline ---------------------------------- */
/* Copy and screenshot share a centre line, as they did before the dot grid. */
.lead__copy { padding-top: 0; }
.lead__in { align-items: center; }
.lead__shot { margin-top: 0; }
.lead .pill { margin-bottom: 0.9rem; }
.lead .dots { margin-block: 0.2rem 1.35rem; max-width: 34rem; }
.lead .lede { font-size: 1.05rem; }
.lead .hero__actions { margin-top: 1.4rem; }
.lead__marks { margin-top: 1.3rem; }

/* The hero carries a dot block, a lede, two buttons and the proof marks, so it
   needs more height than the set-type version did. */
.lead { min-height: min(100svh, 54rem); }
@media (max-width: 72rem) { .lead { min-height: 0; } }

/* --- one centre line for the board and the screenshot --------------------
   The copy column stretches to the row height and lays out as three rows:
   an equal flexible band above and below the dot board. That puts the board
   on the column's exact centre, and the screenshot centres against the same
   line, so the two read as aligned rather than approximately so. */
.lead__in { align-items: center; }
.lead__copy {
  display: block;
}
/* the heading has no type to size it, so it must be told to fill the column */
.lead__copy > h1.has-dots { display: block; width: 100%; }
.lead .pill { align-self: end; margin-bottom: 0.9rem; }
.lead__shot { align-self: center; }

@media (max-width: 72rem) {
  .lead__copy { display: block; }
  .lead .pill { margin-bottom: 0.9rem; }
}

/* The caption sits outside the flow so the column's height is the frame's
   height; otherwise centring aligns "frame + caption", which puts the frame
   itself half a caption above the dot board. */
.lead__shot { position: relative; }
.lead__shot > figcaption {
  position: absolute; left: 0; right: 0; top: 100%;
  margin-top: 0.9rem;
}

/* --- dock: even icons, a round home, a square orange call to action ------- */
/* A percentage size made each glyph scale with how much of its own 24x24
   viewBox it filled, so the home and calendar marks came out visibly smaller.
   A fixed size makes every icon read at the same weight. */
.dock__item svg { width: 1.15rem; height: 1.15rem; flex: none; }
.dock__item--home { border-radius: 50%; }
/* Grey glyph rather than white: it sits on the copper without shouting over
   the other bubbles, and matches the weight of the icons beside it. */
.dock__item--cta {
  border-radius: 16px;   /* matches the dock's outline */
  background: var(--copper-btn); border-color: var(--copper-btn); color: #cbc4bd;
}
.dock__item--cta:hover, .dock__item--cta:focus-visible {
  background: #c9662a; border-color: #c9662a; color: #e4ded8;
}

/* ---------------- phones: the dock folds into one corner button ----------- */
.dock-menu { display: none; }

@media (max-width: 60rem) {
  .dock-wrap { position: static; }
  .dock-menu {
    display: flex; align-items: center; justify-content: center;
    width: 2.75rem; height: 2.75rem; padding: 0; cursor: pointer;
    background: var(--surface-2); color: var(--muted);
    border: 1px solid var(--line); border-radius: var(--r-ui);
    margin-left: auto;
  }
  .dock-menu[aria-expanded="true"] {
    background: var(--copper-btn); border-color: var(--copper-btn); color: #fff7f0;
  }
  .dock-menu__bars, .dock-menu__bars::before, .dock-menu__bars::after {
    display: block; width: 1.05rem; height: 1.5px; border-radius: 2px;
    background: currentColor; transition: transform 0.2s ease, background 0.2s ease;
  }
  .dock-menu__bars { position: relative; }
  .dock-menu__bars::before, .dock-menu__bars::after { content: ""; position: absolute; left: 0; }
  .dock-menu__bars::before { top: -5px; }
  .dock-menu__bars::after { top: 5px; }
  .dock-menu[aria-expanded="true"] .dock-menu__bars { background: transparent; }
  .dock-menu[aria-expanded="true"] .dock-menu__bars::before { transform: translateY(5px) rotate(45deg); }
  .dock-menu[aria-expanded="true"] .dock-menu__bars::after { transform: translateY(-5px) rotate(-45deg); }

  /* A full-width sheet under the header rather than a floating card in the
     corner: it opens on the page, so nothing important sits behind it.
     `.top nav` (0,1,1) still carries the old mobile menu's rules and outranks
     a bare `.dock`, so the selector is raised to match. */
  .top nav.dock {
    display: none;
    position: absolute; left: 0; right: 0; margin: 0;
    top: 100%; z-index: 60;
    flex-direction: column; align-items: stretch; gap: 0.1rem;
    width: 100%;
    max-height: calc(100dvh - var(--header-height)); overflow-y: auto;
    padding: 0.55rem var(--pad) calc(0.9rem + env(safe-area-inset-bottom));
    background: #17150f00; background: #171512;
    border: 0; border-top: 1px solid var(--line); border-radius: 0;
    box-shadow: 0 24px 50px rgba(0, 0, 0, 0.55);
  }
  .top nav.dock.is-open { display: flex; }

  .dock__item {
    width: 100% !important; height: auto;
    justify-content: flex-start; gap: 0.75rem;
    padding: 0.85rem 0.75rem;
    background: transparent; border: 0; border-radius: var(--r-ui);
    font-size: 1rem; color: var(--text);
  }
  /* one hairline between rows, and none before the call to action */
  .dock__item + .dock__item { border-top: 1px solid var(--line-soft); border-radius: 0; }
  .dock__item--cta, .dock__item--cta + .dock__item { border-top: 0; }
  .dock__item svg { width: 1.1rem; height: 1.1rem; color: var(--faint); }
  .dock__item--cta {
    justify-content: center; gap: 0.6rem;
    margin: 0.85rem 0 0.15rem; padding: 0.95rem 1rem;
    background: var(--copper-btn); border-radius: var(--r-ui);
    font-weight: 600;
  }
  .dock__item--cta .dock__label { flex: 0 0 auto; text-align: center; }
  .dock__item--cta svg, .dock__item--cta .dock__label { color: #fff7f0; }

  /* The desktop tooltip rules put a translateX(-50%) on hover and focus, which
     survived a tap and dragged the row's text off the left edge. */
  .dock__item:hover .dock__label,
  .dock__item:focus-visible .dock__label,
  .dock__item[aria-expanded="true"] .dock__label {
    transform: none; opacity: 1;
  }
  .dock__label {
    display: block; flex: 1 1 auto;
    position: static; transform: none; opacity: 1; pointer-events: auto;
    padding: 0; background: none; border: 0;
    font-family: var(--sans); font-size: 1rem; letter-spacing: 0; font-weight: 500;
    color: inherit; white-space: normal; text-align: left;
  }
  /* A wrapping flex row put the icon, label, chevron and sub-list in an order
     that depended on how much space was left. An explicit two-row grid is
     deterministic: icon | label | chevron on top, the sub-list spanning below. */
  .dock__item[aria-haspopup="true"] {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center; column-gap: 0.75rem; row-gap: 0;
  }
  /* Explicit areas, not auto-placement: with the sub-list spanning every
     column, the chevron was being pushed onto a third row and the label lost
     its slot. Rows and columns are now stated outright. */
  .dock__item[aria-haspopup="true"] > svg { grid-area: 1 / 1; }
  .dock__item[aria-haspopup="true"] > .dock__label { grid-area: 1 / 2; min-width: 0; }
  .dock__item[aria-haspopup="true"] > .dock__subs { grid-area: 2 / 1 / 3 / -1; }
  .dock__item[aria-haspopup="true"]::after {
    content: ""; grid-area: 1 / 3; flex: none; width: 0.45rem; height: 0.45rem; margin-right: 0.25rem;
    border-right: 1.5px solid var(--faint); border-bottom: 1.5px solid var(--faint);
    transform: rotate(45deg) translateY(-0.1em); transition: transform 0.2s ease;
  }
  .dock__item[aria-expanded="true"]::after { transform: rotate(-135deg) translateY(-0.1em); }

  /* `width: 100%` on a grid child that already spans every column made the
     row wider than the sheet, which dragged the icon and label off to the
     left the moment a group was opened. Auto width keeps the row still. */
  .dock__subs {
    position: static; transform: none; display: none;
    flex-direction: column; align-items: stretch; gap: 0.1rem;
    width: auto; min-width: 0; margin: 0.35rem 0 0.4rem; padding-left: 1.85rem;
  }
  .dock__item[aria-expanded="true"] { display: grid; background: transparent; color: var(--text); }
  .dock__item[aria-expanded="true"] .dock__subs { display: flex; }
  .dock__sub {
    opacity: 1; transform: none; pointer-events: auto;
    width: 100%; padding: 0.6rem 0.75rem;
    background: transparent; border: 0; border-radius: var(--r-ui);
    font-size: 0.94rem;
  }
  .top nav a.dock__sub[aria-current="page"] { background: var(--copper-btn); color: #fff7f0; }
}

/* ---------------- one radius across the site ----------------
   Buttons, cards, images and panels all share --r-ui so nothing reads as a
   different family. The dot board keeps its own radius deliberately. */
:root { --r-ui: 10px; }
.btn, .card, .bcard, .rows, .shot img, .feature__shot img, .hw__shot img,
.hw__lineup img, .lead__frame, .bcard img, .bcard__panel img, .picks a,
.picks img, .flow__scroll, .lightbox__scroll, .dock-menu, .menu-btn,
.top nav a.cta, .faq details, .shotwait, .bcard__ico, .card__art img {
  border-radius: var(--r-ui);
}
.picks img { border-radius: var(--r-ui) var(--r-ui) 0 0; }
.bcard__panel img { border-radius: var(--r-ui) 0 0 0; }
/* The home and call-to-action bubbles are <a> elements, so site.css's
   `.top nav a { border-radius: 7px }` (0,1,1) beat a bare `.dock__item`.
   Both need matching against that rule, not under it. */
.dock__item, .top nav a.dock__item { border-radius: 50%; }
.dock__item--cta, .top nav a.dock__item--cta { border-radius: 16px; }
.bcard__ring, .bcard__ico { border-radius: 50%; }

/* ---------------- alternating section grounds ----------------
   Dark, light, dark, light down every page, ending dark. build.py stamps the
   classes so the run survives pages that differ in section count. */
/* Two greys, nothing else: the band ground and the surface that sits on it. */
:root { --band-dark: #0d0c0b; --band-light: #141210; }
.sect--dark  { background: var(--band-dark); }
.sect--light { background: var(--band-light); }

/* every card, panel and row shares the one surface colour */
.card, .bcard, .rows, .picks a, .faq details, .lead__frame, .flow__scroll,
.bcard__panel, .shotwait { background: var(--band-light); }

/* a hairline between every band, so the rhythm is consistent down the page */
main > section + section { border-top: 1px solid rgba(255, 240, 226, 0.055); }
main > section.section--band { background: inherit; }

/* Every dark band carries the copper scatter. scatter.js draws it live: a
   fixed 20px grid so the texture is the same weight on a short band as on a
   tall one. Most marks hold 5-10s before fading out or becoming another; a few
   only blink into existence and go.
   Decoration only, so it sits behind content and is hidden from assistive tech. */
.sect--dark { position: relative; isolation: isolate; }
.scatter {
  position: absolute; inset: 0; z-index: -1;
  width: 100%; height: 100%;
  color: var(--copper);
  opacity: 0.10;
  pointer-events: none;
}

/* ---------------- FAQ: bordered cards, not underlined rows ----------------
   Each question is its own panel on the surface colour, so the scatter behind
   the band does not read through the text, and the label and the toggle sit
   inside the box rather than against its edges. */
.faq { border-top: 0; gap: 0.5rem; max-width: 52rem; }
.faq details {
  background: var(--band-light);
  border: 1px solid var(--line); border-radius: var(--r-ui);
  overflow: hidden;
}
.faq details[open] { border-color: var(--line-strong); }
.faq summary {
  padding: 1.05rem 3.25rem 1.05rem 1.25rem;
  font-size: 1rem;
}
.faq summary:hover { color: var(--copper-hi); }
.faq summary::after { right: 1.25rem; top: 0.95rem; }
.faq details > div { padding: 0 1.25rem 1.15rem; }

/* ==========================================================================
   One card system, everywhere.
   The six-column bento left a dead column whenever a row's spans did not add
   to six. Auto-fit removes the arithmetic: every card claims an equal share of
   the row and the last row stretches to fill, so there is no wasted space on
   any page or at any width.
   ========================================================================== */
.bento {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr));
  gap: clamp(0.75rem, 1.5vw, 1.1rem);
  align-items: stretch;
}
.bento > .bcard,
.bento > .bcard--third,
.bento > .bcard--half { grid-column: auto; }
/* a card carrying a wide split takes two shares where there is room for it */
@media (min-width: 62rem) { .bento > .bcard--split { grid-column: span 2; } }

/* --- the card itself, shared by .card and .bcard ------------------------- */
.card, .bcard {
  display: flex; flex-direction: column; height: 100%;
  gap: 0.55rem;
  background: var(--band-light);
  border: 1px solid var(--line); border-radius: var(--r-ui);
  padding: clamp(1.15rem, 2.2vw, 1.65rem);
}
.card > h3, .bcard > h3 { margin: 0.2rem 0 0; font-size: 1.06rem; letter-spacing: -0.02em; }
.card > p, .bcard > p { margin: 0; color: var(--muted); font-size: 0.93rem; max-width: none; }
/* the last paragraph is the description, so it sits against the bottom and
   every card in a row ends on the same line */
.card > p:last-of-type, .bcard > p:last-of-type { margin-top: auto; padding-top: 0.35rem; }
.card > p + p, .bcard > p + p { margin-top: 0; }
.card .micro, .bcard .micro { order: -1; }

/* imagery sits at the top of the card and never crops the subject */
.bcard > img, .card > img, .card__art img {
  width: 100%; height: auto; margin-bottom: 0.5rem;
  border: 1px solid var(--line); border-radius: calc(var(--r-ui) - 3px);
  background: #101010; cursor: zoom-in;
}
/* square product art gets a consistent frame */
.card__art img[width="1200"], .bcard > img[width="1200"] { aspect-ratio: 1; object-fit: cover; }

/* icon-led cards: a mark stands in for the picture */
.bcard__ico, .card__ico {
  flex: none; width: 2.6rem; height: 2.6rem; margin-bottom: 0.35rem;
  display: flex; border: 1px solid var(--line); border-radius: 50%;
}
.bcard__ico svg, .card__ico svg { margin: auto; width: 1.15rem; height: 1.15rem; color: var(--copper-hi); }

/* the grids that predate the bento behave the same way */
.grid { align-items: stretch; }
.grid > .card { height: 100%; }
.rows { background: var(--band-light); }

/* split cards keep their two halves aligned and never overflow the card */
.bcard--split { display: grid; gap: 1.25rem; align-items: center; }
@media (min-width: 40rem) { .bcard--split { grid-template-columns: 1fr 1fr; } }
.bcard--split > div { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
.bcard--split > div > p:last-of-type { margin-top: auto; }
.bcard__panel { margin: 0; padding: 0; border: 0; min-width: 0; }
.bcard__panel::before { display: none; }
.bcard__panel img { width: 100%; border-radius: calc(var(--r-ui) - 3px); }

/* the three promise columns are plain articles, not cards, but they still have
   to end on one line */
.promises article { display: flex; flex-direction: column; height: 100%; }
.promises article > p:last-child { margin-top: auto; padding-top: 0.4rem; }

/* --- card interiors: no hollow middles ----------------------------------
   Pinning the description to the bottom equalised the baselines but left a
   pool of dead space in every short card. Content now flows from the top, the
   card stretches to its row, and picture cards share one aspect so a row of
   them does not tower over a row of text. */
.card > p:last-of-type, .bcard > p:last-of-type,
.promises article > p:last-child,
.bcard--split > div > p:last-of-type { margin-top: 0; padding-top: 0; }

.bcard > img, .card > img, .card__art img, .bcard__panel img {
  aspect-ratio: 16 / 10; object-fit: cover; object-position: top center;
}
/* product shots are square by design and keep their frame */
.card__art img[width="1200"], .bcard > img[width="1200"] { aspect-ratio: 1; }

/* short, icon-led cards centre their content rather than hugging the top */
.bcard--center { justify-content: center; text-align: center; }
.bcard--center .bcard__ring, .bcard--center .bcard__stat { margin-inline: auto; }
.bcard--center > p { margin-inline: auto; max-width: 30ch; }

/* the split card's two halves both start at the top */
.bcard--split { align-items: start; }
.bcard--split > div:first-child { justify-content: flex-start; }

/* --- every card is one share -------------------------------------------
   A card spanning two columns leaves a hole whenever the row cannot be filled
   exactly. Split cards now stack like the rest: picture on top, then the text.
   Nothing spans, so no row can come up short at any width. */
@media (min-width: 62rem) { .bento > .bcard--split { grid-column: auto; } }
.bcard--split { display: flex; flex-direction: column; gap: 0.55rem; align-items: stretch; }
@media (min-width: 40rem) { .bcard--split { grid-template-columns: none; } }
.bcard--split > .bcard__panel { order: -1; margin-bottom: 0.35rem; }
.bcard--split > div { gap: 0.5rem; }

/* --- the last row fills ---------------------------------------------------
   Under grid, a trailing row with fewer cards than columns leaves the unused
   tracks empty. Flex lets the final row's cards grow into the space instead,
   so no page ends on a half-empty band. */
.bento {
  display: flex; flex-wrap: wrap;
  align-items: stretch;
}
.bento > * {
  flex: 1 1 17.5rem;
  min-width: 0;
  max-width: 100%;
}
@media (min-width: 62rem) {
  /* keeps a lone trailing card from stretching the full width of the page */
  .bento > * { max-width: calc(50% - 0.55rem); }
}

/* `height: 100%` on a flex item resolves against an indefinite line height and
   collapses back to content, which undid the stretch. Let the flex line do it. */
.bento > .bcard { height: auto; align-self: stretch; }

/* The contact form is a panel, not a content card: same surface and border,
   but it is not expected to carry a title and a description. */
.panel {
  display: flex; flex-direction: column; gap: 0.6rem;
  background: var(--band-light);
  border: 1px solid var(--line); border-radius: var(--r-ui);
}
.panel--pad { padding: clamp(1.15rem, 2.2vw, 1.65rem); }

/* ==========================================================================
   Rev 2 — corrections
   ========================================================================== */

/* 1. The board's marks settle at the same rate the background scatter fades.
      The intro scramble keeps its own fast transition so the effect still
      resolves quickly. */
.dots i { transition: background 1s ease; }
.dots.is-scramble i { transition: background 0.1s linear; }

/* 2. Progressive enhancement: the real heading remains visible until the
      board exists. Phones intentionally skip the board, and script failures
      must never erase the page title. */
[data-dots]:not(.has-dots) { visibility: visible; }

/* 3. FAQ: spaced boxes, and no stray rule above the first one. */
.faq { border-top: 0; gap: 0.6rem; }
.faq details { margin: 0; }

/* 4. The rule between bands is the same weight and colour as a card's border. */
main > section + section { border-top: 1px solid var(--line); }

/* 5. One spacing scale for every band, 25% tighter than before and identical
      top and bottom no matter what the block contains. */
:root { --band-pad: clamp(2.8rem, 5.25vw, 4.9rem); }
.section,
.hero,
.home-brief,
.sect--dark,
.sect--light { padding-block: var(--band-pad); }
.section, .hero { padding-block: var(--band-pad); }
.home-brief__in { padding-block: 0; }
.hero--split { padding-block: var(--band-pad); }
.feature { padding-block: 0; }
.feature + .feature { padding-top: var(--band-pad); }
.flow { padding-block: var(--band-pad); }
.closer__in { padding-block: var(--band-pad); }
/* the lead carries artwork and sets its own height */
.lead { padding-block: 0; }
.lead__in { padding-block: var(--band-pad); }

/* The two claim cards close the grid and take the wide slots, so the feature
   cards lead and the promise lands last. */
.bento > .bcard--wide { flex: 1 1 26rem; }
.bcard__ico--lg { width: 3.6rem; height: 3.6rem; }
.bcard__ico--lg svg { width: 1.65rem; height: 1.65rem; }

/* --- corrections, round two ---------------------------------------------- */

/* The rule between bands has to beat site.css's own soft borders on
   .section--band, .boundary and .closer, which are class-level and were
   out-specifying a plain element selector. The general sibling combinator is
   used because a full-bleed <figure> sits between two sections on /machines/
   and broke adjacency. */
main > section ~ section[class] { border-top: 1px solid var(--line); }
main > section.section--band,
main > section.boundary,
main > section.closer { border-bottom-color: var(--line); }
main > section.section--band { border-block-color: var(--line); }

/* one spacing scale wins over anything a page set for itself */
main > section:not(.lead):not(.closer) { padding-block: var(--band-pad); }

/* --- the grid reads 2, 3, 3 -------------------------------------------
   Flex alone wraps by available width, which gave 3/3/2. Explicit bases hold
   the intended rhythm: two lead cards, then two rows of three. */
@media (min-width: 62rem) {
  .bento > .bcard--r1 { flex: 1 1 calc(50% - 0.6rem); max-width: calc(50% - 0.55rem); }
  .bento > .bcard--r2,
  .bento > .bcard--r3 { flex: 1 1 calc(33.333% - 0.75rem); max-width: calc(33.333% - 0.74rem); }
}

/* the closing link is centred under the grid */
.note--center { text-align: center; }
.note--center .btn { margin-inline: auto; }

/* --- centring and breathing room ----------------------------------------
   `.note` inherits the 62ch measure from `p`, so a centred button inside one
   was centred within that measure rather than the column — which read as
   left-aligned on the page. */
.note--center { max-width: none; width: 100%; }
.note--center .btn { display: inline-flex; }

/* A link sitting under a spec table or a price list had almost no separation
   from it. Double the gap wherever one follows a block like that. */
.rows + .note,
.rows + p,
.rows + .hero__actions,
.shot + .note,
dl + .note { margin-top: clamp(1.5rem, 3vw, 2.4rem); }
.note + .note { margin-top: 1rem; }

/* a centred head still laid its buttons out from the left */
.head--center .hero__actions,
.note--center .hero__actions { justify-content: center; }

/* ==========================================================================
   Gap scale
   An audit across eleven pages found 13 distinct gaps under 12px — headings
   sitting 9px off their paragraph, notes 9px off the block above, spec rows
   4px apart. Rather than patch each one, every vertical relationship now
   resolves to one of four steps, and nothing falls below the smallest.
   The dot board keeps its own 2px cell gap; it is a grid of pixels, not type.
   ========================================================================== */
:root {
  --gap-xs: 0.6rem;    /* inside a tight cluster */
  --gap-sm: 0.95rem;   /* a heading and the text under it */
  --gap-md: 1.5rem;    /* between blocks within a section */
  --gap-lg: 2.4rem;    /* a block and the note or link that follows it */
}

/* heading to its text */
main h2 + p, main h3 + p, main h4 + p,
main .micro + h2, main .micro + h3, main .micro + h4,
main .pill + h1, main .pill + h2 { margin-top: var(--gap-sm); }
main h2, main h3, main h4 { margin-bottom: 0; }

/* text to text */
main p + p, main p + ul, main p + ol, main ul + p, main ol + p { margin-top: var(--gap-sm); }

/* a block and the aside that follows it */
main p + .note, main .grid + .note, main .bento + .note,
main dl + .note, main .rows + .note, main .shot + .note,
main .picks + .note { margin-top: var(--gap-lg); }

/* prose against a table or a price list */
main p + .rows, main .rows + p, main h3 + .rows, main h2 + .rows,
main p + .ui, main .suggest + .ui { margin-top: var(--gap-md); }

/* spec rows were 4px apart when they wrapped */
.rows > div { gap: var(--gap-xs) 1.25rem; }

/* Card interiors are laid out by the flex gap, which out-specifies the margin
   rules above, so the step is set there rather than fought with margins. */
.card, .bcard, .bcard--split > div, .flow__head, .promises article { gap: var(--gap-sm); }
.card > h3, .bcard > h3 { margin-top: 0; }
.card > .micro, .bcard > .micro { margin-bottom: 0; }

/* the amount and its caption in a claim card */
.bcard .amount + p, .card .amount + p { margin-top: var(--gap-sm); }

/* the same step outside cards, above anything the base sheet still governs */
main .head h2 + p, main .head h3 + p,
main .wrap > h2 + p, main .wrap > h3 + p,
main div > h2 + p, main div > h3 + p { margin-top: var(--gap-sm); }
main .rows + p, main dl + p { margin-top: var(--gap-md); }
main p + p.note { margin-top: var(--gap-lg); }
main h2 + .card, main h2 + .panel, main h3 + .card, main h2 + .grid,
main h2 + .bento, main h3 + .grid { margin-top: var(--gap-md); }

/* the sizing claim sits apart from the price list it qualifies */
.sized-note { margin-top: var(--gap-lg); max-width: 46rem; }
.sized-note h3 { margin: 0; }

/* the label above a scrollable transcript */
.scroll-hint {
  margin: 0 0 0.5rem;
  color: var(--faint);
  text-align: center;
}

/* A closing note under a section is centred wherever it appears, so the
   pattern is consistent instead of centred on some pages and not others. */
main > section > .wrap > p.note:last-child,
main > section > .wrap > .bento + p.note,
main > section > .wrap > .grid + p.note { text-align: center; max-width: none; }

/* ---------------- rotating phrase ----------------
   One phrase at a time in a fixed slot, characters staggered in from below and
   out through the top. The slot's width animates between phrases so the words
   either side of it move smoothly instead of snapping. Timings live here rather
   than in the script: the browser owns the animation, JavaScript only ever
   swaps a class. */
.rot {
  --rot-dur: 520ms;
  --rot-stagger: 22ms;
  --rot-ease: cubic-bezier(0.2, 1.08, 0.36, 1);
  position: relative; display: inline-block; vertical-align: bottom;
  white-space: nowrap; overflow: hidden;
  /* room for descenders, taken back off the line so nothing shifts */
  padding-block: 0.16em; margin-block: -0.16em;
  transition: width 420ms var(--rot-ease);
}
/* a zero-width character gives the empty box the height of the line it sits on */
.rot::before { content: '\200b'; }
.rot__sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.rot__stack { position: absolute; inset: 0.16em 0; display: block; }
.rot__set { position: absolute; left: 0; top: 0; display: flex; white-space: pre; }
.rot__word { display: flex; }
.rot__ch, .rot__gap { display: inline-block; }
.rot__ch {
  opacity: 0; transform: translateY(100%);
  transition: transform var(--rot-dur) var(--rot-ease),
              opacity var(--rot-dur) var(--rot-ease);
  transition-delay: calc(var(--i, 0) * var(--rot-stagger));
}
.rot__set.is-in .rot__ch { opacity: 1; transform: none; }
.rot__set.is-out .rot__ch { opacity: 0; transform: translateY(-120%); }
/* parked back below the line between turns, without animating the trip */
.rot__set--reset .rot__ch { transition: none; }

/* A reserved slot never changes size, so there is nothing to animate. */
.rot--fixed { transition: none; }
.rot--centered .rot__set { left: 50%; transform: translateX(-50%); }

/* The changing word carries the accent, so it reads as the part that moves. */
.rot--accent { color: var(--copper-hi); }

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

/* ---------------- a lone card on the last row ----------------
   Five price tiers in a two-column grid leave the last one by itself. Left
   against the edge it reads as a layout that ran out rather than a tier, so it
   spans the row and sits in the middle at exactly one column's width. */
@media (min-width: 56rem) {
  .grid--2 > .price-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    width: calc(50% - var(--grid-gap) / 2);
    margin-inline: auto;
  }
}

/* ---------------- the closing band ----------------
   Two spacing rules from before this band existed were stacking under it.

   `.note--center` carries a top margin so a centred note clears the block it
   follows. In the closing band the note is the only thing there, so that margin
   just pushed the line off centre — 103px above it against 58px below.

   The footer then added its own top margin. That was right when a page ended on
   an ordinary content block, but every page now ends on a full-bleed band that
   already carries `--band-pad` underneath its content, so the margin was a
   second gap on top of the first, in page background, outside the band. */
.sect--closeout .note--center { margin-top: 0; }
.foot { margin-top: 0; }

/* ---------------- card carousel ----------------
   The original grid becomes the track, so every card keeps the background,
   border, radius and padding its own container gives it. The track is moved by
   transform, never by scrolling: a scroll container clamps at its ends, which
   let the wheel run off its own cards into empty space. */
.car { position: relative; }
.car__viewport {
  overflow: hidden;
  cursor: grab;
  /* vertical page scroll passes through, and two fingers still pinch the
     screenshots inside the cards */
  touch-action: pan-y pinch-zoom;
}
.car--drag .car__viewport { cursor: grabbing; user-select: none; }
.car--drag .car__viewport * { pointer-events: none; }

.car .car__track {
  display: flex; align-items: stretch;
  width: max-content; min-width: 100%; max-width: none;
  grid-template-columns: none;
  padding-inline: 0;
  will-change: transform;
}
.car .car__track > * {
  flex: 0 0 var(--car-w, 20rem);
  width: var(--car-w, 20rem);
  max-width: none; min-width: 0;
  height: auto;
}
/* every card renders at full strength — fading the neighbours caused more
   trouble than the focus cue was worth */

.car__ui {
  display: flex; align-items: center; justify-content: center;
  gap: var(--gap-md, 1.5rem); margin-top: var(--gap-md, 1.5rem);
}
.car__arrow {
  width: 2.5rem; height: 2.5rem; padding: 0; flex: none;
  display: grid; place-items: center; cursor: pointer;
  border-radius: 50%; border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--muted);
  transition: color 0.18s ease, border-color 0.18s ease,
              background 0.18s ease, transform 0.18s ease;
}
.car__arrow svg { width: 1rem; height: 1rem; display: block; }
.car__arrow--prev svg { transform: rotate(180deg); }
.car__arrow:hover { color: var(--text); border-color: var(--copper); transform: scale(1.07); }
.car__arrow:active { transform: scale(0.94); }
.car__arrow:focus-visible { border-color: var(--copper-hi); outline-offset: 3px; }

.car__dots { display: flex; align-items: center; gap: 0.5rem; }
.car__pip {
  width: 0.5rem; height: 0.5rem; padding: 0; border: 0; cursor: pointer;
  border-radius: 50%; background: var(--line-strong);
  transition: background 0.2s ease, transform 0.2s ease, width 0.2s ease;
}
.car__pip:hover { background: var(--muted); }
.car__pip.is-on { background: var(--copper-hi); width: 1.35rem; border-radius: 0.25rem; }
.car__pip:focus-visible { outline: 2px solid var(--copper-hi); outline-offset: 3px; }

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

/* ---------------- a lone card on the last row ----------------
   Five price tiers in a two-column grid leave the last one by itself. Left
   against the edge it reads as a layout that ran out rather than a tier, so it
   spans the row and sits in the middle at exactly one column's width. */
@media (min-width: 56rem) {
  .grid--2 > .price-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    width: calc(50% - var(--grid-gap) / 2);
    margin-inline: auto;
  }
}

/* ---------------- the closing band ----------------
   Two spacing rules from before this band existed were stacking under it.

   `.note--center` carries a top margin so a centred note clears the block it
   follows. In the closing band the note is the only thing there, so that margin
   just pushed the line off centre — 103px above it against 58px below.

   The footer then added its own top margin. That was right when a page ended on
   an ordinary content block, but every page now ends on a full-bleed band that
   already carries `--band-pad` underneath its content, so the margin was a
   second gap on top of the first, in page background, outside the band. */
.sect--closeout .note--center { margin-top: 0; }
.foot { margin-top: 0; }

/* ---------------- card carousel ----------------
   The original grid becomes the track, so every card keeps the background,
   border, radius and padding its own container gives it. These rules only
   change how that container lays its children out. */
.car { position: relative; }
.car__viewport {
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
  cursor: grab;
}
.car__viewport::-webkit-scrollbar { display: none; }
.car__viewport:focus-visible { outline: 2px solid var(--copper-hi); outline-offset: 4px; }
.car--drag .car__viewport { cursor: grabbing; user-select: none; }
.car--drag .car__viewport * { pointer-events: none; }

.car .car__track {
  display: flex; align-items: stretch;
  width: max-content; min-width: 100%; max-width: none;
  grid-template-columns: none;
}
.car .car__track > * {
  flex: 0 0 var(--car-w, 20rem);
  width: var(--car-w, 20rem);
  max-width: none; min-width: 0;
  height: auto;
}
/* every card renders at full strength — fading the neighbours caused more
   trouble than the focus cue was worth */

.car__ui {
  display: flex; align-items: center; justify-content: center;
  gap: var(--gap-md, 1.5rem); margin-top: var(--gap-md, 1.5rem);
}
.car__arrow {
  width: 2.5rem; height: 2.5rem; padding: 0; flex: none;
  display: grid; place-items: center; cursor: pointer;
  border-radius: 50%; border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--muted);
  transition: color 0.18s ease, border-color 0.18s ease,
              background 0.18s ease, transform 0.18s ease;
}
.car__arrow svg { width: 1rem; height: 1rem; display: block; }
.car__arrow--prev svg { transform: rotate(180deg); }
.car__arrow:hover { color: var(--text); border-color: var(--copper); transform: scale(1.07); }
.car__arrow:active { transform: scale(0.94); }
.car__arrow:focus-visible { border-color: var(--copper-hi); outline-offset: 3px; }

.car__dots { display: flex; align-items: center; gap: 0.5rem; }
.car__pip {
  width: 0.5rem; height: 0.5rem; padding: 0; border: 0; cursor: pointer;
  border-radius: 50%; background: var(--line-strong);
  transition: background 0.2s ease, transform 0.2s ease, width 0.2s ease;
}
.car__pip:hover { background: var(--muted); }
.car__pip.is-on { background: var(--copper-hi); width: 1.35rem; border-radius: 0.25rem; }
.car__pip:focus-visible { outline: 2px solid var(--copper-hi); outline-offset: 3px; }

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

/* ---------------- the closing band ----------------
   Two spacing rules from before this band existed were stacking under it.

   `.note--center` carries a top margin so a centred note clears the block it
   follows. In the closing band the note is the only thing there, so that margin
   just pushed the line off centre — 103px above it against 58px below.

   The footer then added its own top margin. That was right when a page ended on
   an ordinary content block, but every page now ends on a full-bleed band that
   already carries `--band-pad` underneath its content, so the margin was a
   second gap on top of the first, in page background, outside the band. */
.sect--closeout .note--center { margin-top: 0; }
.foot { margin-top: 0; }

/* ---------------- card carousel ----------------
   The track is a real scroll container, so a swipe, a trackpad flick and the
   keyboard all work without the script touching them. Cell width is written in
   by site.js as a pixel value; everything else is here. */
.car { position: relative; }
.car__viewport {
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.car__viewport::-webkit-scrollbar { display: none; }
.car__viewport:focus-visible { outline: 2px solid var(--copper-hi); outline-offset: 4px; border-radius: var(--r-ui); }
.car__track {
  display: flex; align-items: stretch;
  gap: clamp(0.7rem, 1.5vw, 1.05rem);
  width: max-content; min-width: 100%;
}
/* The bento sizes its own cards; inside a carousel every card is one width.
   Matched against `.bento > .bcard` rather than raised above it with a flag. */
.car__track > .car__cell,
.car__track > .bcard.car__cell,
.car__track > .card.car__cell {
  flex: 0 0 var(--car-w, 20rem);
  width: var(--car-w, 20rem);
  max-width: none; min-width: 0;
  scroll-snap-align: start;
  height: auto;
}
.car--held .car__viewport { scroll-snap-type: x mandatory; }
.car--peek .car__track > .car__cell { scroll-snap-align: center; }
/* Off-centre cards sit back a little, so the one in front reads as the subject
   and the neighbours read as somewhere to go next. */
.car--peek .car__track > .car__cell {
  opacity: 0.55; transition: opacity 0.35s ease;
}
.car--peek .car__track > .car__cell.is-active { opacity: 1; }

/* nothing to scroll: fall back to a plain row */
.car--static .car__ui { display: none; }
.car--static .car__track { justify-content: center; }

.car__ui {
  display: flex; align-items: center; justify-content: center;
  gap: var(--gap-md, 1.5rem); margin-top: var(--gap-md, 1.5rem);
}
.car__arrow {
  width: 2.5rem; height: 2.5rem; padding: 0; flex: none;
  display: grid; place-items: center; cursor: pointer;
  border-radius: 50%; border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--muted);
  transition: color 0.18s ease, border-color 0.18s ease,
              background 0.18s ease, transform 0.18s ease;
}
.car__arrow svg { width: 1rem; height: 1rem; display: block; }
.car__arrow--prev svg { transform: rotate(180deg); }
.car__arrow:hover { color: var(--text); border-color: var(--copper); transform: scale(1.07); }
.car__arrow:active { transform: scale(0.94); }
.car__arrow:focus-visible { border-color: var(--copper-hi); outline-offset: 3px; }

.car__dots { display: flex; align-items: center; gap: 0.5rem; }
.car__pip {
  width: 0.5rem; height: 0.5rem; padding: 0; border: 0; cursor: pointer;
  border-radius: 50%; background: var(--line-strong);
  transition: background 0.2s ease, transform 0.2s ease, width 0.2s ease;
}
.car__pip:hover { background: var(--muted); }
.car__pip.is-on { background: var(--copper-hi); width: 1.35rem; border-radius: 0.25rem; }
.car__pip:focus-visible { outline: 2px solid var(--copper-hi); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .car__viewport { scroll-behavior: auto; }
  .car__arrow, .car__pip, .car--peek .car__track > .car__cell { transition: none; }
}

/* Keep the small visual pips while giving every carousel control a 24px hit area. */
.car__pip {
  position: relative;
  width: 1.5rem;
  height: 1.5rem;
  background: transparent;
}
.car__pip::before {
  content: "";
  position: absolute;
  inset: 0.5rem;
  border-radius: 50%;
  background: var(--line-strong);
}
.car__pip:hover, .car__pip.is-on { background: transparent; width: 1.5rem; }
.car__pip:hover::before { background: var(--muted); }
.car__pip.is-on::before {
  inset-inline: 0.2rem;
  border-radius: 0.25rem;
  background: var(--copper-hi);
}

/* ---------------- no dead air at band edges ----------------
   Three margins were stacking on top of the band padding and reading as empty
   bands: a first heading's top margin straight after a hero, a section-closing
   figure's bottom margin, and a head block's bottom margin when nothing
   follows it. The band padding alone is the rhythm; the extras go. */
.article > h2:first-child { margin-top: 0; }
main section > .hw__lineup:last-child { margin-bottom: 0; }
.wrap > .head:last-child { margin-bottom: 0; }

/* ---------------- phone hero order ----------------
   On a phone the product shot belongs straight under the headline, not below
   the paragraph and buttons. display:contents lifts the copy wrapper out of
   the way so its pieces and the figure share one column and can be ordered. */
@media (max-width: 72rem) {
  .lead__in { display: flex; flex-direction: column; align-items: stretch; gap: 1.4rem; }
  .lead__copy { display: contents; }
  .lead__copy .pill { order: 1; margin-bottom: 0; }
  .lead__copy h1 { order: 2; margin: 0; }
  .lead__shot { order: 3; margin-top: 0; }
  /* stacked, the caption is part of the column again — out of flow it printed
     straight over the paragraph that follows */
  .lead__shot > figcaption { position: static; margin-top: 0.7rem; }
  .lead__rest { order: 4; }
}

/* Phones read in document order. The copy explains the product before the
   screenshot, and headings stay compact enough for long regional titles. */
@media (max-width: 44rem) {
  .lead__in {
    gap: 2rem;
    padding-block: 2.5rem 3rem;
  }
  .lead__copy { display: block; }
  .lead__copy .pill { margin-bottom: 0.9rem; }
  .lead__copy h1,
  .hero h1 {
    margin-bottom: 1.1rem;
    font-size: clamp(2.25rem, 11vw, 2.8rem);
    line-height: 1.04;
    letter-spacing: -0.04em;
  }
  .lead__rest .lede {
    font-size: 1.05rem;
    line-height: 1.55;
  }
  .lead__shot { order: initial; }
  .lead__shot > figcaption { font-size: 0.8rem; line-height: 1.45; }
  .lead .hero__actions { margin-top: 1.5rem; }
  .lead__marks { margin-top: 1.4rem; gap: 0.55rem; }
  .lead__marks li { width: 100%; }
  .section,
  .hero,
  .sect--dark,
  .sect--light { padding-block: clamp(2.5rem, 10vw, 3.25rem); }
}

/* In a carousel row every card is as tall as the tallest. A centred stat card
   floats into that height and opens a hole above itself — pin card content to
   the top so the row reads level. */
.car .car__track > .bcard { justify-content: flex-start; }

/* The rotator line may never break: while the slot's width animates between
   phrases, WebKit re-evaluates the line and briefly wrapped "back." onto a
   third line. Locked, the worst mid-transition case is a few px of invisible
   overhang instead of a reflow. */
.line-lock { white-space: nowrap; }

/* ---------------- real Harness demo films ----------------
   Native video keeps the captures sharp on Retina screens. Desktop masters are
   1464x1080 (61/45); phone masters are 780x1688 portrait.

   Every film shares the same landscape frame so a row of cards stays level -
   a portrait film left at its own ratio ran nearly three times the height of a
   landscape one and dragged every card in the row up to 958px. The phone films
   sit inside that frame with `contain`, so the whole screen is visible and
   nothing is cropped; the frame is a thumbnail and clicking opens the film at
   its full native size. */
/* Element-qualified so these beat `.bcard > img` (0,1,1), which otherwise forces
   every card image to 16/10. The carousel renders cloned cards as poster <img>
   rather than <video>, so both element types must be covered or the clones crop. */
img.demo-media,
video.demo-media {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 61 / 45;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--r-ui);
  box-shadow: 0 24px 55px -32px rgba(0, 0, 0, 0.92);
  cursor: zoom-in;
}
img.demo-media--phone,
video.demo-media--phone {
  width: 100%;
  aspect-ratio: 61 / 45;
  object-fit: contain;
  object-position: center;
  margin-inline: auto;
}
.demo-media:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.demo-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2rem;
  margin: 0.65rem auto 0;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: 0.72rem;
  cursor: pointer;
}
.demo-toggle:hover { color: var(--text); border-color: var(--muted); }
.demo-toggle:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.demo-control-host { position: relative; }
.demo-toggle--overlay {
  position: absolute;
  z-index: 2;
  top: 0.65rem;
  right: 0.65rem;
  margin: 0;
  background: rgba(18, 18, 18, 0.9);
  color: #fff;
}
.lead__frame > .demo-media {
  height: 100%;
  aspect-ratio: auto;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.lead__frame--demo { aspect-ratio: 61 / 45; }
.hero__flow > .demo-media { width: 100%; }
.hero__flow > figcaption { margin-top: 0.7rem; }
.bcard > .demo-media { margin-bottom: 0.25rem; }
.picks .demo-media {
  width: 100%;
  max-width: none;
  aspect-ratio: 16 / 10;
  object-position: top center;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  box-shadow: none;
}
.picks .demo-media--desktop { object-fit: cover; }
.picks .demo-media--phone {
  width: 100%;
  object-fit: contain;
  object-position: center;
}
.section-demo {
  width: 100%;
  max-width: 64rem;
  margin: var(--gap-lg) auto 0;
}
.section-demo--wide { max-width: 72rem; }
.section-demo--phone { max-width: 64rem; }
.section-demo > .demo-media { width: 100%; }
.section-demo--phone .demo-media--phone {
  width: min(100%, 24rem);
  aspect-ratio: 780 / 1688;
  object-fit: contain;
  margin-inline: auto;
}
.section-demo > figcaption {
  margin-top: 0.7rem;
  color: var(--faint);
  text-align: center;
}
.grid > .section-demo {
  align-self: start;
  max-width: none;
  margin: 0;
}
.grid > .section-demo--phone {
  width: 100%;
  justify-self: stretch;
}
.demo-rotate { display: none; }

.lightbox--video .lightbox__panel--video {
  max-height: calc(100dvh - 2 * var(--lb-pad));
  gap: 0;
}
.lightbox__video {
  display: block;
  width: 100%;
  height: auto;
  max-height: calc(100dvh - 2 * var(--lb-pad));
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ui);
  background: transparent;
  box-shadow: 0 26px 70px -24px rgba(0, 0, 0, 0.85);
}

@media (max-width: 44rem) {
  .demo-rotate { display: block; margin-top: 0.65rem; text-align: center; }
}

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

/* The head-to-grid gap was written against the grid itself (`h2 + .bento`),
   so wrapping a grid in a carousel dropped it to zero and the cards sat hard
   against the lede. The carousel takes the same rhythm the grid had. */
.head + .car,
.wrap > .car:first-child { margin-top: var(--gap-md); }

/* Card width is (band width - gaps) / cards-across, so the only way to grow the
   films without showing fewer of them is a wider band. These wraps hold nothing
   but the carousel - the section heading sits in its own wrap - so widening
   them enlarges every card and leaves the headings where they are. Browsers
   without :has() keep the standard reading width, which is merely smaller. */
.wrap:has(> .car:only-child) { max-width: 84rem; }
