/* ============================================================================
   Likidera — Shared components (v5 shell)
   Consumes tokens.css only. Mobile-first; min-width queries layer up.
   Order: ledger texture · section shell · buttons · header/nav · drawer ·
   surfaces · badges · chart · table · accordion · forms · modal · footer.
   The chart, table, badge and status blocks are the shared library for the
   inner pages; the homepage uses the chart classes in its closing motif.
   ============================================================================ */

/* ═══════════════ LEDGER TEXTURE ═══════════════
   Fine ruled lines echoing finance graph paper. ~4% contrast, masked so it
   fills the flanks without sitting under long copy. Shared across pages. */
.grid-texture {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--text) 4%, transparent) 0 1px, transparent 1px 56px),
    repeating-linear-gradient(0deg,
      color-mix(in srgb, var(--text) 4%, transparent) 0 1px, transparent 1px 56px);
  -webkit-mask-image: radial-gradient(120% 110% at 62% 0%, #000 32%, transparent 78%);
  mask-image: radial-gradient(120% 110% at 62% 0%, #000 32%, transparent 78%);
}
/* Full-width band: content sits on its graph paper. */
.grid-texture--spread {
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 60%, transparent 96%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 60%, transparent 96%);
}
/* Centered pool for closing bands. */
.grid-texture--band {
  -webkit-mask-image: radial-gradient(85% 90% at 50% 24%, #000 25%, transparent 78%);
  mask-image: radial-gradient(85% 90% at 50% 24%, #000 25%, transparent 78%);
}

/* ═══════════════ SECTION SHELL ═══════════════ */
.section {
  padding-block: var(--section-y);
  position: relative;
}
/* Seams are a plain hairline; the ruler appears only where it means something. */
.section + .section { border-top: 1px solid var(--rule); }
.section--tight { padding-block: var(--section-y-tight); }
/* Alternating rhythm: a half-step surface lift. */
.section--tint { background: var(--section-tint); }

/* Section masthead: the eyebrow's rule runs to the container edge. */
.section .eyebrow::after {
  content: "";
  flex: 1 1 auto;
  min-width: 32px;
  height: 1px;
  background: var(--rule);
}

.section__title {
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-heading);
  max-width: 22ch;
  margin-top: var(--space-4);
}
.section__title + .lead { margin-top: var(--space-3); }

/* Masthead: eyebrow rule across, then title and lead as a pair split by a
   hairline — short titles sit composed beside tall leads. */
.section-head { display: grid; }
.section-head > .lead { max-width: 60ch; }
/* Not 900: there the lead column is 335px / 38 characters, which is a
   newspaper column, and no character budget can hold a line count across a
   335-487px swing. At 1180 the column is 452px / 51ch (elegance pass R6). */
@media (min-width: 1180px) {
  .section-head {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--space-7);
    align-items: center;
  }
  .section-head .eyebrow { grid-column: 1 / -1; }
  .section-head .section__title { grid-column: 1; grid-row: 2; }
  .section-head .lead {
    grid-column: 2;
    grid-row: 2;
    margin-top: var(--space-4);
    padding: 0.35rem 0 0.35rem var(--space-6);
    border-left: 1px solid var(--rule);
  }
}

/* A head that stacks at every width instead of taking the 7/5 split. Used
   where the body under it is itself two columns: a two-column head above a
   two-column body makes four quadrants per section, and the head's 5/12 lead
   would sit above the body's 6/12 column at a different width. Stacked, the
   eyebrow, the title and the lead share one left edge and one measure with the
   body under them. Ten uses: seven on Product, three on Pricing.
   Placed after the 1180 block deliberately — it carries the same specificity,
   so source order is what makes it win. */
.section-head--stacked { grid-template-columns: minmax(0, 1fr); }
.section-head--stacked .eyebrow,
.section-head--stacked .section__title,
.section-head--stacked .lead { grid-column: 1; grid-row: auto; }
.section-head--stacked .lead { margin-top: var(--space-3); padding: 0; border-left: 0; }
/* From 900 the stacked lead keeps the rule and indent that make it read as a
   lead — the .page-head__lead treatment, at the same breakpoint and for the
   same reason. Below that the indent costs measure a phone cannot spare. */
@media (min-width: 900px) {
  .section-head--stacked .lead {
    padding-left: var(--space-6);
    border-left: 1px solid var(--rule);
  }
}

/* Text link with an arrow: the site's secondary action. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  justify-self: start;
  text-align: start;   /* a <button> centres its label by default */
  color: var(--brand);
  font-weight: var(--weight-medium);
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  transition: color var(--duration-fast) var(--ease-out);
}
.link-arrow:hover { color: var(--text); }
.link-arrow svg { flex: none; }

/* Inline link. base.css sets a { color: inherit; text-decoration: none }, and
   every cross-link on the built pages is a .link-arrow, so a link inside a
   sentence would be invisible — a 1.4.1 failure. The underline, not the
   colour, is what identifies it. */
.link-inline {
  color: var(--brand);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color var(--duration-fast) var(--ease-out);
}
.link-inline:hover { color: var(--brand-hover); }

/* ═══════════════ SPOT ILLUSTRATIONS ═══════════════
   One family: 1.8 stroke, round joins, muted base strokes, teal for the
   Likidera element of each scene, dashes for future/denied, soft fills. */
.spot { display: block; width: 100%; height: auto; }
.spot__stroke {
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.spot__accent { stroke: var(--brand); }
.spot__dash { stroke-dasharray: 4 4; }
.spot__bold { stroke-width: 2.2; }
.spot__fill { fill: color-mix(in srgb, var(--brand) 12%, transparent); }
.spot__soft { fill: var(--surface-2); }
.spot__point { fill: var(--brand); }
.spot__dot { fill: var(--text-faint); }

/* Small inline icon in the same stroke language (20–32px). */
.icon {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon .spot__accent { stroke: var(--brand); }
.icon .spot__point { fill: var(--brand); stroke: none; }
.icon .spot__fill { fill: color-mix(in srgb, var(--brand) 12%, transparent); }
.icon .spot__dash { stroke-dasharray: 3 3; }
.icon .spot__faint { stroke: var(--text-faint); opacity: 0.6; }


/* ═══════════════ BUTTONS ═══════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 0.7rem 1.15rem;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  border: 1px solid transparent;
  white-space: nowrap;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}
.btn svg { flex: none; }

.btn--primary {
  background: var(--brand);
  color: var(--on-brand);
}
/* Hover warmth comes from the accent itself: lighter fill + faint teal cast. */
.btn--primary:hover {
  background: var(--brand-hover);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--brand) 30%, transparent);
}

.btn--ghost {
  border-color: var(--border-strong);
  color: var(--text);
}
.btn--ghost:hover {
  border-color: var(--brand);
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 7%, transparent);
}

/* Press reads as motion — the only feedback on touch. */
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: default; transform: none; }

.btn--lg { padding: 0.9rem 1.5rem; font-size: var(--text-body); }
.btn--block { width: 100%; }


/* ═══════════════ HEADER / NAV ═══════════════ */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--bg);
  border-bottom: 1px solid transparent;
  transition:
    border-color var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease-out);
}
.site-header.is-scrolled { border-bottom-color: var(--rule); }
/* Frosted header once scrolled; solid --bg where blur is unsupported. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .site-header.is-scrolled {
    background: var(--header-bg-scrolled);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--header-h);
}

/* Logo lockup: badge, then the wordmark with the tagline set under it, all
   inside the one link (owner, 2026-10-22: beside the logo, behind a rule, the
   tagline read as a menu item). Same lockup in header, sheet, footer and 404,
   at every width. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  flex: none;
}
.brand__text {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.brand__word {
  font-size: 1.25rem;
  font-weight: var(--weight-bold);
  letter-spacing: -0.03em;
  line-height: 1;
}
.brand__tagline {
  font-size: 0.6875rem;
  font-weight: var(--weight-medium);
  letter-spacing: 0.01em;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--text-muted);
}

.site-nav { display: none; }

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
  margin-left: auto;
}
.header-actions .btn--primary { display: none; }

/* Segmented language control — reads as a control, not a nav link. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  height: 36px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
/* At touch widths the two links are 44 wide, and their hit box reaches 4px
   above and below the 36px they draw inside the control's padding, to 44. */
@media (pointer: coarse), (max-width: 899px) {
  .lang-switch { height: 44px; }
  .lang-switch a { position: relative; min-width: 44px; justify-content: center; }
  .lang-switch a::before { content: ""; position: absolute; inset: -4px 0; }
}
/* The lockup carries its tagline at every width, which makes it wider than the
   old badge + wordmark. Under 480 the theme toggle leaves the header for the
   drawer's utility row, which carries it anyway. Under 400 the cluster and the
   lockup close up. Under 360 (the 320px phones) the header drops the tagline;
   the drawer and the footer keep it. */
@media (max-width: 479px) {
  .header-actions [data-theme-toggle] { display: none; }
}
@media (max-width: 399px) {
  .site-header__inner { gap: var(--space-2); }
  .header-actions { gap: var(--space-2); }
  .brand { gap: 0.5rem; }
  .brand__tagline { font-size: 0.625rem; letter-spacing: 0; }
}
@media (max-width: 359px) {
  .site-header .brand__tagline { display: none; }
}
.lang-switch a {
  display: inline-flex;
  align-items: center;
  height: 100%;
  padding-inline: 0.5rem;
  border-radius: 3px;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-faint);
  transition:
    color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}
.lang-switch a:hover { color: var(--text); }
.lang-switch a.is-active {
  background: var(--surface-2);
  color: var(--text);
  box-shadow: var(--edge-highlight);
}

/* Footer language link: plain text, as on the root site. */
.lang-link {
  display: inline-flex;
  align-items: center;
  padding-block: 0.35rem;
  font-size: var(--text-sm);
  color: var(--text-muted);
  transition: color var(--duration-fast) var(--ease-out);
}
.lang-link:hover { color: var(--text); }

.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  color: var(--text-muted);
  transition:
    color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}
.icon-button:hover { color: var(--text); border-color: var(--border-strong); }
@media (pointer: coarse), (max-width: 899px) {
  .icon-button { width: 44px; height: 44px; }
}

/* Theme toggle: moon in light ("go dark"), sun in dark ("go light").
   Light is the default, so the moon is the base state. */
.theme-toggle__sun { display: none; }
[data-theme="dark"] .theme-toggle__moon { display: none; }
[data-theme="dark"] .theme-toggle__sun { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .theme-toggle__moon { display: none; }
  :root:not([data-theme]) .theme-toggle__sun { display: block; }
}

.menu-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  color: var(--text);
}
@media (pointer: coarse), (max-width: 899px) {
  .menu-button { width: 44px; height: 44px; }
}
.menu-button span {
  display: block;
  width: 16px;
  height: 1.5px;
  background: currentColor;
  position: relative;
}
.menu-button span::before,
.menu-button span::after {
  content: "";
  position: absolute;
  left: 0;
  width: 16px;
  height: 1.5px;
  background: currentColor;
}
.menu-button span::before { top: -5px; }
.menu-button span::after { top: 5px; }

@media (min-width: 640px) {
  .header-actions .btn--primary { display: inline-flex; }
}
/* A nav label never breaks mid-phrase. Without this the seventh item does not
   overflow, it silently wraps "Why Likidera" onto two lines. Bound to the
   class: the Product item now carries a panel of links under it, and a rule
   on .site-nav a would reach them. */
.site-nav__link { white-space: nowrap; }

/* 1280, not 1180: seven items need 572px intrinsic and the nav has 536 at
   1180, so the labels wrapped. 1280 is --container, and the width at which
   .container already steps its padding — the nav and the full-width container
   arrive together. Only the header block moves; the three other 1180
   breakpoints are content splits with their own reasons. */
@media (min-width: 1280px) {
  .site-nav {
    display: flex;
    align-items: center;
    align-self: stretch;          /* full header height: the Product item hangs its panel from it */
    gap: var(--space-5);
    margin-left: auto;
  }
  .site-nav__link {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-muted);
    padding-block: 0.35rem;
    border-bottom: 1px solid transparent;
    transition:
      color var(--duration-fast) var(--ease-out),
      border-color var(--duration-fast) var(--ease-out);
  }
  .site-nav__link:hover,
  .nav-item.is-open .site-nav__link { color: var(--text); }
  .site-nav__link[aria-current="page"] {
    color: var(--text);
    border-bottom-color: var(--brand);
  }
  .header-actions { margin-left: 0; }
  /* The action cluster separates from the nav with one hairline. */
  .header-actions::before {
    content: "";
    width: 1px;
    height: 20px;
    background: var(--border);
    margin-right: var(--space-1);
  }
  .menu-button { display: none; }
}

/* ---- Product item: link, disclosure, panel ------------------------------
   The link navigates; the button opens. Hover with intent is added by ui.js
   for fine pointers only, so a keyboard or a touch never meets a panel that
   opens on its own. The item spans the header's height so the cursor's path
   from the link down to the panel never leaves it. */
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  align-self: stretch;
  gap: 2px;
}
/* 24 wide, not 36: the seven-item nav sits at its breakpoint with little to
   spare at 1280. Full row height, so the target is 24x68. */
.nav-item__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  align-self: stretch;
  color: var(--text-faint);
  transition: color var(--duration-fast) var(--ease-out);
}
.nav-item__toggle svg { transition: transform var(--duration-fast) var(--ease-out); }
.nav-item__toggle:hover,
.nav-item.is-open .nav-item__toggle { color: var(--text); }
.nav-item.is-open .nav-item__toggle svg { transform: rotate(180deg); }
.nav-item__toggle:focus-visible { outline-offset: -2px; }

/* The panel hangs from the header's foot with its first label on the link's
   left edge: panel padding plus item padding, taken back. 55rem holds two
   columns of the six sections with each title on one line, and the capture
   beside them; the width is derived under .mega__list. The 8px of clear
   space above the card is inside .mega, so the cursor crossing it is still
   inside the item. */
.mega {
  position: absolute;
  top: 100%;
  left: calc(-1 * (var(--space-5) + var(--space-3)));
  width: 55rem;
  padding-top: var(--space-2);
  animation: pop-rise var(--duration-base) var(--ease-out) both;
}
.mega__panel {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--edge-highlight), var(--shadow-pop);
}
/* Three columns: the six sections in two, and the Daily Tasks capture in a
   third that spans all three rows. The panel's left edge is 44px left of the
   Product link, which sits 406px into the header's 1280px container at every
   nav width, so 886px is all the room there is to the container's right
   padding — the 55rem panel uses 880 of it. That leaves each section column
   263px, 239 inside the item's padding; the longest label, "Budgeting &
   Profitability", measures 221px (2026-10-26). Re-measure if a label
   changes. */
.mega__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)) minmax(0, 16rem);
  gap: var(--space-1) var(--space-5);
}
/* One block per section: the index bar's label over the section's own
   title. The whole block is the link; the surface lift is the hover, as on
   the language control. */
.mega__item {
  display: grid;
  /* start, not the default stretch: the item fills its grid cell (height:
     100%), and stretch hands the surplus of a one-line description to the
     gap between label and description — 36px beside a two-line neighbour's
     26px (owner, 2026-09-10). Packed at the top, the surplus falls below the
     description, where it belongs. */
  align-content: start;
  gap: var(--space-1);
  height: 100%;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  transition: background-color var(--duration-fast) var(--ease-out);
}
.mega__item:hover { background: var(--surface-2); }
.mega__label {
  font-size: var(--text-label);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text);
  transition: color var(--duration-fast) var(--ease-out);
}
.mega__item:hover .mega__label { color: var(--brand); }
/* The seventh item: the Today screen, linked to the Today section. Its whole
   frame is the link, and it answers hover the way the six do — the edge turns
   brand — plus a 2px lift, the one motion a picture can make without moving
   anything around it. "span 3", not "1 / -1": the rows are implicit, so -1 is
   line 1 and the frame would take one row and push Group into its column. The
   link's 12px of block padding is the items' own, so the frame's top sits on
   the first label's top line and its foot on the last description's; the crop
   is cut to that height (604x618 at 256px wide, a T2 Detail with no caption
   bar — the 28px the bar used to take is 28px more of the table). */
.mega__media { grid-column: 3; grid-row: 1 / span 3; }
.mega__media-link {
  display: block;
  padding-block: var(--space-3);
  border-radius: var(--frame-radius);
}
.mega__media .shot__frame {
  transition:
    transform var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}
.mega__media-link:hover .shot__frame,
.mega__media-link:focus-visible .shot__frame {
  transform: translateY(-2px);
  border-color: var(--brand);
}
.mega__desc {
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--text-muted);
  text-wrap: pretty;
}
/* The foot: the page itself, one link, on the labels' left edge. Block, not
   .link-arrow's inline-flex, so the rule runs the panel's full width under
   both columns rather than stopping at the link's own extent. */
.mega__all {
  display: flex;
  /* .link-arrow sets justify-self: start, and Chromium now honours box
     alignment in block layout, so without this the foot shrink-wraps to
     289px and its rule stops at the left column. */
  justify-self: stretch;
  margin: var(--space-4) var(--space-3) 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--rule);
  border-bottom: 0;
  font-size: var(--text-sm);
}


/* ═══════════════ DRAWER (mobile menu) ═══════════════
   The sheet is the backdrop; the panel slides in from the right. */
.sheet {
  position: fixed;
  inset: 0;
  z-index: var(--z-sheet);
  background: var(--backdrop);
}
.sheet[hidden] { display: none; }

.sheet__panel {
  position: absolute;
  top: 0;
  right: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--space-4) var(--space-6);
  background: var(--bg);
  border-left: 1px solid var(--rule);
  box-shadow: var(--shadow-pop);
  overflow-y: auto;
}
@media (min-width: 480px) {
  .sheet__panel { width: min(var(--drawer-w), 100vw); }
}

.sheet__top {
  display: flex;
  align-items: center;
  min-height: var(--header-h);
}
.sheet__close { margin-left: auto; color: var(--text); }

.sheet__nav {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-4);
}
.sheet__link {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-height: 56px;
  padding-inline: var(--space-3);
  border-bottom: 1px solid var(--rule);
  font-size: 1.25rem;
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-heading);
}
.sheet__link[aria-current="page"] { box-shadow: inset 2px 0 0 var(--brand); }

/* The Product row folds its six sections under it. Closed by default: open,
   the six rows push Request a Demo below an 844px viewport, and a reader who
   opened the drawer for another page should not meet them. The toggle shares
   the row's hairline so the row stays one object. */
.sheet__group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
}
.sheet__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  min-height: 56px;
  border-bottom: 1px solid var(--rule);
  color: var(--text-muted);
}
.sheet__toggle svg { transition: transform var(--duration-fast) var(--ease-out); }
.sheet__group.is-open .sheet__toggle svg { transform: rotate(180deg); }
.sheet__toggle:focus-visible { outline-offset: -3px; }
.sheet__sub {
  flex: 1 0 100%;
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--rule);
}
/* The .module-map rule at drawer scale: one left hairline is the whole
   "these belong to Product" reading. */
.sheet__sub li { border-left: 1px solid var(--rule); }
.sheet__sublink {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--space-4);
  font-size: var(--text-body);
  color: var(--text-muted);
  transition: color var(--duration-fast) var(--ease-out);
}
.sheet__sublink:hover { color: var(--text); }

.sheet__footer {
  margin-top: auto;
  padding-top: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.sheet__utils {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--space-3);
}


/* ═══════════════ SURFACES: CARD · CALLOUT ═══════════════ */
.card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--edge-highlight), var(--shadow-card);
}
.card--quiet { box-shadow: none; }
/* Interactive cards never jump; only the edge warms. */
.card--hover {
  transition:
    border-color var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}
.card--hover:hover {
  border-color: color-mix(in srgb, var(--brand) 38%, var(--border));
}
/* A plated card cannot warm an edge that is already --brand, so warming it is
   a no-op and the card stops responding. It lifts instead. */
.card--hover.plate:hover {
  border-color: var(--brand-hover);
  box-shadow: inset 0 1px 0 var(--plate-inner), var(--shadow-pop);
}

/* Framed emphasis: card surface + teal-tinted edge + 3px spine.
   Body text stays at full contrast — this surface is never softened. */
.callout {
  position: relative;
  overflow: hidden;
  padding: var(--space-5);
  padding-left: calc(var(--space-5) + 3px);
  border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--border));
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--edge-highlight), var(--shadow-card);
}
.callout::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--brand);
}
.callout p { color: var(--text); line-height: 1.62; }

/* ═══════════════ QUESTIONS LAYOUT ═══════════════
   Heading and the group link in the left column, the accordion in the right.
   Moved here from forecasting.css when the FAQ page became the second
   consumer: Forecasting uses it once, the FAQ five times. It is what gives a
   text-only page its density — a question list must not be solved by centring
   one narrow column. */
.questions__layout { display: grid; gap: var(--space-6); }
.questions__links {
  display: grid;
  gap: var(--space-3);
  justify-items: start;
}
@media (min-width: 900px) {
  .questions__layout {
    grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
    column-gap: var(--space-8);
    align-items: start;
  }
  .questions__links { margin-top: var(--space-5); }
}
/* The left column is a label of fixed height beside a column that grows as the
   reader opens answers, so it overhangs by (accordion - label) — a function of
   the answers, not of the item count. Sticky is the site's answer to a short
   column beside a long one (05 §5.1), and it keeps the heading and its link
   beside whichever question is open. Two columns in one row, so 05 §18's
   multi-row sticky constraint is not engaged. */
@media (min-width: 900px) {
  .questions__layout > :first-child {
    position: sticky;
    top: calc(var(--header-h) + var(--space-5));
  }
}


/* ═══════════════ BADGES · CHIPS · STATUS ═══════════════ */
.badge {
  display: inline-block;
  align-self: start;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-md);
  font-size: var(--text-micro);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}
.badge--income { background: color-mix(in srgb, var(--income) 18%, transparent); color: var(--income); }
.badge--expense { background: color-mix(in srgb, var(--expense) 18%, transparent); color: var(--expense); }

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.chip strong { font-weight: var(--weight-semibold); color: var(--text); }
.chip--income { border-color: color-mix(in srgb, var(--income) 40%, var(--border)); }
.chip--income strong { color: var(--income); }
.chip--expense { border-color: color-mix(in srgb, var(--expense) 40%, var(--border)); }
.chip--expense strong { color: var(--expense); }
.chip--warning { border-color: color-mix(in srgb, var(--warning) 40%, var(--border)); }
.chip--warning strong { color: var(--warning); }

.status {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-micro);
  font-weight: var(--weight-medium);
  background: color-mix(in srgb, var(--income) 16%, transparent);
  color: var(--income);
  white-space: nowrap;
}
.status--watch {
  background: color-mix(in srgb, var(--warning) 18%, transparent);
  color: var(--warning);
}

.flow-in { color: var(--income); }
.flow-out { color: var(--expense); }


/* ═══════════════ CHART LANGUAGE ═══════════════
   Drawing law across every chart: realized solid, forecast dashed,
   the TODAY axis between them. */
.chart { margin: 0; }

.plot { width: 100%; height: auto; display: block; }

.plot__grid { stroke: var(--chart-grid); stroke-width: 1; }

.plot__label {
  fill: var(--text-faint);
  font-size: var(--plot-label-size, 12px);
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums slashed-zero;
  letter-spacing: var(--tracking-data);
}
.plot__label--end { text-anchor: end; }

.plot__realized {
  fill: none;
  stroke: var(--chart-realized);
  stroke-width: 2.25;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.plot__forecast {
  fill: none;
  stroke: var(--chart-forecast);
  stroke-width: 2;
  stroke-dasharray: 6 5;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: 0.85;
}
.plot__band { fill: var(--chart-band); }
.plot__area { fill: var(--chart-area); }
.plot__deviation { fill: var(--chart-deviation); }

.plot__today { stroke: var(--chart-today); stroke-width: 1; }
.plot__point { fill: var(--chart-realized); }

.chart-legend {
  margin-top: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  color: var(--text-faint);
}
.chart-legend span { display: inline-flex; align-items: center; gap: var(--space-2); }
.chart-legend i {
  display: inline-block;
  width: 18px;
  height: 0;
  border-top: 2px solid var(--brand);
}
.chart-legend i.is-dashed { border-top-style: dashed; }
.chart-legend i.is-thick { border-top-color: var(--chart-series-3); border-top-width: 3px; }
.chart-legend i.is-area {
  height: 10px;
  border-top: 0;
  border-radius: 2px;
  background: var(--chart-deviation);
}


/* ═══════════════ ACCORDION (FAQ) ═══════════════ */
.accordion {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--edge-highlight), var(--shadow-card);
  overflow: hidden;
}
.accordion__item + .accordion__item { border-top: 1px solid var(--border); }

.accordion__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  min-height: 44px;
  padding: 1.15rem var(--space-4);
  cursor: pointer;
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading);
  list-style: none;
  transition: background-color var(--duration-fast) var(--ease-out);
}
.accordion__question:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.accordion__question::-webkit-details-marker { display: none; }
.accordion__question::marker { content: ""; }

/* Plus mark from the hairline vocabulary; collapses to minus when open. */
.accordion__icon {
  position: relative;
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--text-faint);
  transition: color var(--duration-fast) var(--ease-out);
}
.accordion__icon::before,
.accordion__icon::after {
  content: "";
  position: absolute;
  background: currentColor;
  border-radius: 1px;
  transition:
    transform var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
}
.accordion__icon::before { top: 8.25px; left: 0; width: 18px; height: 1.5px; }
.accordion__icon::after { left: 8.25px; top: 0; width: 1.5px; height: 18px; }

.accordion__question:hover .accordion__icon,
.accordion__item[open] .accordion__icon { color: var(--brand); }
.accordion__item[open] .accordion__icon::after { transform: scaleY(0); opacity: 0; }

.accordion__answer { padding: 0 var(--space-4) var(--space-5); }
.accordion__answer p {
  color: var(--text-muted);
  font-size: var(--text-sm);
  line-height: 1.65;
  max-width: 62ch;
}

@media (min-width: 640px) {
  .accordion__question { padding-inline: var(--space-5); }
  .accordion__answer { padding-inline: var(--space-5); }
}

/* Smooth open where supported; instant elsewhere — no information loss. */
@supports (interpolate-size: allow-keywords) {
  .accordion__item { interpolate-size: allow-keywords; }
  .accordion__item::details-content {
    block-size: 0;
    overflow: hidden;
    transition-property: block-size, content-visibility;
    transition-duration: var(--duration-base);
    transition-timing-function: var(--ease-out);
    transition-behavior: allow-discrete;
  }
  .accordion__item[open]::details-content { block-size: auto; }
}


/* ═══════════════ FORMS ═══════════════ */
/* align-content: a paired field must not stretch its control to match an
   error line under its neighbour. */
.field { display: grid; align-content: start; gap: var(--space-1); }
.field__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
}
.field__optional { color: var(--text-faint); font-weight: var(--weight-regular); }
.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 3.125rem;
  padding: 0.65rem 0.8rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  transition: border-color var(--duration-fast) var(--ease-out);
}
.field input::placeholder,
.field textarea::placeholder { color: var(--text-faint); opacity: 1; }
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible { border-color: var(--brand); }
.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--expense); }
.field textarea { resize: vertical; min-height: 5.5rem; line-height: 1.5; }
.field__error { font-size: var(--text-xs); color: var(--expense); }

/* A native select on the site's border. The wrapper draws the arrow so it
   takes currentColor and re-themes with the control; the placeholder option
   reads as one while it is the value. */
.field__select { position: relative; display: block; }
.field__select::after {
  content: "";
  position: absolute;
  right: 1rem;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--text-muted);
  border-bottom: 1.5px solid var(--text-muted);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}
.field select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 2.6rem;
  line-height: var(--leading-body);
}
.field select:invalid { color: var(--text-faint); }
.field select:has(option[value=""]:checked) { color: var(--text-faint); }
.field select option { color: var(--text); }

/* Ten fields in one column would be a wall: pairs that belong together share
   a row once the modal is wide enough for two controls. */
.field-row { display: grid; gap: var(--space-4); }
@media (min-width: 640px) {
  .field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Bot trap: hidden from everyone, present in the form data. */
.honeypot {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.form-note {
  font-size: var(--text-xs);
  color: var(--text-faint);
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  line-height: 1.5;
}
.form-note svg { flex: none; margin-top: 2px; }
/* The contact note has no icon: it indents to the first note's text (14px is
   the lock's width) and sits a half step under it rather than a full field
   gap, so the two read as one footnote. */
.form-note--contact {
  display: block;
  padding-left: calc(14px + var(--space-2));
}
.form-note + .form-note--contact { margin-top: calc(var(--space-2) - var(--space-4)); }
.form-status { font-size: var(--text-xs); color: var(--expense); }


/* ═══════════════ MODAL ═══════════════ */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--backdrop);
  display: grid;
  place-items: center;
  padding: var(--space-4);
  overflow-y: auto;
}
.modal-overlay[hidden] { display: none; }

/* The modal is capped to the viewport and the view inside it scrolls, so a
   ten-field form on a 650px laptop or a phone keeps its submit button
   reachable, the close button keeps its corner, and the overlay's centring
   never clips the top. The view's padding is pulled out to the modal's edges
   so the scrollbar sits at the frame and focus rings are not clipped. */
.modal {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(38rem, 100%);
  max-height: calc(100vh - 2 * var(--space-4));
  max-height: calc(100dvh - 2 * var(--space-4));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
  padding: var(--space-7) var(--space-5) var(--space-5);
}
.modal > [data-modal-view] {
  min-height: 0;
  overflow-y: auto;
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-5));
  padding: var(--space-2) var(--space-5);
}
@media (min-width: 640px) {
  .modal { padding: var(--space-7) var(--space-6) var(--space-6); }
  .modal > [data-modal-view] {
    margin-inline: calc(-1 * var(--space-6));
    padding-inline: var(--space-6);
  }
}

.modal__close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  color: var(--text-muted);
}
.modal__close:hover { color: var(--text); }

.modal__title { font-size: var(--text-h3); }
.modal__lead {
  margin-top: var(--space-3);
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.modal__form {
  margin-top: var(--space-5);
  display: grid;
  gap: var(--space-4);
}
.modal__thanks { text-align: center; }
.modal__check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid var(--brand);
  color: var(--brand);
  margin-bottom: var(--space-4);
}
.modal__actions { margin-top: var(--space-5); }
.modal__contact {
  margin-top: var(--space-5);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
/* Contact links (card, form note, thanks view): full ink and a hairline
   underline at rest, so a link inside a muted sentence is a link by more
   than its colour; the underline fills on hover. On the plate --text is the
   plate's own. */
.cta__contact a,
.form-note--contact a,
.modal__contact a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-underline-offset: 0.18em;
  padding-block: 0.25rem;   /* inline: no layout change, a 24px hit box at 13px type */
  transition: text-decoration-color var(--duration-fast) var(--ease-out);
}
.cta__contact a:hover,
.form-note--contact a:hover,
.modal__contact a:hover { text-decoration-color: currentColor; }

/* Entry motion on token durations: zero under reduced motion. */
.modal-overlay { animation: overlay-fade var(--duration-fast) var(--ease-out) both; }
.modal { animation: pop-rise var(--duration-base) var(--ease-out) both; }
.sheet { animation: overlay-fade var(--duration-fast) var(--ease-out) both; }
.sheet__panel { animation: drawer-in var(--duration-base) var(--ease-out) both; }
@keyframes overlay-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes pop-rise {
  from { opacity: 0; transform: translate3d(0, 6px, 0); }
  to { opacity: 1; transform: none; }
}
@keyframes drawer-in {
  from { transform: translate3d(100%, 0, 0); }
  to { transform: none; }
}


/* ═══════════════ BACK TO TOP ═══════════════
   One icon-only button, bottom right, on every page but the 404. ui.js
   un-hides it once the page has scrolled a viewport and hides it again below
   that, through the hidden attribute, so it is never in the tab order while
   invisible; body.is-locked (modal or drawer open) hides it too. It shares
   the index bar's layer: the bar is pinned at the top, this sits bottom
   right, and the two never meet. Entry is the modal's pop-rise on the base
   duration, which reduced motion zeroes at the token. */
.to-top {
  position: fixed;
  right: var(--space-5);
  bottom: calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
  z-index: var(--z-float);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--edge-highlight), var(--shadow-card);
  animation: pop-rise var(--duration-base) var(--ease-out) both;
  transition:
    color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}
.to-top:hover { color: var(--brand); border-color: var(--brand); }
.to-top[hidden],
.is-locked .to-top { display: none; }

/* ═══════════════ PAGE HEAD — the inner-page opening ═══════════════
   Inner pages get no slider, no ruler floor, no glow: the opening is a text
   instrument. Breadcrumb, eyebrow, H1 and a lead on the .section-head 7/5
   split, closed by a jump row. Shared by every inner page. */
.page-head { padding-block: var(--section-y); }
/* An inner page carries more blocks than the homepage, so its step is tighter. */
.page-inner .section { padding-block: var(--section-y-inner); }
/* R1: baseline, not start — start leaves the title cap ~8px off the lead and a
   near-miss reads worse than an obvious offset. Global: the homepage differs
   by only 6px between the two, so no override is needed. */
.section-head { align-items: baseline; }
/* A title binds down to what it introduces, so the head pays no bottom step of
   its own: the following section's top padding is the whole gap. Both at full
   strength put ~125px under a one-line H1 on the heads with no lead, which read
   as a hole (FAQ, Pricing). One step now, on every inner page. */
.page-inner .page-head { padding-block: var(--section-y-inner) 0; }
.page-inner #accuracy { padding-block: calc(var(--section-y-inner) + var(--space-5)); }
.page-head__inner { display: grid; }
/* The breadcrumb names the page, but the H1 one line below it says the same
   thing at scale, so the crumb stays a quiet trail and marks the current
   segment with weight and ink only. It takes the teal nick the eyebrow used to
   carry, because the eyebrow was printing the same word one line below on five
   of six inner pages and has been deleted. */
.page-head__crumbs {
  display: flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
}
.page-head__crumbs::before {
  content: "";
  flex: none;
  width: 28px;
  height: 1px;
  background: var(--brand);
}
.page-head__crumbs ol { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3); }
.page-head__crumbs li { display: inline-flex; align-items: center; gap: var(--space-2); }
.page-head__crumbs li + li::before { content: "\203a"; color: var(--text-faint); }
.page-head__crumbs a { color: var(--text-muted); }
.page-head__crumbs a:hover { color: var(--text); }
/* The current segment is marked, not enlarged. Weight and full-strength ink
   separate it from the trail at the trail's own size; raising it to heading
   scale made a navigation strip compete with the H1 one line below it. */
.page-head__crumbs [aria-current="page"] {
  font-weight: var(--weight-semibold);
  color: var(--text);
}
.page-head__title {
  margin-top: var(--space-5);
  font-size: var(--text-h1);
  letter-spacing: var(--tracking-heading);
  text-wrap: balance;
  max-width: 18ch;
}
/* The lead sits under the title at every width, one column (owner,
   2026-10-22): beside it, the old 7/5 split read as two separate blocks.
   Directly under the H1 it reads as the lead without the rule and indent the
   side-by-side version needed. 60ch is the section-head lead's measure. */
.page-head__lead {
  margin-top: var(--space-4);
  color: var(--text-muted);
  max-width: 60ch;
}
/* A head with no lead (Pricing) has no second line to pair with, so its title
   takes a wider measure from 1180. 32ch is there so a longer title wraps
   rather than reaching the container edge. */
@media (min-width: 1180px) {
  .page-head--single .page-head__title { max-width: 32ch; }
}

/* Jump targets: the header must not cover the heading they land on. */
[id] { scroll-margin-top: calc(var(--header-h) + var(--space-5)); }


/* ═══════════════ PAGE INDEX — the section anchors, pinned ═══════════════
   Moved here from product.css when Forecasting and Security became its
   second and third consumers (brief 12 §3). A page opts in with
   <html class="has-page-index">, which carries the two scroll-offset rules
   below; the bar is the same on all three.

   Not a hero jump row: nine blocks and 6,001px from the index to #budget at
   1440 leave a reader who reaches #treasury with no route back except the
   scroll bar. And not a left rail: at 1440 a 176px rail plus its gap takes the
   record's prose column from 70 to 58 characters, where a top bar costs 50px
   of viewport height and no horizontal room at all.

   The form is .fact-strip's vocabulary made sticky — six uppercase labels in
   six hairline-divided cells, the running head of a reference book. */

/* Header (68) + index bar (48 + two hairlines) + a 16px clearance. An anchor
   that lands at the shared [id] offset lands under the bar; scroll-margin on
   the target and scroll-padding on the scrollport both apply and add, so the
   target's own margin is zeroed rather than raised. The class is on <html>,
   not <body>: scroll-padding-top has to be set on the actual scrolling
   element, and this is the only way to reach it without :has(). */
.has-page-index {
  scroll-padding-top: calc(var(--header-h) + var(--index-h) + 2px + var(--space-4));
}
.has-page-index [id] { scroll-margin-top: 0; }

.page-index {
  /* The head no longer pays a bottom step, so the bar carries the gap itself.
     Sticky positions from the static box, so a margin here moves the bar's
     resting place and nothing about its pinned one. */
  margin-top: var(--section-y-inner);
  position: sticky;
  top: var(--header-h);
  z-index: var(--z-float);        /* under --z-header (100), over the content */
  /* Its own ground, not --bg: on --bg the bar was six hairlines and a row of
     faint labels, and read as a seam rather than a running head. */
  background: var(--index-bg);
  /* --border-strong, not --rule: a crisp edge top and bottom is half of what
     makes the band read as a band (owner, 2026-09-14). */
  border-block: 1px solid var(--border-strong);
  transition: background-color var(--duration-base) var(--ease-out);
}
/* Mirrors the header's frost — same alphas, same blur, one threshold in ui.js —
   but keeps its own value under it. The tint stays when pinned because the
   pinned state is where the bar spends the page: 6,001px of the 6,500 it
   serves. What made two pinned bars read as an accident was two treatments,
   not two values; frosted on frosted with a half-step between is one band. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .page-index.is-scrolled {
    background: var(--index-bg-scrolled);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }
}

/* minmax(max-content, 1fr) is the load-bearing line. With a plain 1fr
   "Budgeting & Profitability" (228px with its padding, in a 203px cell) wraps
   to two lines at every desktop width while the other five sit on one; with
   max-content alone Product's bar is left-aligned and leaves 308px of empty
   rail at 1440 (2026-10-26). This gives every cell its natural width and
   shares the surplus, so the cells encode the label lengths. */
.page-index__list {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(max-content, 1fr);
}
.page-index__list a {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--index-h);
  padding-inline: var(--space-3);
  font-size: var(--text-micro);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  /* --text, full contrast: the labels are the bar; muted read as skimmable
     (owner, 2026-09-14). */
  color: var(--text);
  border-left: 1px solid var(--border);
  /* Reserved, so marking a cell current shifts nothing and needs no
     transition — which is also why the active marker never slides. */
  box-shadow: inset 0 -2px 0 transparent;
  transition: color var(--duration-fast) var(--ease-out);
}
.page-index__list li:first-child a { border-left: 0; }
.page-index__list a:hover { color: var(--brand); }
/* The rule sits at the foot of the cell, where the header's own current
   marker sits — the two pinned bars mark "here" the same way. 4.68 against
   the ground in light, 8.78 in dark; the label at --text carries the rest. */
/* --brand-hover, not --brand: on the active cell's fill the light brand teal
   measured 3.68; teal-800 measures 5.10 (dark: teal-300, well clear). */
.page-index__list a[aria-current="true"] {
  color: var(--brand-hover);
  background: var(--index-active-bg);
  box-shadow: inset 0 -2px 0 var(--brand);
}
/* The bar is exactly 50px and the anchor offset is derived from it, so the
   ring is drawn inside the cell rather than outside it, where the scroller
   below 1000 would clip it. */
.page-index__list a:focus-visible { outline-offset: -3px; }

/* Product's six cells measure 908px with their padding and hairlines, in both
   languages (2026-10-26), so with the container's padding the fitted bar stops
   fitting at 957px of viewport; 1000 is the nearest number already in the
   system and leaves 43px of headroom. Below it the row scrolls rather than
   wrapping — the .page-head__jump pattern, reused.
   If any of the six labels changes, re-measure that total. */
@media (max-width: 999px) {
  .page-index__list {
    grid-auto-columns: max-content;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* A classic scrollbar would add ~15px to a bar whose 50px height the
       anchor offset is derived from. Scroll-snap makes the swipe land on a
       whole label. */
    scrollbar-width: none;
    /* The affordance: a fade at the right edge while the row has more, a fade
       at the left once it has scrolled. A mask on the scroller dissolves the
       labels into whatever the bar's ground is, so it survives the frosted
       state. Where scroll-driven animations exist the row's own scroll
       position drives both edges; elsewhere the right fade stands and the
       left never appears. Mid-word clipping under a fade is accepted. */
    --index-fade-l: 0px;
    --index-fade-r: var(--edge-fade);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--index-fade-l), #000 calc(100% - var(--index-fade-r)), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 var(--index-fade-l), #000 calc(100% - var(--index-fade-r)), transparent);
  }
  .page-index__list::-webkit-scrollbar { display: none; }
  /* A run of empty track after the last cell, so the fade at the end of the
     scroll covers track rather than the last label. A pseudo grid item, not
     inline-end padding: Safari has not always counted end padding as
     scrollable overflow. */
  .page-index__list::after { content: ""; width: var(--edge-fade); }
  .page-index__list a { justify-content: flex-start; white-space: nowrap; }
  .page-index__list li { scroll-snap-align: start; }
}
@property --index-fade-l { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --index-fade-r { syntax: "<length>"; inherits: false; initial-value: 0px; }
@supports (animation-timeline: scroll()) {
  @media (max-width: 999px) {
    .page-index__list {
      animation: index-fade-l linear both, index-fade-r linear both;
      animation-timeline: scroll(self inline), scroll(self inline);
      animation-range: 0 var(--edge-fade), calc(100% - var(--edge-fade)) 100%;
    }
  }
}
@keyframes index-fade-l {
  from { --index-fade-l: 0px; }
  to { --index-fade-l: var(--edge-fade); }
}
@keyframes index-fade-r {
  from { --index-fade-r: var(--edge-fade); }
  to { --index-fade-r: 0px; }
}


/* ═══════════════ STEPS — a small, numbered mechanism ═══════════════
   Genuinely sequential, so numbering is earned. One hairline runs through
   the node centres; the segment after the last node is dashed in --brand,
   because the process continues tomorrow. Shared with later pages. */
.steps {
  display: grid;
  gap: var(--space-5);
  counter-reset: none;
}
/* 4px of top padding centres the step title's first line on the 28px node;
   the column layout from 900 replaces it. */
.steps__item { position: relative; padding-left: 3.25rem; padding-top: 0.25rem; }
.steps__no {
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 1.5px solid var(--brand);
  border-radius: 50%;
  background: var(--surface);
  color: var(--brand);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* Vertical connector below 900: the root measurement timeline, upright. */
.steps__item::before {
  content: "";
  position: absolute;
  left: 13.5px;
  top: 28px;
  bottom: calc(-1 * var(--space-5));
  border-left: 1px solid var(--chart-axis);
}
.steps__item:last-child::before {
  bottom: -0.5rem;
  border-left-style: dashed;
  border-left-color: var(--brand);
}
.steps__item h3 {
  font-size: var(--text-body);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading);
}
.steps__item p {
  margin-top: var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-sm);
  max-width: 46ch;
}
/* The node is a <p> too, and the paragraph margin above would drop it 8px
   off the connector that runs through its centre. */
.steps__item .steps__no { margin-top: 0; }
@media (min-width: 900px) {
  .steps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-6);
  }
  .steps__item { padding-left: 0; padding-top: 3rem; }
  .steps__no { top: 0; left: 0; }
  /* The hairline now runs horizontally through the node centres. */
  .steps__item::before {
    left: 28px;
    right: calc(-1 * var(--space-6));
    top: 14px;
    bottom: auto;
    border-left: 0;
    border-top: 1px solid var(--chart-axis);
  }
  .steps__item:last-child::before {
    right: 0;
    border-top-style: dashed;
    border-top-color: var(--brand);
  }
}


/* ═══════════════ MATRIX KEY — how to read a pending frame ═══════════════
   A pending frame cannot teach itself, so a four-item key does, in DOM text
   that survives the swap. The fact-strip vocabulary, reused. No example
   values: the key describes structure, never results. */
.matrix-key {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-5);
}
.matrix-key div { min-width: 0; }
.matrix-key dt {
  font-size: var(--text-label);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-faint);
}
.matrix-key dd {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
@media (min-width: 640px) {
  .matrix-key { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5); }
}
@media (min-width: 900px) {
  .matrix-key { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }
  .matrix-key div { padding-inline: var(--space-4); border-left: 1px solid var(--rule); }
  .matrix-key div:first-child { padding-left: 0; border-left: 0; }
  .matrix-key div:last-child { padding-right: 0; }
}


/* ═══════════════ FACT STRIP ═══════════════
   Five statements on the homepage, three on Product. The homepage strip runs
   as a ruled list with the icon beside each statement → three over two →
   one row of five, the icon above its statement from 900, and no statement
   past two lines at any width. The three-item variants carry no icons, so
   :has(.icon) keeps the homepage's rules off them. */
/* balance: a two-line statement splits evenly instead of leaving its last
   word alone on the second line. */
.fact-strip {
  display: grid;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-muted);
  text-wrap: balance;
}
.fact-strip li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}
.fact-strip .icon { color: var(--brand); }
.fact-strip .figures { color: var(--text); }
/* One-line and two-line statements alternate, so rules between them hold the
   rhythm that a plain gap loses. */
@media (max-width: 899px) {
  .fact-strip:has(.icon) li + li {
    padding-top: var(--space-3);
    border-top: 1px solid var(--rule);
  }
}
/* The three-item variants go three across from 640 on this gap. */
@media (min-width: 640px) {
  .fact-strip { column-gap: var(--space-5); }
}
/* Stacked, a statement gets the whole column rather than the column less the
   icon, which is what keeps the longest to two lines from here up. */
@media (min-width: 900px) {
  .fact-strip li { flex-direction: column; }
  .fact-strip .icon { width: 28px; height: 28px; }
}
/* Three over two, ruled like the row of five. The second row splits the full
   width in half rather than centring under the first, so no corner is left
   empty. */
@media (min-width: 900px) and (max-width: 1159px) {
  .fact-strip { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .fact-strip li { grid-column: span 2; }
  .fact-strip li:nth-child(4) { grid-column: 1 / span 3; }
  .fact-strip li:nth-child(5) { grid-column: 4 / span 3; }
  .fact-strip:has(.icon) { gap: 0; }
  .fact-strip:has(.icon) li { padding: 0 var(--space-5) var(--space-5); }
  .fact-strip:has(.icon) li:nth-child(n+4) {
    padding-top: var(--space-5);
    padding-bottom: 0;
    border-top: 1px solid var(--rule);
  }
  .fact-strip:has(.icon) li:nth-child(3n+1) { padding-left: 0; }
  .fact-strip:has(.icon) li:nth-child(3),
  .fact-strip:has(.icon) li:last-child { padding-right: 0; }
  .fact-strip:has(.icon) li:not(:nth-child(3n+1)) { border-left: 1px solid var(--rule); }
}
@media (min-width: 1160px) {
  .fact-strip {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0;
  }
  .fact-strip li { padding-inline: var(--space-4); }
  .fact-strip li:first-child { padding-left: 0; }
  .fact-strip li:last-child { padding-right: 0; }
  .fact-strip li + li { border-left: 1px solid var(--rule); }
  /* Equal fifths are too narrow at 1160 for the longest statement to hold two
     lines. As a flex row each column shrinks in proportion to its statement,
     so all five break into the same two lines. The variants keep their equal
     grid, as does a browser without :has(). */
  .fact-strip:has(.icon) { display: flex; }
  .fact-strip:has(.icon) li { flex: 1 1 auto; }
}
@media (min-width: 1280px) {
  .fact-strip li { padding-inline: var(--space-5); }
}


/* ═══════════════ PAIR ROWS ═══════════════, deliberately not a table ═══════
   Two states of one job, side by side, deliberately not a table: one constant
   index column and two constant content columns, separated by hairlines.
   Why Likidera contrasts today with Likidera; Security contrasts what happens
   inside the product with what only a bank can do, where the right column is
   empty on purpose until the last row. */
.pair-rows { margin-top: var(--space-6); }
.pair-rows__head { display: none; }
.pair-rows__tag {
  font-size: var(--text-label);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-faint);
}
.pair-rows__row {
  display: grid;
  gap: var(--space-2);
  padding-block: var(--space-5);
  border-top: 1px solid var(--rule);
}
.pair-rows__row dt {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  color: var(--text-faint);
}
.pair-rows__row dd {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}
/* The state label is one element, read aloud at every width; below 1000 it is
   also the visible tag, because the stacked rows lose the column heads. */
.pair-rows__state {
  display: block;
  font-size: var(--text-micro);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--space-1);
}
@media (max-width: 999px) {
  .pair-rows__row .pair-rows__with { padding-top: var(--space-3); border-top: 1px solid var(--rule); }
}
@media (min-width: 1000px) {
  .pair-rows__state {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .pair-rows__head,
  .pair-rows__row {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 4.5fr) minmax(0, 4.5fr);
    column-gap: var(--space-6);
    align-items: start;
  }
  .pair-rows__head { padding-bottom: var(--space-3); }
  /* The wash is inset by its own padding, so it is pulled back by the same
     amount on three sides: its text then sits on the same line as the Today
     cell and under its own column head, and only the right edge stays put. */
  .pair-rows__row dd.pair-rows__with {
    background: color-mix(in srgb, var(--brand) 5%, transparent);
    margin: calc(-1 * var(--space-3)) 0 calc(-1 * var(--space-3)) calc(-1 * var(--space-3));
    padding: var(--space-3);
  }
}
.pair-rows dl { border-bottom: 1px solid var(--rule); }


.pair-rows__row--payload dd { color: var(--text); font-weight: var(--weight-medium); }


/* ═══════════════ SPEC LIST — term and value ═══════════════
   A datasheet: uppercase term against a value, hairline rows. The --loose
   modifier raises the term to --text and the value to body size, for the
   places where the pair is a definition rather than a specification.
   Security's #compliance and Product's #today. */
.spec-list { display: grid; margin-top: var(--space-6); }
.spec-list__row {
  display: grid;
  gap: var(--space-1);
  padding-block: var(--space-3);
  border-top: 1px solid var(--rule);
}
.spec-list dt {
  font-size: var(--text-label);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-faint);
}
.spec-list dd {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-muted);
}
.spec-list--loose__row { padding-block: var(--space-4); }
.spec-list--loose dt { color: var(--text); }
.spec-list--loose dd { font-size: var(--text-body); }
@media (min-width: 640px) {
  .spec-list--loose__row {
    grid-template-columns: 200px minmax(0, 1fr);
    column-gap: var(--space-6);
    align-items: baseline;
  }
}
@media (min-width: 900px) {
  .spec-list--loose { grid-template-columns: minmax(0, 1fr); }
}


/* ═══════════════ RECORD — description beside its exhibit ═══════════════
   A column of prose against a column of evidence, alternating side down a
   page. An even 6/6 at 1440 gives a 584px prose column (70 characters, the
   site's body measure) and a 584px exhibit in which a 16:10 frame renders
   584x394 — inside the 460px frame budget. Forecasting's #accuracy keeps a
   local 7/5 because its exhibit is a frame with an axis under it. */
.record { display: grid; gap: var(--space-6); margin-top: var(--space-6); }
.record__prose { min-width: 0; display: grid; gap: var(--space-5); align-content: start; }
.record__prose p { color: var(--text-muted); }
.record__exhibit { min-width: 0; display: grid; gap: var(--space-4); align-content: start; }

/* Below the split the exhibit stops short of the full measure: a capture
   across the full 720px at 768 is a 400px-tall dark block between two runs
   of prose; at 560 it is a figure set into the column, and its type is still
   read. An inset is that figure between two runs of prose at every width, so
   it takes the same cap. */
@media (max-width: 999px) {
  .record__exhibit,
  .record__inset { width: 100%; max-width: 560px; margin-inline: auto; }
}
/* 1000, not 1180: at 1000 the columns are 452px (54 characters), narrow but
   above the ~45ch floor, and splitting there rather than stacking to 1180
   saves ~900px of page. */
@media (min-width: 1000px) {
  .record {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--space-7);
    align-items: start;
  }
  .record--flip .record__prose { order: 2; }
  .record--flip .record__exhibit { order: 1; }
  /* align-items: start leaves the shorter column ragged; sticky turns that
     hole into a companion — the exhibit travels with the paragraph it
     evidences. It is layout, not motion, so reduced motion needs no gate. */
  .record__exhibit {
    position: sticky;
    top: calc(var(--header-h) + var(--index-h) + var(--space-5));
  }
}

/* A second capture set into the prose (.record__inset), directly after the
   paragraph it evidences. The exhibit column keeps the section's one window
   and stays sticky beside the whole column, so a record carries two or three
   captures without its exhibit stack outgrowing its prose — stacked under the
   first frame, the second one did exactly that in #today (713 against 377px
   at 1440) and #budget (692 against 296). The prose grid's own gap sets the
   inset apart from the paragraphs on both sides; it needs no rule of its own
   above the split. A sticky grid item travels the full height of .record, so
   nothing full-width may follow the two columns inside a record: a legend
   goes under its frame in the exhibit column, stacked. */


/* ═══════════════ NOTE ═══════════════
   A muted paragraph after a block: the standing form for a point that is
   prose rather than evidence, and the only body copy allowed below one. */
.note {
  margin-top: var(--space-5);
  color: var(--text-muted);
  max-width: 54ch;
}
.note + .note { margin-top: var(--space-4); }
/* A note that opens a section takes the section-opening gap, not its own. */
.section-head + .note { margin-top: var(--space-6); }


/* ═══════════════ QUESTION LIST ═══════════════
   Questions on a teal spine: the callout's spine at question scale. Shared —
   the homepage retired it, Why Likidera revived it. */
.question-list {
  margin-top: var(--space-6);
  border-left: 2px solid var(--brand);
  padding-left: var(--space-5);
}
.question-list li { padding-block: var(--space-4); }
.question-list li + li { border-top: 1px solid var(--rule); }
.question-list li:first-child { padding-top: 0; }
.question-list li:last-child { padding-bottom: 0; }
.question-list h3 {
  font-size: clamp(1.35rem, 1.15rem + 0.9vw, 1.7rem);
  font-weight: var(--weight-semibold);
  line-height: 1.2;
  letter-spacing: var(--tracking-heading);
}
.question-list p {
  margin-top: var(--space-2);
  color: var(--text-muted);
  max-width: 54ch;
}


/* ═══════════════ FIT LIST ═══════════════
   Teal checks for what fits. The --plain variant marks what does not with a
   faint square: this site never scores anyone, including its non-customers,
   so there is no cross and no warning colour. */
.fit__list {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
  color: var(--text-muted);
}
.fit__list li { display: flex; align-items: flex-start; gap: var(--space-2); }
.fit__list .icon { color: var(--brand); margin-top: 0.3em; }
.fit__list--plain li::before {
  content: "";
  flex: none;
  width: 3px;
  height: 3px;
  margin-top: 0.62em;
  background: var(--text-faint);
}


/* ═══════════════ CONSEQUENCE LINE ═══════════════
   The one place body-scale text runs at full contrast: a short teal rule and
   a dark line, so the eye learns that this pairing means "this is the point". */
.consequence {
  position: relative;
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  font-size: var(--text-lead);
  color: var(--text);
  max-width: 46ch;
}
.consequence::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 28px;
  height: 1px;
  background: var(--brand);
}


/* ═══════════════ HORIZON RULER ═══════════════
   A hairline with ticks at --ruler-pitch and tabular labels below. Labels are
   absolutely placed at proportional positions and backed with --bg so they
   sit on the line; edge labels align start/end so nothing leaves the
   container. The ruler itself bleeds edge to edge. */
.ruler {
  position: relative;
  height: 2.5rem;
}
.ruler__track {
  position: absolute;
  inset: 0 0 auto 0;
  height: 7px;
  background:
    linear-gradient(var(--ruler-line), var(--ruler-line)) 0 0 / 100% 1px no-repeat,
    repeating-linear-gradient(90deg, var(--ruler-tick) 0 1px, transparent 1px var(--ruler-pitch))
      0 0 / 100% 7px no-repeat;
}
.ruler > .container { height: 100%; }
.ruler__labels {
  position: relative;
  height: 100%;
}
.ruler__label {
  position: absolute;
  top: 14px;
  left: var(--at, 0%);
  transform: translateX(-50%);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-data);
  line-height: 1;
  white-space: nowrap;
  color: var(--ruler-label);
  background: var(--bg);
  padding-inline: 0.35em;
}
.ruler__label--start { transform: none; padding-left: 0; }
.ruler__label--end { transform: translateX(-100%); padding-right: 0; }
.ruler__label--today { color: var(--text-muted); }
/* TODAY node: teal point with a --bg ring so it sits on the line cleanly. */
.ruler__label--today::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -14px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ruler-today);
  box-shadow: 0 0 0 1.5px var(--bg);
  transform: translate(-50%, -50%);
}
.ruler__label--start.ruler__label--today::before { left: 0; }
/* Label density: two labels at 320, three from 480, all from 1150. */
.ruler__label--mid,
.ruler__label--near { display: none; }
@media (min-width: 480px) { .ruler__label--mid { display: block; } }
@media (min-width: 1150px) { .ruler__label--near { display: block; } }

/* ═══════════════ SCREENSHOT SLOT ═══════════════
   figure.shot: chrome bar + image inside an elevated frame, no caption. Two
   states share the frame. .shot--illus carries a drawn explanation on the
   page's own surfaces, so its chrome re-themes with the page. .shot--real
   carries a capture of the application, which is dark in every theme, so its
   frame is dark in every theme: the window is one object, and the page's
   theme only decides how its edge is drawn (the --shot-real-* tokens). */
.shot { margin: 0; }
.shot__frame {
  border: 1px solid var(--frame-border);
  border-radius: var(--frame-radius);
  background: var(--shot-bg);
  box-shadow: var(--edge-highlight), var(--shadow-card);
  overflow: hidden;
}
/* Height cap (frame budget). Teaser media sets its own explicit height, so
   this binds only where a frame sizes itself. */
.shot--illus .shot__frame { max-height: var(--shot-illus-h); }
.shot__chrome {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 28px;
  padding-inline: var(--space-3);
  background: var(--shot-chrome);
  border-bottom: 1px solid var(--frame-border);
}
/* The bar carries the screen name and nothing else: no window buttons of
   any operating system, so the frame presents the capture without claiming
   to be a window from any particular desktop. */
.shot__label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--text-micro);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Finished-illustration state: the frame carries a drawn explanation rather
   than a screenshot, so nothing is pending and no hatch applies. These rules
   were removed with the hero slider and are re-added for the Security teaser. */
.shot--illus .shot__body {
  /* An explicit height, not a ratio: with aspect-ratio plus a max-height the
     browser derives the width from the capped height too, which shrank the
     body to 768 of the frame's 1216. */
  height: calc(var(--shot-illus-h) - 28px);
  display: grid;
  place-items: center;
  padding: 6%;
}
.shot--illus .shot__illus-art { max-height: 100%; }

.shot--real .shot__frame {
  border-color: var(--shot-real-edge);
  background: var(--shot-real-bg);
  box-shadow: var(--shot-real-highlight), var(--shadow-real);
}
.shot--real .shot__chrome {
  background: var(--shot-real-chrome);
  border-bottom-color: var(--shot-real-rule);
}
.shot--real .shot__label { color: var(--shot-real-label); }
/* The capture keeps its own ratio and is never cropped by CSS: every file is
   already cut to what should be seen. width: 100% with height: auto, so the
   width and height attributes reserve the ratio before a lazy file arrives
   (with width: auto the box was 0px tall until then, and the frame grew on
   scroll). The height budget is a guard, not a clip: if it ever binds,
   contain letterboxes the capture on the app's own ground. It binds at no
   measured width. 28px is the chrome bar. */
.shot__img {
  display: block;
  width: 100%;
  height: auto;
  max-height: calc(var(--shot-real-h) - 28px);
  object-fit: contain;
}


/* ═══════════════ SCREENSHOT TREATMENTS ═══════════════
   Five named distances onto the same twenty captures. The hierarchy is bought
   with distance, scale and overlap — all properties of the content — and never
   with a tilted plane: the product installs on the customer's own server, so
   there is no device to render and no browser to frame.

   T1 Plate     .shot--real on its own — the whole screen, caption bar kept.
                .shot--lead raises the page's first plate one step.
   T2 Detail    .shot--detail — a crop of one mechanism, no caption bar, a fade
                on each edge where the crop cut the application's own content.
   T3 On plate  .shot-stack — a T2 detail pulled up over the lower corner of the
                T1 plate it was cut from.
   T4 Band      .shot--band — a capture the application already draws as a
                horizontal band, at container width; .shot--bleed lets it run
                past the container's right edge on a wide screen.
   T5 Pair      .shot-pair — two captures of one workflow, the rear offset
                down-and-right behind the front. The site's one rotation. */

/* ---- T1 · Plate ---------------------------------------------------------- */
.shot--lead .shot__frame { box-shadow: var(--shot-real-highlight), var(--shadow-real-lead); }

/* Home hero only: a low, wide teal line under the composition's bottom edge —
   not a halo around the frame and not a section background. Static, so there
   is no motion for `reduce` to stop. */
.shot-glow { position: relative; }
.shot-glow::after {
  content: "";
  position: absolute;
  z-index: -1;
  top: 100%;          /* the frame's bottom edge: the gradient's hot point */
  left: 6%;
  right: 6%;
  height: 52px;
  background: radial-gradient(62% 100% at 50% 0, var(--shot-underglow), transparent 72%);
  pointer-events: none;
}

/* ---- T2 · Detail --------------------------------------------------------- */
/* No caption bar: a chrome bar on a 685x105 crop claims it is a whole screen.
   Identification moves into `alt`, which names the screen the crop came from.
   Each --cut-* edge fades the capture into the frame's own ground (the app's
   #1A1A2E in both themes), so the reader reads "part of a larger screen". The
   frame's 1px edge is the rule the fade sits behind; masks are progressive —
   without them the crop simply keeps a hard edge. */
.shot--cut-top { --cut-top: var(--shot-cut-fade); }
.shot--cut-right { --cut-right: var(--shot-cut-fade); }
.shot--cut-bottom { --cut-bottom: var(--shot-cut-fade); }
.shot--cut-left { --cut-left: var(--shot-cut-fade); }

.shot--detail .shot__img,
.shot--band .shot__img {
  /* The height budget guards a frame that sizes itself against a caption bar
     it no longer has; a crop is already cut to what it shows. */
  max-height: none;
  --shot-mask-x: linear-gradient(
    to right,
    rgba(0, 0, 0, 0) 0,
    #000 var(--cut-left, 0px),
    #000 calc(100% - var(--cut-right, 0px)),
    rgba(0, 0, 0, 0) 100%);
  --shot-mask-y: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0,
    #000 var(--cut-top, 0px),
    #000 calc(100% - var(--cut-bottom, 0px)),
    rgba(0, 0, 0, 0) 100%);
  -webkit-mask-image: var(--shot-mask-x), var(--shot-mask-y);
  mask-image: var(--shot-mask-x), var(--shot-mask-y);
  /* The legacy keyword first: Chromium before 120 and WebKit read only that. */
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* ---- T3 · Detail on plate ------------------------------------------------ */
/* The detail is cut from the same capture as the plate behind it, which makes
   the object a magnifier rather than a collage. Below 900 the overlap unstacks
   to a plain vertical sequence; nothing overlaps at phone width. */
.shot-stack { display: grid; gap: var(--space-4); }
/* Unstacked, the detail takes the measure — which is wider than the crop on a
   tablet, so it is capped at the file's own pixels. Nothing is ever upscaled. */
.shot-stack__detail {
  max-width: var(--stack-native, none);
  justify-self: start;
}
.shot-stack__detail .shot__frame {
  box-shadow: var(--shot-real-highlight), var(--shadow-real-lift);
}
@media (min-width: 900px) {
  .shot-stack { gap: 0; }
  .shot-stack__detail {
    position: relative;
    z-index: 1;
    justify-self: end;
    width: var(--stack-w, 80%);
    /* Pulled up over the plate's bottom edge. The overlap is set per placement
       to the band of plate that carries no figure, axis label or row. */
    margin-top: calc(-1 * var(--stack-overlap, 28px));
    margin-right: var(--stack-inset, 20px);
  }
}

/* ---- T4 · Bleed strip ---------------------------------------------------- */
/* Only for a capture the application itself draws as a band. Never a table
   cropped down to make one. The bleed is capped at the page margin the
   container leaves unused, the same cap the teaser media take. */
.record + .shot--band { margin-top: var(--space-6); }
.shot--bleed {
  --shot-bleed: 0px;
  --container-pad: var(--gutter);
  margin-inline-end: calc(-1 * var(--shot-bleed));
}
@media (min-width: 1200px) {
  .shot--bleed {
    --shot-bleed: min(112px, calc((100vw - min(100vw, var(--container))) / 2 + var(--container-pad)));
  }
  /* The edge that leaves the page is squared and loses its hairline, so the
     band reads as running off rather than as a card parked at the screen edge. */
  .shot--bleed .shot__frame {
    border-right: 0;
    border-start-end-radius: 0;
    border-end-end-radius: 0;
  }
}
@media (min-width: 1280px) {
  .shot--bleed { --container-pad: var(--space-6); }
}

/* ---- T5 · Offset pair ---------------------------------------------------- */
/* Both panels keep their caption bar: that is what says two screens, not one.
   The rear carries the only rotation on the site. It sits on .shot__frame and
   never on the figure, because reveal-rise ends at `transform: none` with
   fill-mode both and would erase it. A static transform is layout, not motion,
   so `reduce` has nothing to gate; below 900 the pair is two plain plates. */
.shot-pair { display: grid; gap: var(--space-5); }
@media (min-width: 900px) {
  .shot-pair { gap: 0; }
  .shot-pair > * { grid-area: 1 / 1; }
  .shot-pair__front {
    position: relative;
    z-index: 1;
    align-self: start;
    margin-right: var(--pair-offset, 28px);
  }
  .shot-pair__front .shot__frame {
    box-shadow: var(--shot-real-highlight), var(--shadow-real-lift);
  }
  .shot-pair__rear {
    position: relative;
    z-index: 0;
    margin-top: var(--pair-offset, 28px);
    margin-left: var(--pair-offset, 28px);
  }
  /* The rear capture carries its own bottom corners: it is the panel whose
     lower edge sits under the front, and matching the frame's radius there
     keeps the seam clean at every width. Inert everywhere else. */
  .shot-pair__rear .shot__img {
    border-end-start-radius: calc(var(--frame-radius) - 1px);
    border-end-end-radius: calc(var(--frame-radius) - 1px);
  }
}


/* ═══════════════ PHOTOGRAPH ═══════════════
   figure.photo: one treatment for every photograph — the frame of the
   screenshot slots, a light saturation pull and a navy→teal tint so the
   photo reads as part of the instrument, not a stock insert. The slot crop
   is CSS: --frame-ratio + object-fit + --focus. */
.photo { margin: 0; }
.photo__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--frame-ratio, 3 / 2);
  border: 1px solid var(--frame-border);
  border-radius: var(--frame-radius);
  background: var(--shot-bg);
  box-shadow: var(--edge-highlight), var(--shadow-card);
}
.photo__frame > picture { display: block; height: 100%; }
.photo__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  filter: saturate(var(--photo-saturate)) contrast(var(--photo-contrast));
}
.photo__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(160deg, var(--photo-tint-a), var(--photo-tint-b));
  mix-blend-mode: multiply;
}
[data-theme="dark"] .photo__frame::after { mix-blend-mode: soft-light; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .photo__frame::after { mix-blend-mode: soft-light; }
}


/* ═══════════════ TEASER — one block per inner page ═══════════════
   Headline, argument, two or three points, one link, one visual. No card, no
   border: the media is the surface. The split is an even 6/6 at every desktop
   width — the media's weight comes from its height, never from more columns,
   so the picture is sized to the text and not the other way round. */
.teaser-run {
  display: grid;
  gap: var(--teaser-gap);
}
.teaser {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}

/* Media height by role; the bleed is capped so a wide screen widens the crop
   instead of inflating the picture. Measured against direction v2.4 §9:
   1000 -> 468x300, 1280 -> 608x384, 1440 -> 688x432, 1920 -> 696x440
   (lead: 340 / 435 / 490 / 500). --container-pad tracks .container padding. */
.teaser__media {
  min-width: 0;
  --frame-ratio: 3 / 2;
  --container-pad: var(--space-5);
  --teaser-media-h: clamp(280px, 30vw, 440px);
  --teaser-bleed: min(120px, calc((100vw - min(100vw, var(--container))) / 2 + var(--container-pad)));
}
.teaser--lead .teaser__media { --teaser-media-h: clamp(320px, 34vw, 500px); }
/* A frame is a drawing, not a photograph: it keeps the wider silhouette. */
.shot.teaser__media { --frame-ratio: 16 / 10; }

.teaser__title {
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-heading);
  text-wrap: balance;
}
.teaser--lead .teaser__title { font-size: var(--text-h1); }
.teaser__lead {
  color: var(--text-muted);
  max-width: 46ch;
}
.teaser--lead .teaser__lead { max-width: 52ch; }

/* Points carry the specifics the body deliberately leaves out. Square teal
   markers, not checks — checks are the fact strip's vocabulary. */
.teaser__points {
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.teaser__points li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}
.teaser__points li::before {
  content: "";
  flex: none;
  width: 3px;
  height: 3px;
  margin-top: 0.55em;
  background: var(--brand);
}

/* Below 1000 the body is transparent to the grid so the media can sit between
   title and body: eyebrow, title, media, body, points, link. */
.teaser__body { display: contents; }
.teaser__body .eyebrow { order: 1; }
.teaser__title { order: 2; }
.teaser__media { order: 3; }
.teaser__lead { order: 4; }
.teaser__points { order: 5; }
.teaser__body .link-arrow { order: 6; justify-self: start; }
/* Tablet guard: a 768 screen must not make a 480px-tall block. The photo
   frame keeps its ratio by shrinking its own width; the illustration frame
   carries the chrome bar as well, so its width is capped instead and both
   are centred once the guard bites. */
.teaser__media .photo__frame { max-height: min(52vh, 420px); margin-inline: auto; }
.teaser__media .shot__body { max-height: min(52vh, 420px); }
.teaser__media .shot__frame {
  max-width: calc(min(52vh, 420px) * 16 / 10);
  margin-inline: auto;
}

@media (min-width: 1280px) {
  .teaser__media { --container-pad: var(--space-6); }
}

@media (min-width: 1000px) {
  .teaser {
    grid-template-columns: minmax(0, 6fr) minmax(0, 6fr);
    column-gap: var(--space-8);
  }
  .teaser__body { display: block; grid-column: 1; grid-row: 1; }
  .teaser__title { margin-top: var(--space-4); }
  .teaser__lead { margin-top: var(--space-4); }
  .teaser__points { margin-top: var(--space-5); }
  .teaser__body .link-arrow { margin-top: var(--space-5); }
  /* The height wins at desktop; --frame-ratio is for < 1000 only. */
  .teaser__media {
    grid-column: 2;
    grid-row: 1;
    margin-inline-end: calc(-1 * var(--teaser-bleed));
  }
  .teaser__media .photo__frame,
  .teaser__media .shot__frame {
    height: var(--teaser-media-h);
    max-height: none;
    max-width: none;
    aspect-ratio: auto;
  }
  .teaser__media .shot__body { height: 100%; max-height: none; aspect-ratio: auto; }
  .teaser--flip .teaser__body { grid-column: 2; }
  .teaser--flip .teaser__media {
    grid-column: 1;
    margin-inline-end: 0;
    margin-inline-start: calc(-1 * var(--teaser-bleed));
  }
}


/* ═══════════════ PLATE — the site's themed ground ═══════════════
   One composition, two palettes: same gradient direction, same border, same
   under-light. Only the values change, so there is one code path and dark is
   the card that shipped. Used by the closing CTA on all six pages and by
   Pricing's price plate.

   In light the plate is defined by its edge, not its ground: no pale ground can
   separate from --bg #F8FAFC (white measures 1.05:1, and a teal tint is worse
   at 1.01:1 because it moves toward the page's own luminance), while --teal-700
   at full strength reaches 5.23:1. The card and its button then share one
   colour, so the plate reads as one object built around its action. */
.plate {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);

  /* Local re-theme. Every value paired; the composition is identical. */
  --text:        light-dark(var(--ink-text), var(--paper));
  --text-muted:  light-dark(var(--ink-muted), color-mix(in srgb, var(--paper) 70%, var(--ink)));
  --text-faint:  light-dark(var(--ink-faint), color-mix(in srgb, var(--paper) 74%, var(--ink)));
  --brand:       light-dark(var(--teal-700), var(--teal-400));
  --brand-hover: light-dark(var(--teal-800), var(--teal-300));
  --on-brand:    light-dark(var(--white), var(--ink));
  --rule:        light-dark(color-mix(in srgb, var(--teal-700) 18%, transparent),
                            color-mix(in srgb, var(--paper) 16%, transparent));
  --ruler-line:  light-dark(color-mix(in srgb, var(--teal-700) 30%, transparent),
                            color-mix(in srgb, var(--paper) 32%, transparent));
  --chart-today: light-dark(color-mix(in srgb, var(--teal-700) 35%, transparent),
                            color-mix(in srgb, var(--paper) 35%, transparent));

  /* light-dark() is a <color> function, so the gradient stops and the shadow
     colour are tokenised and the shapes stay single declarations. */
  --plate-grad-a: light-dark(var(--white), color-mix(in srgb, var(--teal-800) 34%, var(--ink)));
  --plate-grad-b: light-dark(color-mix(in srgb, var(--teal-500) 3%, var(--white)),
                             color-mix(in srgb, var(--teal-800) 16%, var(--ink)));
  --plate-grad-c: light-dark(color-mix(in srgb, var(--teal-500) 6%, var(--white)),
                             color-mix(in srgb, var(--logo-navy) 18%, var(--ink)));
  --plate-edge:   light-dark(var(--teal-700), color-mix(in srgb, var(--teal-400) 32%, transparent));
  --plate-inner:  light-dark(transparent, color-mix(in srgb, var(--teal-300) 16%, transparent));
  --plate-shadow: light-dark(color-mix(in srgb, var(--teal-700) 14%, transparent), rgba(0, 0, 0, 0.55));
  --plate-pool:   light-dark(color-mix(in srgb, var(--teal-500) 10%, transparent),
                             color-mix(in srgb, var(--teal-400) 22%, transparent));

  color: var(--text);
  background: linear-gradient(158deg, var(--plate-grad-a) 0%, var(--plate-grad-b) 54%, var(--plate-grad-c) 100%);
  border: 1px solid var(--plate-edge);
  /* 0 24px 60px is the geometry of --shadow-pop, so dark keeps the lift it
     already had; only the colour is tokenised. */
  box-shadow: inset 0 1px 0 var(--plate-inner), 0 24px 60px var(--plate-shadow);
}

/* Without light-dark() the whole site falls back to light (tokens §2), and
   every paired value above resolves to the guaranteed-invalid value — the plate
   would lose its background entirely — so all fifteen are repeated at their
   light values, not only the seven --plate-* ones. */
@supports not (color: light-dark(#000, #fff)) {
  .plate {
    --text: var(--ink-text);
    --text-muted: var(--ink-muted);
    --text-faint: var(--ink-faint);
    --brand: var(--teal-700);
    --brand-hover: var(--teal-800);
    --on-brand: var(--white);
    --rule: color-mix(in srgb, var(--teal-700) 18%, transparent);
    --ruler-line: color-mix(in srgb, var(--teal-700) 30%, transparent);
    --chart-today: color-mix(in srgb, var(--teal-700) 35%, transparent);
    --plate-grad-a: var(--white);
    --plate-grad-b: color-mix(in srgb, var(--teal-500) 3%, var(--white));
    --plate-grad-c: color-mix(in srgb, var(--teal-500) 6%, var(--white));
    --plate-edge: var(--teal-700);
    --plate-inner: transparent;
    --plate-shadow: color-mix(in srgb, var(--teal-700) 14%, transparent);
    --plate-pool: color-mix(in srgb, var(--teal-500) 10%, transparent);
  }
}

/* The pooled light. Its job was never to lift a dark ground — it lights the
   thing it sits behind — so on white it becomes a soft teal pool. */
.plate__light {
  position: absolute;
  background: radial-gradient(circle, var(--plate-pool), transparent 60%);
  pointer-events: none;
}


/* ═══════════════ CLOSING CTA — the plate, centred ═══════════════ */
.cta__card {
  text-align: center;
  padding: clamp(2.5rem, 5.5vw, 4.25rem) clamp(1.25rem, 4vw, 3.5rem);
}

/* Ruler + realized/forecast line as the card horizon, fading upward. */
.cta__motif {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 120px;
  opacity: 0.4;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(0deg, #000 45%, transparent 100%);
  mask-image: linear-gradient(0deg, #000 45%, transparent 100%);
}
.cta__motif-ruler { stroke: var(--ruler-line); stroke-width: 1; }

/* Placement only; the pool itself is .plate__light. Under the button. */
.cta__light {
  left: 50%;
  bottom: -34%;
  transform: translateX(-50%);
  width: min(820px, 100%);
  height: 460px;
}

.cta__content { position: relative; }
.cta__title {
  font-size: var(--text-h1);
  max-width: 21ch;
  margin-inline: auto;
}
.cta__invite {
  margin-top: var(--space-4);
  margin-inline: auto;
  max-width: 56ch;
}
.cta__action {
  margin-top: var(--space-6);
  display: grid;
  justify-items: center;
  gap: var(--space-4);
}
.cta__action .link-arrow { justify-self: center; }
/* The contact line under the action, for the reader who would rather call
   than fill a form: .note's size and ink, centred by the card. */
.cta__contact {
  margin-top: var(--space-5);
  color: var(--text-muted);
}


/* ═══════════════ FOOTER ═══════════════ */
.site-footer {
  border-top: 1px solid var(--rule);
  font-size: var(--text-sm);
}

.site-footer__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  padding-block: var(--space-7) var(--space-6);
}

.footer-brand__lockup {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.footer-brand__tagline {
  margin-top: var(--space-4);
  color: var(--text-muted);
  max-width: 34ch;
}
.footer-brand__maker {
  margin-top: var(--space-3);
  color: var(--text-muted);
  max-width: 34ch;
}
.footer-brand .btn { margin-top: var(--space-5); }

.footer-menu {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: start;
}
.footer-menu a {
  color: var(--text-muted);
  padding-block: 0.2rem;
  transition: color var(--duration-fast) var(--ease-out);
}
.footer-menu a:hover { color: var(--text); }
/* Touch widths: each menu row is a 44px target. */
@media (max-width: 899px) {
  .footer-menu a { display: inline-flex; align-items: center; min-height: 44px; }
}

.footer-address {
  color: var(--text-muted);
  font-size: var(--text-xs);
  line-height: 1.75;
  font-style: normal;
}
.footer-address strong {
  display: block;
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-2);
}
.footer-address__phones { display: block; margin-top: var(--space-3); }
.footer-address__phones > span { color: var(--text-faint); }
/* Block padding on the inline links, so the hit box clears 24px at 13px type. */
.footer-address__phones a { color: var(--text-muted); padding-block: 0.25rem; }
.footer-address__phones a:hover { color: var(--text); }

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
  justify-content: space-between;
  align-items: center;
  padding-block: var(--space-5);
  border-top: 1px solid var(--rule);
  font-size: var(--text-xs);
  color: var(--text-faint);
}
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
}
.footer-legal a { padding-block: 0.15rem; }
.footer-legal a:hover { color: var(--text); }
.footer-legal .lang-link { font-size: inherit; color: inherit; }

@media (min-width: 640px) {
  .site-footer__inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-6);
    padding-block: var(--space-7) var(--space-6);
  }
  .footer-brand { grid-column: 1 / -1; }
}
@media (min-width: 900px) {
  .site-footer__inner {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr) minmax(0, 1.1fr);
    gap: var(--space-7) var(--space-8);
    padding-block: var(--space-8) var(--space-7);
  }
  .footer-brand { grid-column: auto; }
}


/* ═══════════════ NOT FOUND — /404.html ═══════════════
   The page-head type with the seven pages as exits under it and the contact
   line at the foot: the page names what exists and offers nothing else. The
   block fills the viewport so the footer sits at the bottom of the screen
   rather than halfway up it. */
.not-found {
  display: grid;
  align-content: center;
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100dvh - var(--header-h));
}
.not-found__inner { display: grid; gap: var(--space-7); }
.not-found .page-head__title { margin-top: 0; }
.not-found__lead {
  margin-top: var(--space-4);
  color: var(--text-muted);
  max-width: 46ch;
}
.not-found__links {
  display: grid;
  gap: var(--space-3);
  justify-items: start;
}
@media (min-width: 640px) {
  .not-found__links {
    grid-template-columns: repeat(2, max-content);
    column-gap: var(--space-8);
  }
}
.not-found__contact { margin-top: var(--space-7); }
/* From 1000 the head takes the page-head's 7/5 split and the exits the lead's
   column, so the page is one composition rather than a column on a void. */
@media (min-width: 1000px) {
  .not-found__inner {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--space-7);
    align-items: start;
  }
  .not-found__exits {
    padding-left: var(--space-6);
    border-left: 1px solid var(--rule);
  }
}
