/* ========================================================================
   Suby Marketing Site
   Single shared stylesheet — design tokens, base, components
   ======================================================================== */

/* Breakpoints: sm 480px, md 768px, lg 1024px */

/* ---------- Fonts (Outfit variable, served from one woff2) ---------- */
@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/outfit/Outfit-Variable.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/outfit/Outfit-Variable-LatinExt.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Cyrillic: Outfit ships none, so Montserrat's Cyrillic (as in the app) stands in as its own
   family, listed right after "Outfit" in the stacks that set Russian. It must not join "Outfit":
   Chrome settles on one weight's faces before it looks at unicode-range, and it prefers these
   exact 400-800 faces to the 100-900 variable one, so Latin set at those weights would skip
   Outfit and take the next family in the stack. */
@font-face {
  font-family: "Outfit Cyrillic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-regular-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Outfit Cyrillic";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-medium-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Outfit Cyrillic";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-semibold-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Outfit Cyrillic";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-bold-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Outfit Cyrillic";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-extrabold-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Montserrat whole, Latin included, for recreations in Russian: the app swaps it in for
   every glyph in ru/uk (Type.kt, SubyBrandFontFamily), not only for Cyrillic. The Latin
   files are subset from the app's own shared/.../font/montserrat_*.ttf; the Cyrillic ones
   are the site's. Declaring the family also hides any locally installed Montserrat, so the
   result does not depend on the visitor's machine. */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-regular-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+20BD, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-regular-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-medium-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+20BD, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-medium-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-semibold-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+20BD, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-semibold-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-bold-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+20BD, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-bold-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-extrabold-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+20BD, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("/assets/fonts/montserrat/Montserrat-extrabold-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* ------------------------------------------------------------------------
   Cascade layer order — declared once so precedence follows the same
   top-to-bottom order this file is already authored in (parity-safe).
   Source regions are wrapped into these layers below, in this order,
   without reordering any rule. @font-face / @keyframes stay unlayered.
   ------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------
   Layer order.

   Kobweb Silk injects its own stylesheets at runtime and declares the layers
   `reset, kobweb-compose, base, component-styles, component-variants,
   restricted-styles, general-styles`. Two consequences this file has to own:

   1. Layer order is fixed by FIRST declaration, and this file is parsed before
      Silk's sheets exist. Naming Silk's layers here, ahead of ours, is what
      puts ours above them. Without it every layer in this file ranks BELOW
      Silk's `reset`, and a rule here that needs to beat the reset loses with
      no error and no obvious cause. Measured, not assumed: with the same rule
      declared in both `@layer reset` and `@layer suby.overrides`, ours wins
      with this statement in place and loses without it.

   2. Silk also uses the name `base`. An unprefixed `base` here would be the
      SAME layer, and Silk's sheet is appended after ours, so Silk would win
      inside it on source order. Ours are namespaced under `suby` for that
      reason — never reintroduce a bare layer name.

   `responsive` was declared for years and never used; it is gone rather than
   left as an empty promise. Breakpoint overrides live beside the component
   they modify.
   ------------------------------------------------------------------------ */
@layer reset, kobweb-compose, base, component-styles, component-variants,
       restricted-styles, general-styles,
       suby.tokens, suby.base, suby.layout, suby.components, suby.motion, suby.overrides;

@layer suby.tokens {

/* ---------- Tokens ---------- */
:root {
  --ink: var(--suby-primary);
  --bg: var(--suby-bg);
  --surface: var(--suby-surface);
  --border: var(--suby-divider);
  --muted: var(--suby-secondary-text);
  --on-ink: var(--suby-bg);
  --on-ink-fixed: #fff;
  --ink-fixed: #111;
  --scrim-strong: rgba(0, 0, 0, 0.55);

  --container-width: var(--suby-container-width);
  --gutter: var(--suby-gutter);
  --section-y: var(--suby-section-gap-y);
  --section-y-hero: clamp(36px, 6vw, 84px);
  --section-y-compact: clamp(40px, 8vh, 72px);
  --section-y-cta: clamp(48px, 6vw, 72px);
  --stack-gap: clamp(32px, 4vw, 56px);
  --radius: var(--suby-radius-base);
  --radius-lg: var(--suby-radius-lg);

  --grid-floor-card: 260px;
  --grid-floor-tile: 200px;
  --tap-min: 44px;

  --font: "Outfit", "Outfit Cyrillic", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;

  --transition: var(--suby-duration-base) var(--suby-ease-standard);

  --img-outline: var(--suby-image-outline);
  --card-hover-shadow: var(--theme-dark-card-hover-shadow, 0 20px 50px -20px rgba(0, 0, 0, 0.18));
  --lift-1: var(
    --theme-dark-lift-1,
    0 1px 2px rgba(17, 17, 17, 0.05),
    0 6px 16px -6px rgba(17, 17, 17, 0.10)
  );
  --lift-2: var(
    --theme-dark-lift-2,
    0 2px 4px rgba(17, 17, 17, 0.05),
    0 12px 28px -10px rgba(17, 17, 17, 0.12)
  );
  --header-glass-fill: color-mix(in srgb, var(--bg) 58%, transparent);
  --header-glass-edge: color-mix(in srgb, var(--bg) 78%, var(--ink) 22%);
  --header-glass-sheen: rgba(255, 255, 255, 0.34);
  --header-glass-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.72),
    0 2px 10px rgba(17, 17, 17, 0.10),
    0 24px 56px -28px rgba(17, 17, 17, 0.46);
  --header-shell-height: 88px;
  --language-banner-height: 52px;
  --hairline: var(--theme-dark-hairline, rgba(17, 17, 17, 0.11));
  --rule: var(--theme-dark-rule, rgba(17, 17, 17, 0.13));
  --phone-glow: var(--theme-dark-phone-glow, rgba(17, 17, 17, 0.055));
  --phone-glow-mid: var(--theme-dark-phone-glow-mid, rgba(17, 17, 17, 0.02));

  --space-1: var(--suby-space-1);
  --space-2: var(--suby-space-2);
  --space-3: var(--suby-space-3);
  --space-4: var(--suby-space-4);
  --space-5: var(--suby-space-5);
  --space-6: var(--suby-space-6);
  --space-28: 28px;
  --space-7: var(--suby-space-7);
  --space-40: 40px;
  --space-8: var(--suby-space-8);
  --space-64: 64px;
  --space-96: 96px;

  --radius-sm: var(--suby-radius-sm);
  --radius-md: var(--suby-radius-md);
  --radius-icon: 14px;
  --radius-chip: var(--radius-icon);
  --radius-app-icon: 22.5%;
  --radius-step: 18px;
  --radius-card: 20px;
  --radius-pill: var(--suby-radius-pill);

  --ease: var(--suby-ease-standard);
  --dur-fast: var(--suby-duration-fast);
  --dur-base: var(--suby-duration-base);
  --dur-slow: var(--suby-duration-slow);
  --dur-entrance: 600ms;
  --dur-press: 200ms;
  --dur-emphasis: 320ms;
  --focus-ring: var(--suby-focus-color);

  --text-xs: var(--suby-text-xs);
  --text-sm: var(--suby-text-sm);
  --text-base: var(--suby-text-base);
  --text-md: var(--suby-text-md);
  --text-lg: var(--suby-text-lg);
  --text-eyebrow: var(--suby-text-eyebrow);
  --text-title: var(--suby-text-title);
  --text-h3: var(--suby-text-h3);
  /* Marketing sets its own display rungs. The shared --suby-text-h* tokens are
     sized for adminWeb, where a 72px heading would be absurd; here the headline
     IS the page, and at 56px it read as a caption for the screenshot beside it. */
  /* Capped below the hero's 82px so the headline stays the loudest thing on the
     page; at 72px a two-word section heading was shouting over it. */
  --text-h2: clamp(32px, 5vw, 64px);
  --text-h1: clamp(40px, 7vw, 92px);
  --text-display: var(--suby-text-display);
  /* Display setting: below ~40px the normal heading leading is right, above it
     the lines must close up or the block reads as three sentences, not one. */
  --leading-display: 0.96;

  --weight-regular: var(--suby-font-weight-regular);
  --weight-medium: var(--suby-font-weight-medium);
  --weight-semibold: var(--suby-font-weight-semibold);
  --weight-bold: var(--suby-font-weight-bold);
  --weight-extrabold: var(--suby-font-weight-extrabold);
  --weight-heavy: var(--suby-font-weight-heavy);

  --leading-tight: var(--suby-leading-tight);
  --leading-snug: var(--suby-leading-snug);
  --leading-normal: var(--suby-leading-normal);
  --leading-relaxed: var(--suby-leading-relaxed);

  --tracking-caps: var(--suby-tracking-caps);
  --tracking-caps-wide: 0.14em;
  --tracking-caps-tight: 0.06em;
  --tracking-heading: -0.03em;
  --tracking-tight: var(--suby-tracking-tight);
  --tracking-display: var(--suby-tracking-display);

  --measure: var(--suby-measure);
  --measure-narrow: var(--suby-measure-narrow);
}

@media (max-width: 480px) {
  :root {
    --gutter: 16px;
  }
}

/* Russian pages set every glyph in Montserrat, Latin included, as the app does in ru
   (DESIGN.md: swapped in wholesale, not a Cyrillic patch). Outfit stays behind it for the
   Latin Extended letters the Montserrat subsets leave out. */
html[lang="ru"] {
  --font: "Montserrat", "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}

:root[data-theme="dark"] {
  --theme-dark-img-outline: rgba(255, 255, 255, 0.1);
  --theme-dark-card-hover-shadow: 0 24px 56px -18px rgba(0, 0, 0, 0.6);
  --theme-dark-lift-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
  --theme-dark-lift-2: 0 2px 4px rgba(0, 0, 0, 0.45), 0 12px 28px -10px rgba(0, 0, 0, 0.6);
  /* The ink bands turn white here, and the header passes over one as the page scrolls. At 58%
     the glass there composited to ~#7F7F7F under the sheen, 3.7:1 for the nav text; 72% holds
     5.8:1, and over the dark bands nothing visibly changes. */
  --header-glass-fill: color-mix(in srgb, var(--bg) 72%, transparent);
  --header-glass-sheen: rgba(255, 255, 255, 0.10);
  --header-glass-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 2px 12px rgba(0, 0, 0, 0.42),
    0 28px 68px -30px rgba(0, 0, 0, 0.86);
  --theme-dark-hairline: rgba(255, 255, 255, 0.12);
  --theme-dark-rule: rgba(255, 255, 255, 0.16);
  --theme-dark-phone-glow: rgba(255, 255, 255, 0.07);
  --theme-dark-phone-glow-mid: rgba(255, 255, 255, 0.025);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) {
    --theme-dark-img-outline: rgba(255, 255, 255, 0.1);
    --theme-dark-card-hover-shadow: 0 24px 56px -18px rgba(0, 0, 0, 0.6);
    --theme-dark-lift-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
    --theme-dark-lift-2: 0 2px 4px rgba(0, 0, 0, 0.45), 0 12px 28px -10px rgba(0, 0, 0, 0.6);
    --header-glass-fill: color-mix(in srgb, var(--bg) 72%, transparent);
    --header-glass-sheen: rgba(255, 255, 255, 0.10);
    --header-glass-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.12),
      0 2px 12px rgba(0, 0, 0, 0.42),
      0 28px 68px -30px rgba(0, 0, 0, 0.86);
    --theme-dark-hairline: rgba(255, 255, 255, 0.12);
    --theme-dark-rule: rgba(255, 255, 255, 0.16);
    --theme-dark-phone-glow: rgba(255, 255, 255, 0.07);
    --theme-dark-phone-glow-mid: rgba(255, 255, 255, 0.025);
  }
}

/* Motion easings live with the other tokens, not beside the one section that
   happened to introduce them. */
:root {
  /* Overshoots slightly, then settles — the difference between "it moved" and "it
     has weight". Reserved for entrances; presses stay on the standard ease. */
  --ease-settle: cubic-bezier(0.16, 0.84, 0.36, 1.02);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

} /* end @layer suby.tokens */

@layer suby.base {

/* ---------- Reset / Base ---------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* scroll-behavior: smooth removed — it fights with browser scroll
     restoration on refresh, causing progressive scroll-down bug.
     Anchor navigation still works (instant jump). */
  /* clip on the root scroll container so the off-canvas drawer (position:fixed,
     translated off-screen) can't extend the horizontal scroll region — that was
     pushing the right-aligned header controls off-screen and letting the drawer
     peek. clip (not hidden) preserves fixed overlays and anchor scrolling. */
  overflow-x: clip;
  /* Keep reverse-tab focus and in-page anchors below the fixed release pill
     (WCAG 2.2 SC 2.4.11), with 12px of breathing room beyond its shell. */
  scroll-padding-top: 100px;
}

html:has(.lang-banner) {
  scroll-padding-top: 152px;
}

@media (max-width: 820px) {
  html { scroll-padding-top: 84px; }
  html:has(.lang-banner) { scroll-padding-top: 136px; }
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* clip (not hidden) so the off-canvas drawer can't be scrolled into view and 100vw
     can't exceed the viewport, while the sticky header keeps working. */
  overflow-x: clip;
}

img, svg { display: block; max-width: 100%; height: auto; }

/* Selection, caret and scroll gutter ship in colours that belong to no design
   system. On a page this monochrome, the browser's blue is the loudest thing on
   it until it is claimed. */
::selection {
  background: var(--ink);
  color: var(--bg);
}

::-moz-selection {
  background: var(--ink);
  color: var(--bg);
}

:root {
  caret-color: var(--ink);
  accent-color: var(--ink);
  scrollbar-color: var(--suby-divider-strong) transparent;
}

a {
  color: inherit;
  text-decoration: none;
  /* Inherited by every rule that turns an underline back on, so the handful of
     places that do underline all sit the same distance off the baseline instead
     of each picking their own. */
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
  transition: opacity var(--transition);
}
a:hover { opacity: 0.7; }

/* Sections.kt emits bare h1/h2, and only three selectors ever set --text-h1, so
   /get, /services and /blog were rendering their title at the UA default 36px —
   under the 56px section headings beneath them. Every scoped rule is a class
   selector and still wins. */
h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 0.5em;
  line-height: 1.15;
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

/* Display setting for the two rungs that actually reach display size. Leading
   and tracking both have to close up together: -0.04em at 1.15 leading looks
   like a mistake, and 0.98 leading at -0.02em looks like the lines collided. */
h1, h2 {
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-display);
  font-weight: var(--weight-extrabold);
}

/* A ledger sets its figures on a fixed advance, so columns of numbers line up
   and a changing total doesn't jitter. Outfit ships the feature; use it. */
.price,
.price__subline,
.proof-metric__value,
.join-card__amount-value {
  font-variant-numeric: tabular-nums;
}

p { margin: 0 0 1em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

ul, ol { padding-left: 1.4em; }

strong { font-weight: var(--weight-semibold); }

} /* end @layer suby.base */

@layer suby.layout {

/* ---------- Layout primitives ---------- */
.container {
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--section-y);
}

.section--surface {
  background: var(--surface);
}

/* Entrance for the top of an inner page: an opacity-only stagger on its first three children, so it
   never shifts layout or fights scroll restoration. Like the hero's (see .hero), it plays only for
   nodes created after the first render, never on the exported HTML or the client's copy of it, which
   would be two fades one after the other. Declared in this lower layer so a component's own
   `transition` wins on the rare child that has one. */
@media (prefers-reduced-motion: no-preference) {
  :root[data-entered] .page-enter > :nth-child(-n + 3) {
    transition: opacity var(--dur-entrance) ease-out var(--enter-delay, 0ms);
  }
  :root[data-entered] .page-enter > :nth-child(2) { --enter-delay: 100ms; }
  :root[data-entered] .page-enter > :nth-child(3) { --enter-delay: var(--dur-press); }

  @starting-style {
    :root[data-entered] .page-enter > :nth-child(-n + 3) { opacity: 0; }
  }
}

} /* end @layer suby.layout */

@layer suby.components {

/* ---------- Floating glass release pill ---------- */
.header {
  position: fixed;
  top: 0;
  inset-inline: 0;
  z-index: 100;
  isolation: isolate;
  padding: 12px var(--gutter);
  background: transparent;
}

.header__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1080px;
  display: grid;
  grid-template-columns: minmax(max-content, 1fr) auto minmax(max-content, 1fr);
  align-items: center;
  min-height: 64px;
  gap: clamp(16px, 2.4vw, 36px);
  padding-inline: clamp(18px, 2vw, 28px);
  border: 1px solid var(--header-glass-edge);
  border-radius: var(--radius-pill);
  background: var(--header-glass-fill);
  -webkit-backdrop-filter: saturate(165%) blur(24px);
  backdrop-filter: saturate(165%) blur(24px);
  box-shadow: var(--header-glass-shadow);
}

.header__inner::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(135deg, var(--header-glass-sheen), transparent 44%);
}

/* A fixed bar leaves normal flow. This restores exactly the space its shell occupied, so
   every route starts below it while later content can still travel beneath the glass. */
.header + main {
  padding-top: var(--header-shell-height);
}

/* Brand: icon + wordmark */
.header__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  flex-shrink: 0;
  justify-self: start;
}

.header__brand:hover { opacity: 1; }

.header__brand-icon {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

/* Mask-based logo follows the active theme via background-color. */
.header__brand-icon {
  background-color: var(--ink);
  -webkit-mask: url("/assets/img/logo-mono.svg") no-repeat center / contain;
          mask: url("/assets/img/logo-mono.svg") no-repeat center / contain;
  transition: background-color var(--transition);
}

/* ---------- Theme toggle ---------- */
.theme-toggle {
  position: relative;
  flex-shrink: 0;
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--ink);
  cursor: pointer;
  padding: 0;
  transition: background var(--transition), border-color var(--transition), transform var(--dur-press) var(--ease);
}
.theme-toggle:hover {
  background: var(--surface);
}
.theme-toggle:active {
  transform: scale(0.96);
}
/* All three icons stay in the DOM, stacked and centered; cross-fade with opacity + scale + blur. */
.theme-toggle svg[data-icon] {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 16px;
  height: 16px;
  opacity: 0;
  scale: 0.25;
  filter: blur(4px);
  transition: opacity var(--dur-press) var(--ease),
              scale var(--dur-press) var(--ease),
              filter var(--dur-press) var(--ease);
}
:root[data-theme="light"] .theme-toggle svg[data-icon="sun"],
:root[data-theme="dark"]  .theme-toggle svg[data-icon="moon"],
:root:not([data-theme]) .theme-toggle svg[data-icon="auto"] {
  opacity: 1;
  scale: 1;
  filter: blur(0);
}

.header__wordmark {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-heading);
  color: var(--ink);
}

/* The middle column is intrinsic, so navigation stays geometrically centred while both side
   columns fit their content. When one cannot (Russian's full CTA at desktop), that column keeps
   its max-content width and the nav yields toward the brand instead of running under the CTA. */
.nav {
  display: flex;
  align-items: center;
  gap: 28px;
  font-size: 15px;
  font-weight: var(--weight-medium);
  justify-self: center;
}

.header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  justify-self: end;
}

.nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 40px;
}

.nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 8px;
  height: 2px;
  background: var(--ink);
  border-radius: 1px;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--dur-slow) var(--ease);
}

.nav a:hover { opacity: 1; }
.nav a:hover::after,
.nav a.is-active::after { transform: scaleX(1); }

/* CTA button in header */
.header__cta {
  flex-shrink: 0;
  padding: 11px 20px;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--on-ink);
  background: var(--ink);
  border-radius: var(--radius-pill);
  transition: transform var(--dur-press) var(--ease), background var(--dur-press) ease;
  white-space: nowrap;
}

.header__cta:hover {
  opacity: 1;
  transform: translateY(-1px);
  background: color-mix(in srgb, var(--ink) 85%, transparent);
}

.header__cta:active {
  transform: scale(0.96);
}

.header__cta-compact { display: none; }

/* Hamburger toggle: desktop hidden, shown on phones */
.nav-toggle {
  position: relative;
  flex-shrink: 0;
  width: var(--tap-min);
  height: var(--tap-min);
  display: none;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--ink);
  cursor: pointer;
  padding: 0;
  transition: background var(--transition), border-color var(--transition), transform var(--dur-press) var(--ease);
}
.nav-toggle:hover {
  background: var(--surface);
}
.nav-toggle:active {
  transform: scale(0.96);
}
.nav-toggle svg {
  width: 20px;
  height: 20px;
}

/* Mobile drawer */
.mobile-drawer__overlay {
  position: fixed;
  inset: 0;
  background: var(--suby-scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease);
  z-index: 110;
}
.mobile-drawer {
  position: fixed;
  right: 0;
  top: 0;
  height: 100%;
  height: 100dvh;
  width: min(80vw, 320px);
  background: var(--bg);
  border-left: 1px solid var(--border);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform var(--dur-base) var(--ease), visibility 0s linear var(--dur-base);
  z-index: 120;
  display: none;
  flex-direction: column;
  gap: 4px;
  padding: 24px 20px;
  overflow-y: auto;
}
.mobile-drawer__close {
  align-self: flex-end;
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8px;
  padding: 0;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--transition), transform var(--dur-press) var(--ease);
}
.mobile-drawer__close:hover { background: var(--surface); }
.mobile-drawer__close:active { transform: scale(0.96); }
.mobile-drawer__close svg { width: 18px; height: 18px; }

.mobile-drawer__link {
  display: block;
  padding: 12px 12px;
  min-height: 44px;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--ink);
  border-radius: var(--radius-sm);
}
.mobile-drawer__link:hover {
  opacity: 1;
  background: var(--surface);
}
.mobile-drawer__cta {
  margin-top: 12px;
  width: 100%;
}

:root[data-menu-open] .mobile-drawer {
  transform: none;
  visibility: visible;
  transition: transform var(--dur-base) var(--ease), visibility 0s;
}
:root[data-menu-open] .mobile-drawer__overlay {
  opacity: 1;
  pointer-events: auto;
}

@media (max-width: 1040px) {
  .nav {
    gap: clamp(14px, 2vw, 22px);
    font-size: 14px;
  }
}

/* The compact header and its drawer. Russian keeps them up to 960px in the block after this one;
   change the two together. */
@media (max-width: 820px) {
  :root {
    --header-shell-height: 72px;
  }
  .header {
    padding: 8px;
  }
  .header__inner {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px;
    min-height: 56px;
    padding-inline: 14px 8px;
  }
  .header__brand { min-width: 0; }
  .header__wordmark {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .nav-toggle { display: inline-flex; }
  .nav,
  .header__actions > .theme-toggle { display: none; }
  .header__cta {
    display: inline-flex;
    padding: 10px 14px;
  }
  .header__cta-full { display: none; }
  .header__cta-compact { display: inline; }
  .mobile-drawer { display: flex; }
}

/* «Скачать Suby бесплатно» is 88px wider than "Get Suby free". Up to 900px the Russian desktop row
   overflows the pill, and in Montserrat up to about 935px the brand sits closer to the nav than twice
   the gap between nav links, so it reads as the first link. Russian keeps the compact header to 960px,
   which leaves room for a wider fallback font. */
@media (min-width: 821px) and (max-width: 960px) {
  html[lang="ru"] {
    --header-shell-height: 72px;
    scroll-padding-top: 84px;
  }
  html[lang="ru"]:has(.lang-banner) { scroll-padding-top: 136px; }
  html[lang="ru"] .header { padding: 8px; }
  html[lang="ru"] .header__inner {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px;
    min-height: 56px;
    padding-inline: 14px 8px;
  }
  html[lang="ru"] .nav-toggle { display: inline-flex; }
  html[lang="ru"] .nav,
  html[lang="ru"] .header__actions > .theme-toggle { display: none; }
  html[lang="ru"] .header__cta {
    display: inline-flex;
    padding: 10px 14px;
  }
  html[lang="ru"] .header__cta-full { display: none; }
  html[lang="ru"] .header__cta-compact { display: inline; }
  html[lang="ru"] .mobile-drawer { display: flex; }
}

@media (max-width: 360px) {
  .header {
    padding-inline: 6px;
  }
  .header__inner { padding-inline: 10px 6px; }
  .header__brand { gap: 7px; }
  .header__brand-icon { width: 25px; height: 25px; }
  .header__wordmark { font-size: var(--text-md); }
  .header__cta { padding-inline: 12px; }
  .header__actions { gap: 6px; }
}

.mobile-drawer__preferences {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: auto;
  padding-top: var(--space-6);
  border-top: 1px solid var(--hairline);
}

/* ---------- Language menu (globe icon + popup) ---------- */
.lang-menu {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
}
.lang-menu__button {
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), transform var(--dur-press) var(--ease);
}
.lang-menu__button:hover { background: var(--surface); }
.lang-menu__button:active { transform: scale(0.96); }
.lang-menu__glyph { display: inline-flex; }
.lang-menu__glyph svg { width: 18px; height: 18px; }

.lang-menu__backdrop {
  position: fixed;
  inset: 0;
  z-index: 130;
}
.lang-menu__popup {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 131;
  min-width: 168px;
  padding: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--lift-2, 0 12px 32px -12px rgba(17, 17, 17, 0.28));
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.lang-menu__item {
  display: flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--ink);
  border-radius: var(--radius-sm);
}
.lang-menu__item:hover { opacity: 1; background: var(--surface); }
.lang-menu__item.is-current {
  font-weight: var(--weight-semibold);
  background: var(--surface);
}

.footer__lang {
  margin-top: var(--space-4);
}

.lang-banner {
  position: sticky;
  top: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--language-banner-height);
  gap: var(--space-4);
  padding: 10px var(--space-4);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--on-ink);
  background: var(--ink);
}

/* Keep the fixed glass bar immediately below the optional language prompt. The prompt stays in
   normal flow and remains sticky until dismissed, so its measured slot never becomes dead air. */
.lang-banner + .header {
  top: var(--language-banner-height);
}
.lang-banner__cta {
  color: var(--on-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.lang-banner__cta:hover { opacity: 0.85; }
.lang-banner__dismiss {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  box-sizing: content-box;
  padding: 8px;
  margin: -8px;
  font-size: 14px;
  line-height: 1;
  color: var(--on-ink);
  background: transparent;
  border: none;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--dur-press) var(--ease);
}
.lang-banner__dismiss:hover {
  background: color-mix(in srgb, var(--on-ink) 20%, transparent);
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 14px var(--space-28);
  font: inherit;
  font-weight: var(--weight-semibold);
  font-size: var(--text-base);
  border-radius: var(--radius-pill);
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform var(--transition), background var(--transition), color var(--transition);
}
.btn:hover { opacity: 1; transform: translateY(-1px); }

.btn--primary {
  background: var(--ink);
  color: var(--on-ink);
}
.btn--primary:hover { background: color-mix(in srgb, var(--ink) 85%, transparent); }

.btn--secondary {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}

/* ---------- Store badges ---------- */
.store-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}

.store-badges--centered {
  justify-content: center;
}

/* Both badges match HEIGHT (industry standard from Apple/Google brand
   guidelines). Widths differ naturally based on each badge's design. */
.store-badge {
  position: relative;
  border-radius: 10px;
  transition: transform var(--dur-slow) var(--ease);
}

.store-badge img {
  display: block;
  height: 52px;
  width: auto;
}

.store-badge--rustore .rustore-dark { display: none; }
.store-badge--rustore .rustore-light { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .store-badge--rustore .rustore-light { display: none; }
  :root:not([data-theme="light"]):not([data-theme="dark"]) .store-badge--rustore .rustore-dark { display: block; }
}
:root[data-theme="dark"] .store-badge--rustore .rustore-light { display: none; }
:root[data-theme="dark"] .store-badge--rustore .rustore-dark { display: block; }
:root[data-theme="light"] .store-badge--rustore .rustore-light { display: block; }
:root[data-theme="light"] .store-badge--rustore .rustore-dark { display: none; }

/* Google badge PNG has been trimmed of internal padding.
   No special sizing needed — both badges at height: 52px. */

/* At 360 and 320 — still the modal Android widths — two 52px badges are wider than
   the copy column and the second one drops to its own row, which pushes the device
   below the fold. They stay the same height as each other, just smaller. */
@media (max-width: 400px) {
  .store-badge img {
    height: 42px;
  }
}

.store-badge:hover {
  transform: translateY(-2px) scale(1.02);
  opacity: 1;
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  padding-block: clamp(56px, 8vw, 112px) clamp(28px, 3.4vw, 48px);
}

.hero__layout {
  display: grid;
  grid-template-columns: minmax(0, 1.18fr) minmax(280px, 0.82fr);
  align-items: center;
  gap: clamp(44px, 7vw, 104px);
}

.hero__copy {
  min-width: 0;
  max-width: 720px;
}

/* Shared two-column hero layout: homepage hero + feature-detail hero.
   Stacks to a single left-aligned column at 768px. */
.hero-split {
  position: relative;
  z-index: 1;
  display: grid;
  /* 1.15, not 1.1: at 1.1 the copy column is 543px and "Every subscription," needs
     560 at the headline's cap, so it stranded "Every" alone on line one at every
     desktop width. The device column loses 24px it was not using — the phone is
     capped at 392px and the column was already wider than that. */
  grid-template-columns: 1.15fr 0.85fr;
  gap: 64px;
  align-items: center;
}

/* Centring only works when the two columns are close in height. The hero's are
   not — the device runs ~830px against ~560px of copy — so centring pushed the
   headline a third of the way down the first screen. Both of these start-align;
   the device is allowed to run past the fold, which is where it belongs. */
.hero .hero-split,
.split-section .hero-split {
  align-items: start;
}

/* Let the text column shrink instead of overflowing its long words under the phone. */
.hero-split > * {
  min-width: 0;
}

.hero h1 {
  --hero-h1-size: clamp(48px, 6vw, 84px);
  max-width: 12ch;
  font-size: var(--hero-h1-size);
  line-height: 0.98;
  letter-spacing: max(var(--tracking-display), -0.04em);
  overflow-wrap: break-word;
  margin: 0 0 0.32em;
  text-wrap: balance;
}

/* The Russian headline has one good break, «Все подписки / под контролем.», since «под»
   may not end a line. In Montserrat «под контролем.» is 8.2em and «Все подписки под» 9.5em:
   13.5ch (8.7em) takes the first and refuses the second, and capping the size at 11.7% of
   the copy column keeps 8.2em inside it with 4% to spare at every width. */
html[lang="ru"] .hero__copy { container-type: inline-size; }
html[lang="ru"] .hero h1 {
  max-width: 13.5ch;
  font-size: min(var(--hero-h1-size), 11.7cqi);
}

.hero__h1-sub {
  display: block;
  margin-top: 0.3em;
  color: var(--muted);
  font-size: 0.5em;
  line-height: 1.08;
  letter-spacing: var(--tracking-heading);
}

.hero__more { margin-top: var(--space-6); }

.hero .hero__eyebrow {
  margin: 0 0 var(--space-4);
  text-align: start;
}

.hero p.lead {
  font-size: clamp(18px, 1.8vw, 21px);
  color: var(--muted);
  max-width: 62ch;
  margin-bottom: var(--space-7);
}

.hero-phone {
  min-width: 0;
  justify-self: center;
  /* Capped so the whole phone fits a window: the 108px is the header plus the hero's top padding, and
     1080/2340 is the frame's own ratio. */
  width: min(100%, 340px, calc((100svh - 108px) * 1080 / 2340));
}

.hero-phone__device {
  width: 100%;
}

/* Hero entrance: a calm staggered rise so the header reads as intentional, not busy.
   It plays for hero nodes that appear after the first render, when the home page is opened by a
   client-side navigation. The first load never animates: the exported HTML paints the hero at rest and
   the client then replaces those nodes with identical ones, so any animation here would run on the
   static copy, restart on the client's copy and show as two flashes. `[data-entered]` is set by
   MarketingApp once that first render is on screen; `@starting-style` applies only to nodes created
   after it, so setting it moves nothing already painted. */
@keyframes hero-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: no-preference) {
  :root[data-entered] .hero :is(.hero__eyebrow, h1, p.lead, .store-badges) {
    transition:
      opacity 680ms cubic-bezier(0.22, 0.61, 0.36, 1) var(--enter-delay, 0ms),
      translate 680ms cubic-bezier(0.22, 0.61, 0.36, 1) var(--enter-delay, 0ms);
  }
  :root[data-entered] .hero h1 { --enter-delay: 80ms; }
  :root[data-entered] .hero p.lead { --enter-delay: 200ms; }
  :root[data-entered] .hero .store-badges { --enter-delay: 260ms; }
  :root[data-entered] .hero .hero-phone { transition: opacity 900ms ease 160ms; }

  @starting-style {
    :root[data-entered] .hero :is(.hero__eyebrow, h1, p.lead, .store-badges) {
      opacity: 0;
      translate: 0 16px;
    }
    :root[data-entered] .hero .hero-phone { opacity: 0; }
  }
}

@media (max-width: 900px) {
  .hero__layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
  }
  .hero__copy { max-width: 700px; }
  .hero-phone { width: min(78vw, 330px, calc((100svh - 108px) * 1080 / 2340)); }
}

@media (max-width: 520px) {
  .hero { padding-block: 36px 32px; }
  .hero h1 { --hero-h1-size: clamp(42px, 13vw, 58px); }
  .hero p.lead { font-size: 18px; line-height: 1.55; }
  .hero-phone { width: min(78vw, 282px, calc((100svh - 108px) * 1080 / 2340)); }
}
/* ---------- Device frame ----------
   CSS rather than baked into the PNG: one asset stays crisp at any DPI, follows the
   visitor's theme, and can be restyled without re-exporting every screenshot.
   Aspect is 1080 / 2340 = the 2232-tall capture plus a 108 status band, so the shot
   keeps its native ratio with no crop and the island never lands on app content. */
.device {
  /* The captures' own background. Sampling the PNG is not enough: MarketingImage
     serves a WebP to every modern browser, and a lossy WebP decoded this exact flat
     grey one unit brighter, which is the seam that survived the last attempt at this.
     The encoder, not this value, is the fix — the screenshot WebPs are lossless. */
  --shot-bg: #f5f5f5;
  /* The capture is 17.8px shorter than the screen below this band, and that remainder
     is deliberate: it sits under the app's own bottom bar and reads as the gesture
     area a real phone leaves there. Growing the band to swallow it (6.7618% makes the
     capture fill exactly) pushed the app hard against the bottom bezel. */
  --device-band: 4.615%;
  position: relative;
  aspect-ratio: 1080 / 2340;
  margin-inline: auto;
  /* A percentage padding resolves against the PARENT's width, not the element's, so
     the bezel used to be 2.05% of whatever column the phone happened to sit in —
     measured 2.05% in the hero and 6.8% in the import set piece at 834px, i.e. the
     same drawing reading as two different devices on one page. Everything inside the
     frame is now sized in cqi, which is 1% of the device's own width. */
  container-type: inline-size;
  padding: 0;
  border-radius: 15.5%/7.15%;
  /* Intentional fixed illustration palette; do not tokenize these device frame hex colors. */
  background:
    /* the rim highlights, across the band */
    linear-gradient(90deg,
      #7c7c86 0%, #4a4a53 0.9%, #202027 2.6%, #0d0d10 6%,
      #08080a 50%,
      #0d0d10 94%, #202027 97.4%, #4a4a53 99.1%, #7c7c86 100%),
    /* the length of the body, so the band is not uniform end to end */
    linear-gradient(163deg, #34343a 0%, #0e0e10 14%, #08080a 52%, #101013 78%, #2a2a30 100%);
  box-shadow:
    0 0 0 0.6px rgba(255, 255, 255, 0.30) inset,
    0 1px 0 rgba(255, 255, 255, 0.22) inset,
    0 0 0 1px rgba(0, 0, 0, 0.55),
    0 2px 3px rgba(17, 17, 17, 0.10),
    0 20px 38px -18px rgba(17, 17, 17, 0.28),
    0 58px 96px -46px rgba(17, 17, 17, 0.44);
}

.device__screen {
  position: absolute;
  inset: 2.05cqi;
  display: flex;
  flex-direction: column;
  /* Its own container, so the status bar can scale with the screen rather than with
     whatever column the phone is in. */
  container-type: inline-size;
  border-radius: 13.7%/6.35%;
  overflow: hidden;
  background: var(--shot-bg);
}

/* The glass edge, painted OVER the capture instead of under it. As an inset shadow on
   the screen itself it landed beneath the image — which spans the full screen width —
   so the dark rim and its inner glow were visible across the status band and then
   stopped dead at the capture's top edge. That discontinuity, not the colours, is the
   line you can see where the screenshot meets the frame: it runs the full width and
   the eye reads it as two different surfaces. Above the content it closes around the
   whole screen, and the capture becomes part of one continuous piece of glass. */
.device__screen::before {
  content: "";
  position: absolute;
  z-index: 3;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.72) inset,
    0 0 8px 1px rgba(0, 0, 0, 0.18) inset;
}

/* Drawn, not captured: the app screenshots stop above it, and without it the screen
   ends in bare whitespace and the frame stops reading as a device. */
.device__screen::after {
  content: "";
  position: absolute;
  z-index: 1;
  left: 50%;
  bottom: 0.85%;
  width: 35%;
  height: 0.42%;
  min-height: 3px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: var(--home-indicator, rgba(17, 17, 17, 0.28));
  pointer-events: none;
}

/* iOS metrics against a 393pt screen, all of it in cqi so it tracks the phone: the
   time is SF Pro Semibold 17pt (4.33%), and the row sits on the ISLAND's centreline
   rather than the band's — the band is taller than the island, and centring in it left
   the whole row riding high. The 2.94cqi top pad is that difference, doubled because
   the row is centred in what the padding leaves. The face is the system UI stack on
   purpose: this is OS chrome being drawn, not Suby's voice, and a geometric brand sans
   reads instantly wrong at status-bar size. */
.device__status {
  flex: 0 0 var(--device-band);
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* 6.2cqi, not the 14pt (3.56%) iOS measures against a square edge: this row sits
     24.6px below the screen top, where the corner is still curving and has already
     taken 7.9px of it. At 3.56cqi the clock and battery cleared the glass by 5.5px and
     read as though they were sliding off; this leaves 15.3px. */
  padding: 2.94cqi 6.2cqi 0 6.2cqi;
  font-family: -apple-system, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
  font-size: 4.33cqi;
  font-weight: 500;
  color: var(--status-fg, #111);
  letter-spacing: -0.01em;
  /* Chrome on macOS subpixel-antialiases by default, which fattens SF's strokes well
     past how iOS renders the same glyphs; the clock read a weight heavier than the
     phone it is imitating. Greyscale smoothing is what the device actually does. */
  -webkit-font-smoothing: antialiased;
}

/* The time's leading edge and the battery's trailing edge carry the SAME inset, so the
   row is symmetric about the island. Centring the time inside the left ear is closer to
   what iOS does, but against this frame it pushed the clock 49.7px off the glass while
   the battery sat 23.2px off its own — the imbalance read as a mistake. */
.device__time {
  font-variant-numeric: tabular-nums;
}

.device__glyphs svg {
  display: block;
  height: 3.56cqi;
  width: auto;
}

.device__shot {
  flex: 1 1 auto;
  min-height: 0;
  display: block;
  width: 100%;
  object-fit: cover;
  object-position: top center;
}


/* The island has to float clear of the bezel. Sitting flush against the screen
   edge, a near-black island and a near-black bezel merge into one shape and the
   island reads as clipped off. The top offset is the bezel thickness plus the
   same margin a real device leaves above it. */
.device__island {
  position: absolute;
  z-index: 2;
  /* Small and high on purpose: an oversized island against a thin bezel reads as a
     black slab rather than a cutout. In cqi for the same reason as the bezel — `top`
     was a percentage of HEIGHT while the bezel was a percentage of the parent's
     WIDTH, so between 600 and 900px the island slid out of the screen and welded
     itself to the bezel, which is exactly what this offset exists to prevent. */
  top: 4.66cqi;
  left: 50%;
  transform: translateX(-50%);
  width: 26cqi;
  height: 7.4cqi;
  border-radius: 999px;
  background: #050506;
}

/* Side hardware. Everything else about this frame was already right; what made it
   read as a rounded rectangle rather than a phone was that its edges were unbroken.
   Four nubs in cqi, so they scale with the device the way the bezel does. Behind the
   frame (z-index 0) so only the protruding half shows. */
.device__side {
  position: absolute;
  z-index: 0;
  width: 1.35cqi;
  border-radius: 0.4cqi;
  pointer-events: none;
}

/* Intentional fixed illustration palette; do not tokenize these device frame hex colors. */

.device__side--action,
.device__side--volume-up,
.device__side--volume-down {
  left: -0.85cqi;
  /* Lit from the outer rim inwards, so a button reads as the same milled metal as the
     rail it sits in rather than a dark tab stuck to the side. */
  background: linear-gradient(90deg, #6f6f79 0%, #3a3a42 34%, #17171b 100%);
  box-shadow: -0.15cqi 0 0.35cqi rgba(0, 0, 0, 0.38);
}

.device__side--power,
.device__side--volume {
  right: -0.85cqi;
  background: linear-gradient(270deg, #6f6f79 0%, #3a3a42 34%, #17171b 100%);
  box-shadow: 0.15cqi 0 0.35cqi rgba(0, 0, 0, 0.38);
}

/* Measured off an iPhone 15/16 Pro (146.6mm tall, 70.6mm wide) and converted into
   cqi, which is a share of the device's WIDTH — so every millimetre down the side is
   multiplied by the 2.1667 aspect on the way in. The old values were roughly half
   these, which bunched all four buttons into the top third of the rail:
     action    26.2mm -> 17.9% of height -> 38.8cqi, 7.5mm long  -> 11.1cqi
     volume up 37.7mm -> 25.7%           -> 55.7cqi, 10.4mm      -> 15.4cqi
     volume dn 51.4mm -> 35.1%           -> 76.0cqi, 10.4mm      -> 15.4cqi
     side      44.4mm -> 30.3%           -> 65.6cqi, 22.0mm      -> 32.5cqi */
.device__side--action { top: 38.8cqi; height: 11.1cqi; }
.device__side--volume-up { top: 55.7cqi; height: 15.4cqi; }
.device__side--volume-down { top: 76cqi; height: 15.4cqi; }
.device__side--power { top: 65.6cqi; height: 32.5cqi; }

@media (prefers-reduced-motion: no-preference) {
  .device { transition: transform var(--transition); }
}

/* Android: the same outer box as the iPhone, so switching platforms never moves the
   layout, and the same screen inset, so a recreation keeps its size. What changes is
   what reads as a Pixel: flatter corners, a punch-hole instead of the island, the
   buttons on the right only, a shorter gesture handle and Android's own status bar.
   The punch sits on the status row's centreline, where a Pixel's camera and clock meet. */
.device--android { border-radius: 11%/5.1%; }
.device--android .device__screen { border-radius: 9.6%/4.4%; }
.device--android .device__screen::after { width: 26%; }

/* Intentional fixed illustration palette; do not tokenize these device frame hex colors.
   `top` centres the punch on the status row: the 2.05cqi bezel, plus half the 9.81cqi band
   (4.615% of the 212.57cqi-tall screen), less the 1.7cqi radius. Any higher and it leaves
   too little glass above it and merges into the bezel, as the .device__island note warns. */
.device__punch {
  position: absolute;
  z-index: 2;
  top: 5.25cqi;
  left: 50%;
  transform: translateX(-50%);
  width: 3.4cqi;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #050506;
}

.device--android .device__side--power { top: 47.7cqi; height: 15.2cqi; }
.device--android .device__side--volume { top: 71.5cqi; height: 30.3cqi; }

.device__status--android {
  padding: 0 6.4cqi;
  font-family: Roboto, "Google Sans", system-ui, sans-serif;
  font-size: 3.9cqi;
  font-weight: 500;
  letter-spacing: 0;
}

.device__status--android .device__glyphs svg { height: 3.4cqi; }

/* Illustration palette: the app's own colours (shared/.../ui/theme/Color.kt, AppColors.kt, Theme.kt),
   not the site's tokens. Recreations of app screens must look like the app. */
.app-surface {
  --app-bg: #F5F5F5; --app-surface: #FFFFFF; --app-surface-low: #F8F8F8;
  --app-surface-high: #F0F0F0; --app-surface-highest: #E8E8E8; --app-surface-variant: #EEEEEE;
  --app-ink: #111111; --app-on-ink: #FFFFFF; --app-muted: #6E6E6E; --app-secondary: #666666;
  --app-outline: #CCCCCC; --app-outline-variant: #D6D6D6;
  --app-green: #2E7D32; --app-green-fg: #276B2A; --app-orange: #EF6C00; --app-orange-fg: #A64500; --app-red: #C62828;
  --app-int-1: #E6E6E6; --app-int-2: #D0D0D0; --app-int-3: #B6B6B6;
  --app-font: "Outfit", "Outfit Cyrillic", system-ui, sans-serif;
  /* SubyMotion.kt: Easing.Enter, Easing.Exit and Easing.Move, and Compose's default tween easing (FastOutSlowIn). */
  --app-ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);
  --app-ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15);
  --app-ease-move: cubic-bezier(0.2, 0, 0, 1);
  --app-ease-fosi: cubic-bezier(0.4, 0, 0.2, 1);
  /* 1/393 of THIS element's content box, resolved where it is used. Descendants get it
     right; this element's own padding, border or radius in dp would resolve against the
     next container out (the viewport when there is none), so pad an inner element. */
  --dp: calc(100cqi / 393);
  container-type: inline-size;
  font-family: var(--app-font);
  color: var(--app-ink);
  -webkit-font-smoothing: antialiased;
}
.app-surface :where(p, ul, ol) { margin: 0; padding: 0; list-style: none; }
/* SubyAmountText sets amounts in Outfit in every locale, Russian included. */
.app-surface .amount {
  font-family: "Outfit", "Outfit Cyrillic", system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  letter-spacing: calc(0.2 * var(--dp));
}

html[lang="ru"] .app-surface { --app-font: "Montserrat", "Outfit", "Outfit Cyrillic", system-ui, sans-serif; }

.device--android .app-surface { --dp: calc(100cqi / 412); }

:root[data-theme="dark"] .app-surface:not(:is(.xp-layer[data-kind="app"], .hero-phone, .bento__panel, .feature-detail-shot--statement-review) .app-surface) {
  --app-bg: #090909; --app-surface: #131313; --app-surface-low: #101010;
  --app-surface-high: #1A1A1A; --app-surface-highest: #2A2A2A; --app-surface-variant: #1A1A1A;
  --app-ink: #FFFFFF; --app-on-ink: #000000; --app-muted: #888888; --app-secondary: #AAAAAA;
  --app-outline: #555555; --app-outline-variant: #3A3A3A;
  --app-green: #43A047; --app-green-fg: #43A047; --app-orange: #FB8C00; --app-orange-fg: #FB8C00; --app-red: #E53935;
  --app-int-1: #242424; --app-int-2: #383838; --app-int-3: #4E4E4E;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .app-surface:not(:is(.xp-layer[data-kind="app"], .hero-phone, .bento__panel, .feature-detail-shot--statement-review) .app-surface) {
    --app-bg: #090909; --app-surface: #131313; --app-surface-low: #101010;
    --app-surface-high: #1A1A1A; --app-surface-highest: #2A2A2A; --app-surface-variant: #1A1A1A;
    --app-ink: #FFFFFF; --app-on-ink: #000000; --app-muted: #888888; --app-secondary: #AAAAAA;
    --app-outline: #555555; --app-outline-variant: #3A3A3A;
    --app-green: #43A047; --app-green-fg: #43A047; --app-orange: #FB8C00; --app-orange-fg: #FB8C00; --app-red: #E53935;
    --app-int-1: #242424; --app-int-2: #383838; --app-int-3: #4E4E4E;
  }
}

.app-card {
  background: var(--app-surface);
  border: calc(1 * var(--dp)) solid var(--app-outline);
  border-radius: calc(24 * var(--dp));
}

.app-chip {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  border-radius: calc(20 * var(--dp));
  padding: calc(8 * var(--dp)) calc(12 * var(--dp));
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

.app-avatar {
  flex: none;
  width: calc(32 * var(--dp));
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--app-surface-high);
  font: 700 calc(14 * var(--dp))/1 var(--app-font);
}

.app-avatar--you {
  background: var(--app-ink);
  color: var(--app-surface);
}

/* The app draws a logo over surfaceContainerHigh and fits it; the letter underneath
   is ServiceMonogram, which only shows when there is no logo or it fails to load. */
.app-logo {
  position: relative;
  flex: none;
  border-radius: 28%;
  overflow: hidden;
  background: var(--app-surface-high);
}
.app-logo--54 { width: calc(54 * var(--dp)); height: calc(54 * var(--dp)); }

.app-logo__face,
.app-logo__letter,
.app-logo__img {
  position: absolute;
  inset: 0;
}

.app-logo__letter {
  display: grid;
  place-items: center;
  font: 600 calc(16 * var(--dp))/1 var(--app-font);
  color: var(--app-ink);
}

.app-logo__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--app-surface-high);
}

.app-logo__img[data-failed="true"] { display: none; }

.app-section-header {
  padding: 0 0 calc(4 * var(--dp)) calc(4 * var(--dp));
  font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
  color: var(--app-ink);
}


@media (max-width: 768px) {
  .hero-split { grid-template-columns: 1fr; gap: 48px; }
}

@media (max-height: 480px) {
  .hero {
    padding-block: clamp(32px, 8vh, 64px);
  }
  .section {
    padding-block: var(--section-y-compact);
  }
}

/* ---------- Link arrow ----------
   The `feature-card__*` names are a legacy: the card they were written for is
   gone, but LinkArrow still emits them by default (components/Icons.kt), so the
   classes are live on the feature-detail pages. */
/* Tactile press feedback on large card links — restrained 0.98 for big targets. */
.blog-card:active {
  transform: translateY(0) scale(0.98);
}

.feature-card__link,
.inline-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 18px;
  font-size: 15px;
  font-weight: var(--weight-bold);
}

.feature-card__link-icon,
.inline-link__icon {
  width: 16px;
  height: 16px;
  transition: transform var(--transition);
}

.inline-link:hover .inline-link__icon {
  transform: translateX(2px);
}

.feature-card__link-icon svg,
.inline-link__icon svg {
  width: 100%;
  height: 100%;
}

/* Shared overline / uppercase-label base — per-selector deltas follow after this rule. */
.eyebrow,
.footer__col h4,
.blog-card__meta,
.blog-meta,
.service-directory__heading,
.legal__toc h2 {
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  font-weight: var(--weight-semibold);
  color: var(--muted);
}

.eyebrow {
  text-align: center;
  margin-bottom: 14px;
  font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-caps-wide);
}

.section h2 {
  font-size: var(--text-h2);
  text-align: center;
  margin-bottom: var(--stack-gap);
}

/* Left-aligned section header. Centring is right for a symmetric section
   (pricing, the close) and wrong for one whose body is a left-aligned column —
   there the centred heading floats free of the thing it introduces. */
.section--start > .container > h2,
.section--start .section-head h2 {
  text-align: start;
}

/* Editorial section header: heading left, lead parked on the right rail. The
   columns deliberately match .hero-split's, so the lead lands exactly on the
   media column beneath it rather than nearly on it. Falls to a single column
   below 900px, where two columns would each be too narrow. */
.section-head {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: 64px;
  align-items: end;
  margin-bottom: var(--stack-gap);
}

.section-head > * { min-width: 0; }

.section-head h2 {
  margin-bottom: 0;
}

.section-head__lead {
  color: var(--muted);
  font-size: var(--text-lg);
  max-width: var(--measure-narrow);
  margin: 0;
  padding-bottom: 0.5em;
}

@media (max-width: 900px) {
  .section-head {
    grid-template-columns: 1fr;
    gap: 20px;
  }
  .section-head__lead { padding-bottom: 0; }
}

/* ---------- Gallery: one film strip at every width ----------
   Three-across on desktop stacked six 1284x2778 captures into an 1871px section —
   the tallest thing on the page, spent on slides the feature grid above has
   already made the argument for. The horizontal strip the small screens were
   already getting is the right form everywhere: six frames, one gesture, and the
   sixth peeking past the edge so it is obvious there are more. */
.screenshots {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* The strip is a section-level child, not a container child, so it runs to the
     viewport edge; this inset puts the first frame back on the container's left
     edge, and the last one is free to leave the screen. That overhang is the
     affordance — it is how you know there are more frames without being told. */
  --strip-inset: max(var(--gutter), calc(50vw - (var(--container-width) / 2) + var(--gutter)));
  padding-inline: var(--strip-inset) var(--gutter);
  padding-bottom: var(--space-6);
  /* Without this, mandatory snapping aligns frame one to the scrollport's own
     start edge and scrolls the inset straight off — the strip loads already
     nudged left, out of line with the heading above it. */
  scroll-padding-inline-start: var(--strip-inset);
  scrollbar-width: thin;
}

/* The flex items are <picture> wrappers (webp) or bare <img>; size the item, not
   the inner <img>, or the picture keeps its intrinsic width and overflows. */
.screenshots > img,
.screenshots > picture {
  flex: 0 0 clamp(210px, 24vw, 300px);
  min-width: 0;
  scroll-snap-align: start;
}

.screenshots img {
  border-radius: var(--radius);
  border: 1px solid var(--border);
  width: 100%;
  height: auto;
}

@media (max-width: 1024px) {
  .screenshots > img,
  .screenshots > picture {
    flex: 0 0 60%;
    scroll-snap-align: center;
  }
}

@media (max-width: 600px) {
  .screenshots > img,
  .screenshots > picture {
    flex: 0 0 78%;
  }
}

/* ---------- /get QR ----------
   Only rendered when the UA redirect does not fire, i.e. for a desktop visitor who cannot
   install a phone app on the machine they are reading this on. The code keeps a white plate
   in both themes on purpose: an inverted QR is not reliably scannable. */
.get-page__qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-8);
  padding-top: var(--space-7);
  border-top: 1px solid var(--rule);
}

.get-page__qr h2 {
  font-size: var(--text-lg);
  margin: 0;
}

.get-page__qr-code {
  width: 168px;
  height: 168px;
  padding: var(--space-3);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

.get-page__qr .muted {
  margin: 0;
  font-size: var(--text-sm);
}

/* ---------- Pricing ---------- */
.pricing-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
  max-width: 800px;
  margin-inline: auto;
  /* Equal heights padded the shorter plan with 250px of nothing. Letting each
     card end where its list ends also says the true thing: one of them offers
     more, and you can see how much more without reading a word. */
  align-items: start;
}

.pricing-card {
  padding: var(--space-40) var(--space-7);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.pricing-card--featured {
  background: var(--ink);
  color: var(--on-ink);
  border-color: var(--ink);
}

.pricing-card h3 { font-size: 24px; margin-bottom: 8px; }

.pricing-card .price {
  font-size: 48px;
  font-weight: var(--weight-bold);
  /* The annual line sits between the price and the list now, so the price loses
     its own bottom margin when a subline follows it. */
  margin-block: var(--space-4) var(--space-6);
  letter-spacing: var(--tracking-heading);
  font-variant-numeric: tabular-nums;
}

.pricing-card .price:has(+ .price__subline) { margin-block-end: var(--space-1); }

.price__subline {
  font-size: var(--text-md);
  font-weight: var(--weight-medium);
  opacity: .78;
  margin-block-end: var(--space-6);
  font-variant-numeric: tabular-nums;
}

.pricing-grid + .store-badges { margin-block-start: var(--space-6); }

.price__suffix {
  font-size: var(--text-md);
  font-weight: var(--weight-medium);
}

.inline-svg {
  display: contents;
}

.pricing-card ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.pricing-card li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.pricing-card li::before {
  content: "✓";
  font-weight: var(--weight-bold);
  flex-shrink: 0;
}

@media (max-width: 768px) {
  .pricing-grid { grid-template-columns: 1fr; }
}

/* ---------- FAQ ---------- */
.faq {
  max-width: 800px;
  margin-inline: auto;
}

.faq details {
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--border);
}

.faq summary {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}

.faq summary::-webkit-details-marker { display: none; }

.faq summary::after {
  content: "+";
  font-size: 28px;
  font-weight: var(--weight-regular);
  transition: transform var(--transition);
  flex-shrink: 0;
}

.faq details > *:not(summary) {
  margin-top: var(--space-4);
  color: var(--muted);
}

.faq__more {
  text-align: center;
  margin-top: var(--space-40);
}

.faq__more .inline-link {
  padding-block: var(--space-2);
}

/* ---------- FAQ: heading column + question column ----------
   At full width the heading sat alone above a 900px stack of questions and was
   out of view for most of the section. In its own sticky column it stays with
   the questions it introduces, and the questions get a measure they can be read
   at instead of the full container. */
@media (min-width: 900px) {
  #faq > .container {
    display: grid;
    grid-template-columns: minmax(0, 0.62fr) minmax(0, 1.38fr);
    gap: clamp(32px, 5vw, 80px);
    align-items: start;
  }

  #faq h2 {
    position: sticky;
    top: 104px;
    text-align: start;
    margin-bottom: 0;
  }

  #faq .faq {
    margin: 0;
  }

  #faq .faq__more {
    text-align: start;
  }
}

/* ---------- CTA strip ---------- */
.cta-strip {
  background: var(--ink);
  color: var(--on-ink);
  text-align: center;
  padding-block: var(--section-y-cta);
}

.cta-strip h2 {
  font-size: var(--text-h2);
  color: var(--on-ink);
  margin-bottom: var(--space-7);
}

.cta-strip__note {
  margin: 14px 0 0;
  text-align: center;
  color: color-mix(in srgb, var(--on-ink) 60%, transparent);
  font-size: var(--text-sm);
}

/* ---------- Footer ---------- */
.footer {
  border-top: 1px solid var(--border);
  padding-block: 64px var(--space-7);
}

/* Five equal columns against a Features list ten links long left one column
   running twice as far down the page as the other four. Giving that column the
   width of two and letting its links run in a sub-grid squares the block off. */
.footer__inner {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-6) var(--space-7);
  align-items: start;
}

.footer__col--wide {
  grid-column: span 2;
}

.footer__col--wide .footer__col-links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--space-6);
}

.footer__col h4 {
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
  letter-spacing: var(--tracking-caps-tight);
  color: var(--ink);
}

.footer__col a {
  display: block;
  font-size: var(--text-sm);
  color: var(--muted);
  padding-block: 9px;
}

.footer__col a:hover { color: var(--ink); opacity: 1; }

/* «Конфиденциальность» is wider than its column at every six-column width, and ran into «GitHub».
   Russian breaks a word that long at a syllable; a shorter one keeps wrapping between words. */
html[lang="ru"] .footer__col a {
  hyphens: auto;
  hyphenate-limit-chars: 12 4 4;
  overflow-wrap: break-word;
}

.footer__credit {
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
  font-size: var(--text-eyebrow);
  color: var(--muted);
  text-align: center;
}

@media (max-width: 768px) {
  .footer__inner { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer__col--wide { grid-column: span 2; }
}

@media (max-width: 520px) {
  .footer__col--wide .footer__col-links { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Feature pages ---------- */
/* The breadcrumb is the first band under the sticky header; tighten its top and drop its
   bottom, then keep the hero close to the bar (was clamp(72px,10vw,132px) — too much empty
   space above the fold). Mirrors the service-detail treatment. */
.feature-detail-breadcrumb {
  padding-block: clamp(20px, 3vw, 32px) 0;
}

.feature-detail-hero {
  padding-top: clamp(16px, 2.5vw, 32px);
}

.feature-detail-copy {
  max-width: var(--measure);
}

.feature-detail-copy h1 {
  font-size: var(--text-h1);
  margin-bottom: var(--space-6);
  letter-spacing: var(--tracking-heading);
}

.feature-detail-copy .lead {
  color: var(--muted);
  font-size: clamp(19px, 2vw, 24px);
}


.feature-detail-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: var(--space-7);
}

.feature-detail-shot {
  position: relative;
  width: min(100%, 392px);
}

.feature-detail-live {
  width: min(100%, 392px);
  margin-inline: auto;
}

.feature-detail-live .showcase-note {
  text-align: center;
}

.feature-detail-shot--lock-screen {
  --status-fg: #fff;
  --home-indicator: rgba(255, 255, 255, 0.72);
}

.feature-detail-shot--lock-screen:not(.device--android) .device__time { visibility: hidden; }

.feature-detail-body {
  max-width: 900px;
}

.feature-detail-body h2 {
  text-align: left;
  margin-bottom: var(--space-7);
}

.feature-bullets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--space-4);
  /* The UA's 1em list margin was offsetting the whole column from the capture
     beside it by 16px in a layout that start-aligns both. */
  margin: 0;
  padding: 0;
  list-style: none;
}


@media (max-width: 860px) {
  .feature-bullets {
    grid-template-columns: 1fr;
  }
}

/* ---------- Homepage split-subscriptions section ----------
   Left-aligned header above a two-column showcase, so the bullets sit in a
   single narrow column next to the screenshot instead of the default pair.
   The header shares the bullets' left edge: centred, it belonged to neither
   column and left the eye to find its way down on its own. */
.split-section h2 {
  margin-bottom: var(--space-4);
}

.split-section .feature-bullets {
  grid-template-columns: 1fr;
}

/* Three short bullets beside a full-height portrait capture left ~400px of
   nothing under the text. Cropping the capture to a window — top-anchored, so
   the part that carries the meaning is the part that survives — makes the two
   columns land together and reads as a look into the screen rather than a
   screenshot that ran out of copy to sit next to. */
.split-section .hero-split > picture {
  display: block;
  min-width: 0;
}

.split-section .feature-detail-shot {
  width: min(100%, 392px);
  height: clamp(320px, 40vw, 500px);
  object-fit: cover;
  object-position: top center;
  border-radius: var(--radius-card);
  border: 1px solid var(--hairline);
  box-shadow: var(--lift-2);
  /* The crop is a window, so it should not end on a hard edge. */
  -webkit-mask-image: linear-gradient(180deg, #000 76%, transparent);
          mask-image: linear-gradient(180deg, #000 76%, transparent);
}

.split-section .inline-link {
  margin-top: var(--space-6);
}

/* ---------- Legal pages (Privacy, Terms) ---------- */
.legal {
  max-width: var(--measure);
  margin-inline: auto;
}

.legal h1 {
  font-size: var(--text-h1);
  margin-bottom: var(--space-4);
}

.legal .updated {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-8);
}

.legal h2 {
  font-size: 24px;
  /* `.section h2` centres every section heading at the same specificity and comes first, so the
     alignment has to be restated here or a left-aligned page gets a centred heading. */
  text-align: start;
  margin-top: var(--space-8);
  margin-bottom: var(--space-4);
}

.legal h3 {
  font-size: var(--text-md);
  margin-top: var(--space-7);
  margin-bottom: var(--space-2);
}

.legal table {
  width: 100%;
  min-width: 36rem;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.legal tbody tr:last-child td {
  border-bottom: 0;
}

.legal th, .legal td {
  text-align: left;
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.legal th { font-weight: var(--weight-semibold); }

/* ---------- Comparison pages ---------- */
.compare-breadcrumb {
  padding-block: clamp(20px, 3vw, 32px) 0;
}

.compare {
  padding-block: clamp(16px, 2.5vw, 32px) var(--section-y);
}

.compare h1 {
  font-size: clamp(32px, 5vw, 56px);
  margin-bottom: var(--space-4);
}

.compare h2 {
  font-size: clamp(24px, 3vw, 32px);
  margin-block: var(--space-8) var(--space-4);
}

.compare h3 {
  font-size: var(--text-md);
  margin-block: var(--space-6) var(--space-2);
}

.compare p,
.compare ul {
  max-width: var(--measure);
}

.compare__lead {
  color: var(--muted);
  font-size: clamp(18px, 2vw, 22px);
}

.compare__asof,
.compare__note,
.compare__sources {
  color: var(--muted);
  font-size: var(--text-sm);
}

.compare__cta {
  margin-block: var(--space-6) var(--space-3);
}

.compare__sources li {
  margin-bottom: var(--space-1);
  overflow-wrap: anywhere;
}

.compare li a,
.compare p a {
  text-decoration: underline;
}

/* A table wider than its column scrolls inside this box instead of widening the page. The fade
   is a mask on the scroller, so the frame and the focus ring live on the wrapper: a mask would
   fade the border with the content and clip an outline drawn on the thing it masks.
   ScrollableTable sets the edge attributes. */
.table-scroll {
  --edge-fade: 40px;
  margin-block: var(--space-5);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}

.table-scroll__body {
  overflow-x: auto;
  border-radius: calc(var(--radius-lg) - 1px);
  scrollbar-width: thin;
  /* A column scrolled into view by keyboard focus stops short of the fade instead of under it. */
  scroll-padding-inline: var(--edge-fade);
}

@supports selector(:has(*)) {
  .table-scroll__body:focus-visible {
    outline: none;
  }

  .table-scroll:has(.table-scroll__body:focus-visible) {
    outline: 2px solid var(--focus-ring);
    outline-offset: 4px;
  }
}

.table-scroll[data-edge-end] > .table-scroll__body {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--edge-fade)), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - var(--edge-fade)), transparent);
}

.table-scroll[data-edge-start] > .table-scroll__body {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - var(--edge-fade)), transparent);
          mask-image: linear-gradient(to left, #000 calc(100% - var(--edge-fade)), transparent);
}

.table-scroll[data-edge-start][data-edge-end] > .table-scroll__body {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--edge-fade), #000 calc(100% - var(--edge-fade)), transparent);
          mask-image: linear-gradient(to right, transparent, #000 var(--edge-fade), #000 calc(100% - var(--edge-fade)), transparent);
}

.compare-table {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  font-size: var(--text-sm);
  line-height: 1.45;
}

.compare-table--wide {
  min-width: 900px;
}

.compare-table th,
.compare-table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
}

.compare-table thead th {
  background: var(--surface);
  border-bottom-color: var(--border);
  font-weight: var(--weight-bold);
}

.compare-table thead a {
  text-decoration: underline;
}

.compare-table tbody th {
  width: 16%;
  font-weight: var(--weight-semibold);
}

.compare-table tbody tr:last-child th,
.compare-table tbody tr:last-child td {
  border-bottom: 0;
}

.compare-pointer {
  padding-block: var(--space-7);
  text-align: center;
}

.compare-pointer .inline-link {
  margin-top: 0;
}

/* ---------- Category price hubs ---------- */
.hub-breadcrumb {
  padding-block: clamp(20px, 3vw, 32px) 0;
}

.hub {
  padding-block: clamp(16px, 2.5vw, 32px) var(--section-y);
}

.hub__inner {
  max-width: 880px;
}

.hub h1 {
  font-size: clamp(32px, 5vw, 56px);
  margin-bottom: var(--space-4);
}

.hub .lead {
  max-width: var(--measure);
  color: var(--muted);
}

.hub-table {
  width: 100%;
  margin-block: var(--space-6);
  border-collapse: collapse;
  font-size: var(--text-sm);
  line-height: 1.4;
}

.hub-table th,
.hub-table td {
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: middle;
}

.hub-table thead th {
  border-bottom-color: var(--border);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.hub-table tbody th {
  font-weight: var(--weight-semibold);
}

.hub-table__link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.hub-table__name {
  overflow-wrap: anywhere;
}

.hub-table__price {
  font-variant-numeric: tabular-nums;
}

.hub-table__where {
  display: block;
  color: var(--muted);
  font-size: var(--text-xs);
}

.hub-table td.hub-table__plans,
.hub-table th.hub-table__plans {
  width: 4.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.hub-others {
  margin-top: var(--space-7);
}

.hub-others__label {
  margin: 0 0 var(--space-3);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.hub-others__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
}

.hub-others__list a {
  text-decoration: underline;
}

/* Shared underlined-link base — per-selector color/weight/offset deltas live in each rule. */
.legal a,
.blog-card__link,
.prose a,
.support-email-card a {
  text-decoration: underline;
}

.legal__toc {
  background: var(--surface);
  padding: var(--space-6) var(--space-7);
  border-radius: var(--radius);
  margin-bottom: var(--space-8);
}

.legal__toc h2 {
  margin-top: 0;
  font-size: var(--text-base);
  letter-spacing: var(--tracking-caps-tight);
  font-weight: var(--weight-bold);
  color: var(--ink);
}

.legal__toc ol {
  margin: 0;
  padding-left: var(--space-5);
}

.legal__toc li {
  margin-bottom: 6px;
  font-size: 15px;
}

/* ---------- Support page ---------- */
.support-hero {
  text-align: center;
  padding-block: var(--section-y-cta);
}

.support-hero h1 {
  font-size: var(--text-h1);
}

.support-email-card {
  max-width: 560px;
  margin: var(--space-8) auto 0;
  padding: var(--space-40);
  background: var(--surface);
  border-radius: var(--radius-lg);
  text-align: center;
}

.support-email-card a {
  font-size: 24px;
  font-weight: var(--weight-semibold);
}

/* Set in Montserrat the address is 12em, a sixth wider than in Outfit, and at 24px it ran
   off a 320px screen. */
html[lang="ru"] .support-email-card a { font-size: min(24px, 6vw); }

.support-email-card__label {
  margin-bottom: var(--space-2);
}

/* A 4px ink stripe down one edge is the generic callout costume, and it forced
   the card to give up two of its corners. A hairline all the way round and the
   recessed surface say the same thing in this system's own vocabulary. */
.support-tip {
  max-width: 760px;
  margin: var(--space-8) auto 0;
  padding: var(--space-6) var(--space-7);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: 15px;
  color: var(--muted);
}

/* ---------- Press kit ---------- */
.press-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-floor-card), 100%), 1fr));
  gap: var(--space-6);
  margin-block: var(--space-40);
}

.press-asset {
  padding: var(--space-6);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.press-asset h4 { font-size: var(--text-base); margin: 0; }

.press-asset .meta {
  font-size: var(--text-eyebrow);
  color: var(--muted);
}

.press-asset a {
  margin-top: auto;
  font-weight: var(--weight-semibold);
  text-decoration: underline;
}

.press-bio {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7);
  background: var(--surface);
  padding: var(--space-40);
  border-radius: var(--radius-lg);
  margin-top: var(--space-8);
}

/* ---------- 404 ---------- */
.not-found {
  text-align: center;
  padding-block: clamp(96px, 16vw, 200px);
}

.not-found h1 {
  font-size: clamp(80px, 16vw, 200px);
  margin: 0;
  letter-spacing: var(--tracking-display);
}

.not-found p {
  font-size: 24px;
  color: var(--muted);
  margin-bottom: var(--space-7);
}

.not-found__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: center;
}

/* ---------- Utilities ---------- */
.muted { color: var(--muted); }

/* ---------- Focus visibility (keyboard navigation only) ---------- */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: inherit;
}

/* Mouse clicks stay clean — no ring on pointer focus. */
:focus:not(:focus-visible) {
  outline: none;
}

/* The focus ring is an alpha tint of --ink, so on an inverted band it composites
   straight into the ground: measured at 1.00:1 on .snap in both themes, which is
   a WCAG 2.4.7 failure, not a subtle one. Every ink surface that can hold a
   focusable child needs the on-ink token — this list is the set of them. */
.cta-strip :focus-visible,
.snap :focus-visible,
.lang-banner :focus-visible {
  outline-color: var(--on-ink);
}

/* Mirror the nav hover underline when a link gets keyboard focus. */
.nav a:focus-visible::after {
  transform: scaleX(1);
}

@media (forced-colors: active) {
  :focus-visible {
    outline: 2px solid CanvasText;
  }
}

/* ---------- Skip link ---------- */
.skip-link {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.skip-link:focus-visible {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 1000;
  width: auto;
  height: auto;
  margin: 0;
  padding: var(--space-3) var(--space-5);
  overflow: visible;
  clip: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--ink);
  font-weight: var(--weight-semibold);
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}


.catalog-lead {
  max-width: 720px;
}

/* Tighten the gap between the intro copy and the search panel. */
.catalog-intro {
  padding-bottom: clamp(16px, 2.5vw, 28px);
}

.catalog-section {
  padding-top: clamp(28px, 4vw, 44px);
}

.catalog-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

@media (max-width: 600px) {
  .catalog-controls {
    flex-direction: column;
    align-items: stretch;
  }
  .catalog-search {
    width: 100%;
  }
}

.catalog-search {
  flex: 1 1 280px;
  padding: 14px 18px 14px 46px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--bg)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E")
    no-repeat 18px center;
  color: var(--ink);
  font: inherit;
  font-size: var(--text-base);
  transition: border-color var(--transition), box-shadow var(--transition);
}

.catalog-search::placeholder {
  color: var(--muted);
}

.catalog-search:focus-visible {
  outline: none;
  border-color: var(--ink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 12%, transparent);
}

/* Stacked, the 280px flex-basis sizes the main axis — which is now height — and
   the pill radius paints the result as a 358x280 ellipse. Must sit after the
   base .catalog-search rule to win on source order. */
@media (max-width: 600px) {
  .catalog-search {
    flex: 0 0 auto;
  }
}

.catalog-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* 44 chips wrap to ~935px of filter before the first tile on a phone. One
   scrollable row instead. Must sit after the base rule to win on source order. */
@media (max-width: 600px) {
  .catalog-chips {
    flex-wrap: nowrap;
    /* min-width:auto on a flex item lets it grow to its 5,200px of content and
       html's overflow-x:clip then hides the overflow instead of scrolling it. */
    min-width: 0;
    max-width: 100vw;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }

  .catalog-chips::-webkit-scrollbar { display: none; }

  .catalog-chips > * {
    flex: 0 0 auto;
    scroll-snap-align: start;
  }
}

.catalog-chip {
  padding: 10px var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), color var(--transition), transform var(--transition);
}

.catalog-chip:active { transform: scale(0.96); }

.catalog-chip[aria-pressed="true"] {
  background: var(--ink);
  color: var(--on-ink);
  border-color: var(--ink);
}

/* The count and the one way out of a filter share a line: the count on the left, a quiet
   Clear on the right, both on one baseline. The Clear button's padding is cancelled by an
   equal negative margin so its hit area grows without making the line taller. */
.catalog-statusline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.catalog-statusline--active {
  margin-bottom: var(--space-4);
}

.catalog-clear {
  margin-inline-start: auto;
  margin-block: calc(var(--space-2) * -1);
  padding: var(--space-2) 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: var(--text-sm);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color var(--transition);
}

.catalog-clear:hover { color: var(--ink); }

/* `.section h2` centres every section heading; this one sits above a left-aligned search and
   grid, so centred it floated free of both. The extra class beats that rule on specificity.
   Its gap to the controls is the section-level one, matching the gap `.section h2` leaves
   below it; the filtered status line keeps the tighter control spacing. */
.section .catalog-heading {
  margin-top: var(--stack-gap);
  text-align: left;
}

.catalog-status {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.catalog-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-7);
}

.catalog-more__status {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.catalog-hint {
  margin: -14px 0 var(--space-28);
  max-width: 680px;
  color: var(--muted);
  font-size: var(--text-base);
}

.catalog-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: var(--space-8) 0 var(--space-2);
}

.catalog-empty .btn {
  margin-top: 14px;
}

.catalog-empty__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

.catalog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--grid-floor-tile), 100%), 1fr));
  gap: var(--space-4);
}

.service-tile {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
  transition: transform var(--transition), border-color var(--transition);
}

.service-tile:hover {
  transform: translateY(-2px);
  border-color: var(--ink);
  opacity: 1;
}

.service-tile:active {
  transform: translateY(0) scale(0.96);
}

/* Filtered tiles ease in instead of popping. @starting-style provides the
   enter-from state; reduced motion collapses it via the global killswitch. */
@supports (transition-behavior: allow-discrete) {
  @media (prefers-reduced-motion: no-preference) {
    .service-tile {
      transition: transform var(--transition),
                  border-color var(--transition),
                  opacity var(--dur-base) var(--ease);
    }
    @starting-style {
      .service-tile {
        opacity: 0;
        transform: translateY(6px);
      }
    }
  }
}

/* ---------- Service logo: the app-icon tile ----------
   The hero, the related tiles, the catalogue grid and the category tables draw a service's logo the same way,
   each at its own --logo-size. A logo is one of three looks, set on the tile as data-logo:
     bleed    an app icon: it fills the tile edge to edge. An icon with a margin of its own is cropped to its
              artwork (--crop-x/-y/-w/-h, fractions of the image), and one rounder than the tile gives the tile
              its radius (--logo-radius), so no sliver of the ground shows at a corner;
     contain  a mark with room around it, inset on paper;
     light    a mark drawn in near-white, inset on the dark ground, where it can be seen.
   The build reads each logo it can and writes the look into the page. A logo it could not read is stamped by
   ServiceLogo once it has loaded, and until then, or with no script, it is contained.
   Logos are drawn for a light ground, so paper stays behind the image in every look but `light`: a dark
   single-colour mark (DistroKid) would vanish straight on the dark page, and a glyph cut out of an icon
   (Orange, Duolingo) would vanish into any other ground. */
.service-detail-logo,
.service-tile__logo {
  position: relative;
  flex: none;
  width: var(--logo-size);
  height: var(--logo-size);
  border-radius: var(--logo-radius, var(--radius-app-icon));
  overflow: hidden;
  background: var(--on-ink-fixed);
}

/* The ring is drawn above the artwork, so an icon that bleeds takes it too and a dark one keeps an edge on a dark page. */
.service-detail-logo::after,
.service-tile__logo::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--img-outline);
  pointer-events: none;
}

.service-tile__logo { --logo-size: 40px; }

.service-detail-logo {
  --logo-size: 88px;
  margin-bottom: var(--space-5);
  box-shadow: var(--lift-1);
}

@media (max-width: 640px) {
  .service-detail-logo { --logo-size: 72px; }
}

.service-detail-logo__placeholder,
.service-tile__logo-placeholder {
  position: absolute;
  inset: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-fixed);
  font-size: calc(var(--logo-size) * 0.4);
  font-weight: var(--weight-bold);
}

.service-detail-logo__img,
.service-tile__logo-img {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: 14%;
  object-fit: contain;
  background: var(--on-ink-fixed);
  transition: opacity var(--dur-fast) var(--ease);
}

.service-detail-logo[data-logo="bleed"] > img,
.service-tile__logo[data-logo="bleed"] > img {
  inset: auto;
  top: calc(var(--crop-y, 0) * -100% / var(--crop-h, 1));
  left: calc(var(--crop-x, 0) * -100% / var(--crop-w, 1));
  width: calc(100% / var(--crop-w, 1));
  height: calc(100% / var(--crop-h, 1));
  max-width: none;
  padding: 0;
  object-fit: cover;
}

.service-detail-logo[data-logo="light"],
.service-tile__logo[data-logo="light"],
.service-detail-logo[data-logo="light"] > img,
.service-tile__logo[data-logo="light"] > img {
  background: var(--ink-fixed);
}

.service-detail-logo[data-logo="light"] > span,
.service-tile__logo[data-logo="light"] > span {
  color: var(--on-ink-fixed);
}

/* Held back until it is known which it is, so a logo never changes shape under the reader; the letter waits too. */
.service-detail-logo[data-logo="pending"] > img,
.service-tile__logo[data-logo="pending"] > img {
  opacity: 0;
}

.service-detail-logo[data-logo="pending"] > span,
.service-tile__logo[data-logo="pending"] > span {
  visibility: hidden;
}

/* On load failure the image hides, revealing the monogram placeholder beneath. */
.service-detail-logo__img[data-failed="true"],
.service-tile__logo-img[data-failed="true"] {
  display: none;
}

.service-tile__text {
  flex: 1 1 auto;
  min-width: 0;
}

.service-tile__name {
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.service-tile__sub {
  color: var(--muted);
  font-size: var(--text-eyebrow);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The breadcrumb is the first band under the sticky header; a full section gap on top
   leaves the page looking empty until you scroll. Tighten its top, drop its bottom. */
.service-detail-breadcrumb {
  padding-block: clamp(20px, 3vw, 32px) 0;
}

/* ---------- Service page: hero ----------
   Two columns from 900px: on the left the logo, the H1, the page's one-sentence answer, quiet facts and
   the one action; on the right always a card. Below 900px the same blocks stack in that order. */
.service-detail-hero {
  padding-top: clamp(12px, 2vw, 24px);
  padding-bottom: clamp(28px, 3.6vw, 48px);
}

.service-detail-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
}

@media (min-width: 900px) {
  .service-detail-header {
    grid-template-columns: minmax(0, 1fr) minmax(320px, 400px);
    column-gap: clamp(40px, 6vw, 88px);
    align-items: start;
  }
}

.service-hero__main,
.service-hero__copy {
  min-width: 0;
}

.service-hero__copy h1 {
  min-width: 0;
  margin: 0 0 var(--space-5);
  font-size: clamp(32px, 4.6vw, 56px);
  line-height: 1;
  letter-spacing: var(--tracking-heading);
}

/* The price sentence leads in full ink; the catalogue's blurb is somebody else's copy and sits under it,
   quieter. A service with no prices has only the blurb, which then leads in the quiet colour. */
.service-detail-copy .lead {
  max-width: 62ch;
  margin: 0;
  color: var(--muted);
  font-size: clamp(18px, 1.7vw, 21px);
  line-height: 1.45;
}

.service-detail-copy .service-detail-facts {
  color: var(--ink);
  font-weight: var(--weight-medium);
}

.service-detail-blurb {
  max-width: 62ch;
  margin: var(--space-3) 0 0;
  color: var(--muted);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}

.service-hero__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-5) 0 0;
  padding: 0;
  list-style: none;
  font-variant-numeric: tabular-nums;
}

.service-hero__facts > li {
  display: flex;
  max-width: 100%;
}

.service-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 100%;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: 1.4;
  white-space: nowrap;
}

.service-chip--link {
  color: var(--ink);
  transition: border-color var(--transition);
}

.service-chip--link:hover {
  border-color: var(--ink);
  opacity: 1;
}

.service-chip__text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.service-chip__icon svg {
  flex: none;
  width: 14px;
  height: 14px;
}

.service-hero__action {
  min-width: 0;
  margin-top: var(--space-6);
}

.service-hero__action-label {
  margin: 0 0 var(--space-3);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
}

.service-detail-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ---------- Service page: the hero's card ----------
   One frame for the three cards: the price card, the plans card and the reminder preview. */
.service-hero__card {
  min-width: 0;
  padding: var(--space-5);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface);
}

/* ---------- Service page: the price card ----------
   One country's plans. The list holds the height of the longest country's (--price-card-rows, set from
   the data), so picking another country never moves anything under the card. */
.price-card {
  --price-card-row-h: 72px;
}

.price-card .price-card__title {
  margin: 0 0 var(--space-4);
  font-size: var(--text-title);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.15;
  text-align: start;
}

.price-card__regions {
  display: flex;
  gap: var(--space-2);
  margin: 0 calc(var(--space-5) * -1) var(--space-4);
  padding: 0 var(--space-5);
  overflow-x: auto;
  scrollbar-width: none;
}

.price-card__regions::-webkit-scrollbar { display: none; }

.price-card__region {
  flex: none;
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font: var(--weight-semibold) var(--text-sm)/1 var(--font);
  letter-spacing: 0.04em;
  cursor: pointer;
}

.price-card__region[aria-pressed="true"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--on-ink);
}

.price-card__plans {
  min-height: calc(var(--price-card-rows, 1) * var(--price-card-row-h));
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
  list-style: none;
}

.price-card__plan {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 2px var(--space-3);
  min-height: var(--price-card-row-h);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--hairline);
}

.price-card__plan-name {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px var(--space-2);
  min-width: 0;
  font-weight: var(--weight-medium);
}

.price-card__plan--primary .price-card__plan-name { font-weight: var(--weight-bold); }

.plan-pill {
  padding: 2px 8px;
  border: 1px solid var(--ink);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  line-height: 1.4;
}

.price-card__price {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  align-items: baseline;
  gap: 6px;
  justify-self: end;
  font-variant-numeric: tabular-nums;
}

.price-card__amount {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  letter-spacing: -0.02em;
}

.price-card__plan--primary .price-card__amount { font-size: var(--text-title); }

.price-card__per,
.price-card__yearly {
  color: var(--muted);
  font-size: var(--text-sm);
}

.price-card__yearly {
  grid-column: 2;
  grid-row: 2;
  justify-self: end;
  font-variant-numeric: tabular-nums;
}

/* The line holds its place whether or not it has text, so it filling in once the reader's country is known
   moves nothing under it. */
.price-card__app-note {
  min-height: 1.4em;
  margin: var(--space-3) 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: 1.4;
}

.price-card__all { margin-top: var(--space-4); }

.regional-price__label {
  display: block;
  margin-bottom: var(--space-2);
  color: var(--muted);
  font-size: var(--text-sm);
}

.regional-price__select {
  width: 100%;
  min-height: var(--suby-control-min-size);
  padding: var(--space-3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
}

.regional-price__select:focus-visible {
  outline: var(--suby-focus-outline);
  outline-offset: var(--suby-focus-outline-offset);
}

.regional-price__result {
  min-height: calc(var(--space-7) * 3);
  padding-block: var(--space-4);
}

.regional-price__result p { margin: 0 0 var(--space-2); }
.regional-price__example,
.regional-price__note {
  color: var(--muted);
  font-size: var(--text-sm);
}
.regional-price__provider { margin-bottom: var(--space-4); }
.regional-price__app { width: 100%; }

/* ---------- Service page: the plans card ----------
   What a service with plans and no published prices has in the card's place: the plans by name, the one most
   people are on first, and a footer instead of a price column that would have nothing to hold. */
.plans-card .plans-card__title {
  /* Two classes on purpose: `.section h2` (0,1,1) centres every section heading, and a bare class here loses to it. */
  margin: 0 0 var(--space-4);
  font-size: var(--text-title);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.15;
  text-align: start;
}

.plans-card__list {
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
  list-style: none;
}

.plans-card__plan {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px var(--space-2);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  font-weight: var(--weight-medium);
}

.plans-card__plan--primary {
  font-weight: var(--weight-bold);
}

.plans-card__name {
  min-width: 0;
  overflow-wrap: anywhere;
}

.plans-card__note {
  margin: var(--space-4) 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: 1.4;
}

/* ---------- Service page: the reminder preview ----------
   What a service with no plans on file has in the card's place: the notification Suby sends, drawn static, and
   one quiet line under it. */
.reminder-card__notification {
  padding: var(--space-4);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--bg);
  box-shadow: var(--lift-1);
}

.reminder-card__header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  color: var(--muted);
  font-size: var(--text-sm);
}

.reminder-card__mark {
  position: relative;
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-app-icon);
  background: var(--ink);
}

.reminder-card__mark::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--on-ink);
  -webkit-mask: url("/assets/img/logo-mono.svg") center / contain no-repeat;
          mask: url("/assets/img/logo-mono.svg") center / contain no-repeat;
}

.reminder-card__app {
  font-weight: var(--weight-semibold);
}

.reminder-card__time {
  margin-inline-start: auto;
}

.reminder-card__title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.3;
}

.reminder-card__body {
  margin: 0;
  color: var(--muted);
}

.reminder-card__line {
  margin: var(--space-4) 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
}

/* ── What it costs ─────────────────────────────────────────────────────────────
   The hero's card answers for one country; this section is every price, in a table per cadence. The
   tables keep to ruled rows and tabular digits and add nothing a figure does not need. */
.service-billing .service-billing__heading {
  /* Two classes on purpose: `.section h2` (0,1,1) centres every section heading, and a bare
     class here loses to it. */
  margin: 0 0 var(--space-5);
  text-align: left;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  letter-spacing: -0.02em;
}

.ledger {
  /* The rule between rows IS the component. A border on the block would draw a card. */
  border-top: 1px solid var(--hairline);
  /* Capped and centred so the rules share an axis with the heading above them. At full
     container width the value column ran 1000px for the word "Monthly", which reads as a
     table that lost its other columns rather than as a deliberate measure. */
  max-width: 760px;
  margin-inline: auto;
}

.ledger__row {
  display: grid;
  grid-template-columns: minmax(9rem, 22%) 1fr;
  gap: clamp(16px, 3vw, 40px);
  align-items: baseline;
  padding-block: clamp(14px, 1.6vw, 20px);
  border-bottom: 1px solid var(--hairline);
}

.ledger__label {
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.ledger__value {
  /* Every value in this block is a fact about money or availability, and the app's own
     Tabular Money Rule says those digits never jitter. */
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-medium);
}

.ledger__plans,
.ledger__countries {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 10px;
}

/* Plans are separated by a rule, not a bullet: the middot is punctuation pretending to be
   structure, and at three or more names it stops reading as a list at all. */
.ledger__plan + .ledger__plan {
  padding-left: 10px;
  border-left: 1px solid var(--hairline);
}

.ledger__plan--primary {
  font-weight: var(--weight-bold);
}

.ledger__country {
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  font-variant-numeric: normal;
  letter-spacing: 0.04em;
}

/* ── Price table ───────────────────────────────────────────────────────────── */
/* Wide content scrolls inside its own container; the page body never scrolls sideways. */
/* ── The note under the prices ──────────────────────────────────────────────
   One quiet line: when the prices were read and, for countries left to the app, where to find them. */
.price-matrix__note {
  margin: var(--space-3) 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
}

.price-matrix__note-link {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── Prices by country ──────────────────────────────────────────────────────
   Countries down, plans across, one table per cadence. The first column stays put while the rest scroll
   sideways on a narrow screen, and the table scrolls inside its own box so the page never does. */
.price-matrix__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.price-matrix__label {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.price-matrix__toggle {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
}

.price-matrix__cadence {
  min-height: 32px;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  font: var(--weight-semibold) 14px/1 var(--font);
  cursor: pointer;
}

.price-matrix__cadence[aria-pressed="true"] {
  background: var(--ink);
  color: var(--on-ink);
}

.price-matrix__scroll {
  /* The containing block for the table's visually hidden labels: left to the initial one they sit at
     their static position inside a table wider than the screen, outside this box's clip, and a phone
     widens the whole page to reach them. */
  position: relative;
  overflow-x: auto;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  scrollbar-width: thin;
}

.price-matrix__scroll[hidden] { display: none; }

/* A scrollable region is a tab stop, so it needs a visible focus ring of its own. */
.price-matrix__scroll:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 4px;
}

.price-matrix__table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.price-matrix__table th,
.price-matrix__table td {
  padding: 14px var(--space-4);
  border-bottom: 1px solid var(--hairline);
  background: var(--bg);
  text-align: right;
  white-space: nowrap;
}

.price-matrix__table thead th {
  background: var(--surface);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.price-matrix__table tbody tr:last-child > * { border-bottom: 0; }

.price-matrix__table thead th:first-child,
.price-matrix__table tbody th {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  text-align: left;
}

.price-matrix__table thead th:first-child { z-index: 2; }

.price-matrix__country { font-weight: var(--weight-medium); }

.price-matrix__code {
  margin-inline-start: var(--space-2);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
}

.price-matrix__none { color: var(--muted); }

/* The reader's own row: a tint and weight, never colour alone. The mark is out of flow, so it costs no
   layout when the region resolves after the page has booted. */
.price-matrix__row--viewer > * { background: var(--surface); }

.price-matrix__row--viewer .price-matrix__name { font-weight: var(--weight-bold); }

.price-matrix__you {
  position: absolute;
  inset-inline-end: var(--space-3);
  top: 50%;
  translate: 0 -50%;
  padding: 2px 8px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
}

@media (min-width: 641px) {
  .price-matrix__table tbody th { padding-inline-end: calc(var(--space-4) + 6.5rem); }
}

@media (max-width: 640px) {
  .price-matrix__you { display: none; }

  .price-matrix__table th,
  .price-matrix__table td { padding-inline: var(--space-3); }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Why track it in Suby ───────────────────────────────────────────────────
   Three one-line reasons, the same on every service page, then the store badges. */
.service-value,
.service-billing,
.service-faq {
  padding-block: clamp(28px, 3.6vw, 48px);
}

.service-value .service-value__heading {
  margin: 0 0 var(--space-5);
  text-align: left;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  letter-spacing: -0.02em;
}

.service-value__points {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.service-value__point {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface);
}

.service-value__icon {
  display: grid;
  flex: none;
  width: 40px;
  height: 40px;
  place-items: center;
  border-radius: var(--radius-icon);
  background: var(--ink);
  color: var(--on-ink);
}

.service-value__icon svg {
  width: 20px;
  height: 20px;
}

.service-value__text {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.35;
}

@media (max-width: 899px) {
  .service-value__points { grid-template-columns: minmax(0, 1fr); }
}

.service-faq .service-faq__heading {
  margin: 0 0 var(--space-4);
  text-align: left;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  letter-spacing: -0.02em;
}

/* A plain list: the question on the left, its answer open beside it, ruled rows between. */
.service-faq__item {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  column-gap: var(--space-7);
  row-gap: var(--space-2);
  padding: var(--space-5) 0;
  border-top: 1px solid var(--hairline);
}

.service-faq__item:last-child { border-bottom: 1px solid var(--hairline); }

.service-faq__item h3 {
  margin: 0;
  font-size: var(--text-md);
}

.service-faq__item p {
  margin: 0;
  color: var(--muted);
}

@media (max-width: 760px) {
  .service-faq__item { grid-template-columns: minmax(0, 1fr); }
}

.service-detail-related .container > h2 {
  margin: 0 0 var(--space-5);
  text-align: left;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  letter-spacing: -0.02em;
}

.service-detail-related { padding-block: clamp(28px, 3.6vw, 48px); }

.service-detail-related__hub { margin-top: var(--space-5); }

.price-table__scroll {
  overflow-x: auto;
  max-width: 760px;
  margin-inline: auto;
  margin-bottom: clamp(20px, 2.4vw, 32px);
  /* A scrollable region is a tab stop, so it needs a visible focus ring of its own —
     otherwise a keyboard user lands somewhere invisible. */
  scrollbar-width: thin;
}

.price-table__scroll:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 4px;
}

.price-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.price-table th,
.price-table td {
  padding: clamp(12px, 1.4vw, 18px) 16px;
  border-bottom: 1px solid var(--hairline);
  text-align: right;
  white-space: nowrap;
}

/* The plan column is the row's label, so it reads left like a name and not like a figure. */
.price-table thead th:first-child,
.price-table tbody th {
  text-align: left;
  font-weight: var(--weight-medium);
}

.price-table thead th {
  border-bottom-color: var(--border);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.price-table__row--primary th,
.price-table__row--primary td {
  font-weight: var(--weight-bold);
}

.price-table__asof {
  color: var(--muted);
  font-size: var(--text-sm);
}

@media (max-width: 600px) {
  .ledger__row {
    grid-template-columns: 1fr;
    gap: 4px;
  }
}

.service-detail-empty {
  max-width: 720px;
}

.blog-index-hero {
  padding-bottom: 48px;
}

.blog-index-copy,
.blog-article-header {
  max-width: 820px;
}

.blog-index-section,
.blog-article-section {
  padding-top: 0;
}

.blog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-floor-card), 100%), 1fr));
  gap: 18px;
}

.blog-card {
  display: flex;
  flex-direction: column;
  min-height: 260px;
  padding: var(--space-28);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
  transition: transform var(--transition), border-color var(--transition);
}

.blog-card:hover {
  transform: translateY(-2px);
  border-color: var(--ink);
  opacity: 1;
}

.blog-card__meta,
.blog-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--text-eyebrow);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-caps-tight);
}

.blog-card__meta span + span::before,
.blog-meta span + span::before {
  content: "/";
  margin-right: var(--space-2);
  color: var(--border);
}

.blog-card h2 {
  margin-top: 22px;
  font-size: var(--text-h2);
}

.blog-card p {
  color: var(--muted);
}

.blog-card__link {
  margin-top: auto;
  color: var(--ink);
  font-weight: var(--weight-extrabold);
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
}

.blog-empty {
  padding: var(--space-7);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
}

.blog-empty h2 {
  text-align: start;
}

.blog-article-hero {
  padding-bottom: 48px;
}

.blog-article-header h1 {
  max-width: 900px;
}

.blog-cover {
  width: 100%;
  max-height: 520px;
  margin-top: 36px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  object-fit: cover;
}

.blog-cover[data-failed="true"] {
  display: none;
}

.hero__phone[data-failed="true"],
.feature-detail-shot[data-failed="true"],
.screenshots img[data-failed="true"] {
  visibility: hidden;
}

.blog-article {
  max-width: 780px;
  margin: 0 auto;
  padding: clamp(28px, 5vw, 56px);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
}

.prose {
  max-width: var(--measure);
  color: var(--ink);
}

.prose > *:first-child {
  margin-top: 0;
}

.prose h1,
.prose h2,
.prose h3 {
  margin-top: 1.6em;
}

.prose p,
.prose li {
  color: var(--muted);
  text-wrap: pretty;
}

.prose a {
  color: var(--ink);
  font-weight: var(--weight-bold);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.prose blockquote {
  margin: var(--space-28) 0;
  padding: var(--space-1) 0 var(--space-1) 22px;
  border-left: 3px solid var(--ink);
  color: var(--ink);
}

.prose pre {
  overflow-x: auto;
  padding: 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.prose code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: 0.92em;
}

.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  color: var(--muted);
}

.breadcrumb__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.breadcrumb__item + .breadcrumb__item::before {
  content: "/";
  color: var(--muted);
  opacity: 0.6;
}

.breadcrumb__link {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s ease;
}

.breadcrumb__link:hover,
.breadcrumb__link:focus-visible {
  color: var(--ink);
}

.breadcrumb__current {
  color: var(--ink);
}

/* ---------- Shared subscriptions ----------
   One glance: what it does on the left, and on the right a flat card of a plan split four ways with
   the one person who still owes. The card is the app's own palette at its own 1px-per-dp scale. */
.shared__layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 460px);
  align-items: start;
  column-gap: clamp(40px, 7vw, 104px);
}

.shared__copy { min-width: 0; }

.shared__stage {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
}

.shared__fine {
  margin: 0;
  padding-inline: var(--space-2);
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.split-card {
  --dp: 1px;
  padding: var(--space-6);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--app-surface);
  color: var(--app-ink);
}

.split-card__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
}

.split-card__titles {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.split-card__name {
  font: 700 19px/1.25 var(--app-font);
  overflow-wrap: anywhere;
}

.split-card__context {
  font: 500 14px/1.4 var(--app-font);
  color: var(--app-muted);
}

.split-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid color-mix(in srgb, var(--app-outline-variant) 55%, transparent);
}

.split-row__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 500 17px/1.3 var(--app-font);
}

.split-row__amount {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.3;
}

.split-chip {
  justify-content: center;
  min-width: 88px;
  padding: 6px 12px;
  background: var(--app-surface-high);
  color: var(--app-ink);
  font-size: 13px;
  line-height: 16px;
}

.split-row[data-status="paid"] .split-chip {
  background: color-mix(in srgb, var(--app-green) 15%, transparent);
  color: var(--app-green-fg);
}

.split-chip__check {
  display: inline-flex;
  margin-inline-end: 4px;
}

.split-chip__check svg {
  flex: none;
  width: 14px;
  height: 14px;
}

.split-card__owes {
  margin-top: var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid color-mix(in srgb, var(--app-outline-variant) 55%, transparent);
  font: 600 18px/1.35 var(--app-font);
}

@media (max-width: 899px) {
  .shared__layout {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-6);
  }

  .shared__stage { max-width: 460px; }
}

@media (max-width: 480px) {
  .split-card { padding: var(--space-5); }

  .split-chip { min-width: 0; }
}

.showcase-note {
  margin: var(--space-3) 0 0;
  padding-inline: var(--space-2);
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

/* ---------- OS scenes (the renewal stage) ----------
   Intentional fixed illustration palette: the wallpaper, lock clock and iOS glass are OS chrome,
   the same in both themes. Only Android's notification card follows the theme, because Android
   draws it on its own surface colour. */
.os-wallpaper {
  background:
    radial-gradient(120% 80% at 20% 10%, #3a3a3e 0%, transparent 60%),
    radial-gradient(90% 70% at 90% 90%, #26262a 0%, transparent 60%),
    linear-gradient(165deg, #1c1c1f 0%, #0b0b0d 100%);
}

.scene {
  position: absolute;
  inset: 0;
}

/* A full scene paints the status band too, so the frame's clock and glyphs sit above it. */
.device__screen:has(.scene) > .device__status {
  position: relative;
  z-index: 2;
}

.lock {
  position: relative;
  height: 100%;
  overflow: hidden;
  font-family: var(--os-font);
  color: #fff;
}

.lock--ios { --os-font: -apple-system, "SF Pro Text", system-ui, sans-serif; }
.lock--android { --os-font: Roboto, "Google Sans", system-ui, sans-serif; }

.lock__head { display: grid; }

.lock--ios .lock__head {
  justify-items: center;
  padding-top: calc(62 * var(--dp));
}

.lock--ios .lock__date {
  font: 600 calc(20 * var(--dp))/calc(24 * var(--dp)) var(--os-font);
  color: rgba(255, 255, 255, 0.85);
}

.lock--ios .lock__clock {
  margin-top: calc(-4 * var(--dp));
  font: 700 calc(96 * var(--dp))/1 var(--os-font);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.lock--ios .lock__stack {
  position: absolute;
  inset-inline: calc(16 * var(--dp));
  bottom: calc(150 * var(--dp));
}

.lock__controls {
  position: absolute;
  inset-inline: calc(46 * var(--dp));
  bottom: calc(44 * var(--dp));
  display: flex;
  justify-content: space-between;
}

.lock__control {
  display: grid;
  place-items: center;
  width: calc(50 * var(--dp));
  height: calc(50 * var(--dp));
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  -webkit-backdrop-filter: saturate(165%) blur(24px);
  backdrop-filter: saturate(165%) blur(24px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

.lock__control svg {
  width: calc(22 * var(--dp));
  height: calc(22 * var(--dp));
}

.lock--android .lock__head {
  justify-items: start;
  padding: calc(58 * var(--dp)) calc(28 * var(--dp)) 0;
}

.lock--android .lock__clock {
  font: 400 calc(56 * var(--dp))/calc(64 * var(--dp)) var(--os-font);
  font-variant-numeric: tabular-nums;
}

.lock--android .lock__date {
  margin-top: calc(4 * var(--dp));
  font: 400 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--os-font);
}

.lock--android .lock__stack {
  margin: calc(28 * var(--dp)) calc(16 * var(--dp)) 0;
}

/* iOS 26 Clear glass: web-platform.md's estimates, Apple publishes no notification spec. */
.notif--ios {
  display: grid;
  grid-template-columns: calc(38 * var(--dp)) minmax(0, 1fr) calc(32 * var(--dp));
  grid-template-areas:
    "icon top top"
    "icon body thumb";
  align-items: center;
  column-gap: calc(10 * var(--dp));
  padding: calc(14 * var(--dp));
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: calc(24 * var(--dp));
  background: rgba(28, 28, 30, 0.52);
  -webkit-backdrop-filter: saturate(165%) blur(24px);
  backdrop-filter: saturate(165%) blur(24px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
  color: #fff;
}

.notif--ios .notif__app-icon {
  grid-area: icon;
  width: calc(38 * var(--dp));
  aspect-ratio: 1;
  border-radius: 22.5%;
  background: #fff url("/assets/img/icon-512.png") center / cover no-repeat;
}

.notif__top {
  grid-area: top;
  display: flex;
  align-items: baseline;
  gap: calc(8 * var(--dp));
  min-width: 0;
}

.notif--ios .notif__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 600 calc(15 * var(--dp))/calc(20 * var(--dp)) var(--os-font);
}

.notif--ios .notif__time {
  flex: none;
  font: 400 calc(13 * var(--dp))/calc(20 * var(--dp)) var(--os-font);
  color: rgba(255, 255, 255, 0.6);
}

.notif--ios .notif__body {
  grid-area: body;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  font: 400 calc(15 * var(--dp))/calc(20 * var(--dp)) var(--os-font);
}

.notif--ios .notif__thumb {
  grid-area: thumb;
  width: calc(32 * var(--dp));
  height: calc(32 * var(--dp));
  border-radius: calc(8 * var(--dp));
}

/* Android 16's classic expanded template (AOSP dimens, web-platform.md). */
.notif--android {
  display: grid;
  grid-template-columns: calc(24 * var(--dp)) minmax(0, 1fr) auto;
  grid-template-areas:
    "icon header expand"
    ". content large"
    "actions actions actions";
  column-gap: calc(12 * var(--dp));
  padding: calc(16 * var(--dp)) calc(16 * var(--dp)) calc(4 * var(--dp));
  border-radius: calc(28 * var(--dp));
  background: var(--app-surface);
  color: var(--app-ink);
}

.notif--android .notif__app-icon {
  grid-area: icon;
  position: relative;
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
  border-radius: 50%;
  background: var(--app-surface-high);
}

.notif--android .notif__app-icon::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--app-ink);
  -webkit-mask: url("/assets/img/logo-mono.svg") center / contain no-repeat;
  mask: url("/assets/img/logo-mono.svg") center / contain no-repeat;
}

.notif--android .notif__header {
  grid-area: header;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--os-font);
  color: var(--app-muted);
}

.notif__expand {
  grid-area: expand;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  height: calc(24 * var(--dp));
  padding: 0 calc(8 * var(--dp));
  border-radius: calc(12 * var(--dp));
  background: var(--app-surface-high);
}

.notif__expand svg {
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

.notif__content {
  grid-area: content;
  display: grid;
  min-width: 0;
  margin-top: calc(4 * var(--dp));
}

.notif--android .notif__title {
  font: 500 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--os-font);
}

.notif--android .notif__body {
  font: 400 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--os-font);
  color: var(--app-muted);
}

.notif__large-icon {
  grid-area: large;
  align-self: start;
  width: calc(40 * var(--dp));
  height: calc(40 * var(--dp));
  margin-top: calc(4 * var(--dp));
  border-radius: 50%;
}

/* The label lines up with the title; the button's own padding hangs into the icon column. */
.notif__actions {
  grid-area: actions;
  display: flex;
  align-items: center;
  height: calc(48 * var(--dp));
  margin-top: calc(4 * var(--dp));
  padding-inline-start: calc(24 * var(--dp));
}

.notif__action {
  padding: calc(8 * var(--dp)) calc(12 * var(--dp));
  font: 500 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--os-font);
  color: var(--app-ink);
}

/* Before the lock scene is reached the reminder has not arrived yet. Without either attribute
   (no script, the export, reduced motion) it rests in place. */
:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--lock .notif {
  opacity: 0;
  translate: 0 calc(16 * var(--dp));
}

/* ---------- Home screen and its two widgets (the renewal story) ----------
   Intentional fixed illustration palette: the wallpaper, icon glass, search, dock and labels are
   OS chrome, the same in both themes. The widgets follow the theme at the free default of 100%
   background opacity. Neither system animates a placed widget, so nothing inside one moves. */

/* The launch zoom into the calendar grows from the Payments calendar widget's centre, 343dp down
   the iPhone's 393 × 871dp screen and 394.5dp down the Pixel's 412 × 913dp one. */
.device__screen:has(.scene--home) { --widget-cal-origin: 50% 39.4%; }
.device--android .device__screen:has(.scene--home) { --widget-cal-origin: 50% 43.2%; }

.home {
  position: relative;
  height: 100%;
  overflow: hidden;
  font-family: var(--os-font);
  color: #fff;
}

.home--ios,
.widget--ios { --os-font: -apple-system, "SF Pro Text", system-ui, sans-serif; }

.home--android,
.widget--android { --os-font: Roboto, "Google Sans", system-ui, sans-serif; }

.home__layer {
  position: absolute;
  inset: 0;
}

.home__grid { display: grid; }

.home__apps {
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
}

.home__slot,
.home__app {
  display: grid;
  align-content: start;
  justify-items: center;
  min-width: 0;
}

.home__app-label,
.home__widget-label {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.home__icon {
  display: grid;
  place-items: center;
}

.home__icon--suby { background: #fff url("/assets/img/icon-512.png") center / cover no-repeat; }

.home__icon--glyph {
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.18);
}

.home__icon--glyph svg {
  width: 46%;
  height: 46%;
}

.home__search svg {
  flex: none;
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

/* iOS: four 68dp columns 22dp apart fill the medium widget's 338dp, and a 96dp row puts the small
   widget's bottom on the second icon row's bottom, so its "Suby" lines up with that row's labels. */
.home--ios .home__grid {
  grid-template-columns: repeat(4, calc(68 * var(--dp)));
  grid-auto-rows: calc(96 * var(--dp));
  justify-content: center;
  column-gap: calc(22 * var(--dp));
  padding-top: calc(72 * var(--dp));
}

.home--ios .home__slot--np { grid-area: 1 / 1 / span 2 / span 2; }
.home--ios .home__apps { grid-area: 1 / 3 / span 2 / span 2; }
.home--ios .home__slot--cal { grid-area: 3 / 1 / span 2 / -1; }

.home--ios :is(.home__slot, .home__app) { row-gap: calc(4 * var(--dp)); }

.home--ios :is(.home__app-label, .home__widget-label) {
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--os-font);
}

.home--ios .home__icon {
  width: calc(62 * var(--dp));
  height: calc(62 * var(--dp));
  border-radius: 22.5%;
}

.home--ios .home__search {
  position: absolute;
  inset-inline: 0;
  bottom: calc(124 * var(--dp));
  width: fit-content;
  height: calc(30 * var(--dp));
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: calc(4 * var(--dp));
  padding: 0 calc(12 * var(--dp)) 0 calc(10 * var(--dp));
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: calc(15 * var(--dp));
  background: rgba(255, 255, 255, 0.18);
  -webkit-backdrop-filter: saturate(165%) blur(24px);
  backdrop-filter: saturate(165%) blur(24px);
  font: 500 calc(13 * var(--dp))/1 var(--os-font);
}

.home--ios .home__search svg {
  width: calc(14 * var(--dp));
  height: calc(14 * var(--dp));
}

.home--ios .home__dock {
  position: absolute;
  inset-inline: calc(12 * var(--dp));
  bottom: calc(14 * var(--dp));
  height: calc(96 * var(--dp));
  display: grid;
  grid-template-columns: repeat(4, calc(68 * var(--dp)));
  justify-content: center;
  align-items: center;
  justify-items: center;
  column-gap: calc(22 * var(--dp));
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: calc(38 * var(--dp));
  background: rgba(255, 255, 255, 0.14);
  -webkit-backdrop-filter: saturate(165%) blur(24px);
  backdrop-filter: saturate(165%) blur(24px);
}

/* Android: WidgetKind's 73dp cell (73n - 16dp, WidgetKind.kt:18-19) across five columns, each
   widget inset 8dp in its cells, so the 2 × 2 is 130 × 130dp and the 5 × 3 is 349 × 203dp. The
   icon rows are 80dp so a label clears the icon under it; the 2 × 2 keeps its 130dp at the top. */
.home--android .home__layer { padding-top: calc(52 * var(--dp)); }

.home--android .home__glance {
  display: flex;
  align-items: center;
  height: calc(73 * var(--dp));
  padding-inline: calc(31.5 * var(--dp));
  font: 400 calc(22 * var(--dp))/calc(28 * var(--dp)) var(--os-font);
}

.home--android .home__grid {
  grid-template-columns: repeat(5, calc(73 * var(--dp)));
  grid-template-rows: repeat(2, calc(80 * var(--dp)));
  grid-auto-rows: calc(73 * var(--dp));
  justify-content: center;
}

.home--android .home__slot { padding: calc(8 * var(--dp)); }
.home--android .home__slot--np { grid-area: 1 / 1 / span 2 / span 2; }
.home--android .home__apps { grid-area: 1 / 4 / span 2 / span 2; }
.home--android .home__slot--cal { grid-area: 3 / 1 / span 3 / -1; }

.home--android .home__app {
  row-gap: calc(2 * var(--dp));
  padding-top: calc(4 * var(--dp));
}

.home--android .home__app-label {
  font: 400 calc(12 * var(--dp))/calc(15 * var(--dp)) var(--os-font);
}

.home--android .home__icon {
  width: calc(56 * var(--dp));
  height: calc(56 * var(--dp));
  border-radius: 50%;
}

.home--android .home__dock {
  position: absolute;
  inset-inline: calc(23.5 * var(--dp));
  bottom: calc(96 * var(--dp));
  display: flex;
  justify-content: space-around;
}

.home--android .home__search {
  position: absolute;
  inset-inline: calc(31.5 * var(--dp));
  bottom: calc(24 * var(--dp));
  height: calc(56 * var(--dp));
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: calc(18 * var(--dp));
  border-radius: calc(28 * var(--dp));
  background: rgba(255, 255, 255, 0.16);
}

/* Before the home screen is reached the phone is still locked. Without either attribute (no
   script, the export, reduced motion) it rests in place. */
:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--home .home__layer {
  opacity: 0;
  scale: 1.04;
}

:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--home .home__item {
  opacity: 0;
}

/* The widgets. iOS draws on systemBackground with the system label colours (WidgetViews.swift:7-14);
   Android on glance-material3's widgetBackground, derived from secondaryContainer (#E8E8E8 /
   #2A2A2A): #F6F6F6 / #161616 are a computed approximation, not a sampled value.
   The app's own secondary text falls below the 4.5:1 floor in light, so each platform keeps its
   hue at the lightest value that clears the floor where the text actually sits. iOS: secondaryLabel
   (alpha 0.6) is 3.3:1 over the 5% watermark (#F2F2F2), and tertiaryLabel (alpha 0.3) is 1.7:1
   on white. Android: #666 is 3.95:1 where it crosses the 14% watermark (#D6D6D6). In dark, only
   iOS tertiary needs raising. */
.widget {
  position: relative;
  flex: none;
  overflow: hidden;
  background: var(--widget-bg);
  color: var(--widget-ink);
  font-family: var(--os-font);
}

.widget--ios {
  --widget-bg: #FFFFFF;
  --widget-ink: #000000;
  --widget-secondary: rgba(60, 60, 67, 0.73);
  --widget-tertiary: rgba(60, 60, 67, 0.71);
  --widget-rounded: ui-rounded, "SF Pro Rounded", -apple-system, system-ui, sans-serif;
  border-radius: calc(28 * var(--dp));
}

/* Glance sets labels and the hero as Outfit ExtraBold bitmaps (WidgetLabelBitmap.kt:13-21,
   GlanceDesign.kt:274-341); Outfit has no Cyrillic, so a Russian label falls to the system face. */
.widget--android {
  --widget-bg: #F6F6F6;
  --widget-ink: var(--app-ink);
  --widget-secondary: #5D5D5D;
  --widget-display: "Outfit", Roboto, "Google Sans", system-ui, sans-serif;
  border-radius: calc(24 * var(--dp));
}

:root[data-theme="dark"] .widget--ios {
  --widget-bg: #000000;
  --widget-ink: #FFFFFF;
  --widget-secondary: rgba(235, 235, 245, 0.6);
  --widget-tertiary: rgba(235, 235, 245, 0.5);
}

:root[data-theme="dark"] .widget--android {
  --widget-bg: #161616;
  --widget-secondary: var(--app-secondary);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .widget--ios {
    --widget-bg: #000000;
    --widget-ink: #FFFFFF;
    --widget-secondary: rgba(235, 235, 245, 0.6);
    --widget-tertiary: rgba(235, 235, 245, 0.5);
  }

  :root:not([data-theme="light"]):not([data-theme="dark"]) .widget--android {
    --widget-bg: #161616;
    --widget-secondary: var(--app-secondary);
  }
}

.widget__mark {
  position: absolute;
  background: var(--widget-ink);
  -webkit-mask: url("/assets/img/logo-mono.svg") center / contain no-repeat;
  mask: url("/assets/img/logo-mono.svg") center / contain no-repeat;
  pointer-events: none;
}

/* GhostWatermark: 150pt at 5%, its corner pushed 44pt past the 16pt content box's (WidgetDesign.swift:113-124). */
.widget--ios .widget__mark {
  right: calc(-28 * var(--dp));
  bottom: calc(-28 * var(--dp));
  width: calc(150 * var(--dp));
  height: calc(150 * var(--dp));
  opacity: 0.05;
}

/* GlanceWatermark: 104dp at the vector's own 14%, flush in the corner (GlanceDesign.kt:370-387). */
.widget--android .widget__mark {
  right: 0;
  bottom: 0;
  width: calc(104 * var(--dp));
  height: calc(104 * var(--dp));
  opacity: 0.14;
}

.widget-np__body,
.widget-cal__head,
.widget-cal__body { position: relative; }

.widget__label {
  max-width: 100%;
  color: var(--widget-secondary);
}

/* WidgetLabel sets no lineLimit, so a label too long for the row wraps. */
.widget--ios .widget__label {
  font: 700 calc(9.5 * var(--dp))/calc(12 * var(--dp)) var(--os-font);
  letter-spacing: calc(1.4 * var(--dp));
}

/* GlanceLabel shrinks to its 10sp floor, then ends in '…' (WidgetLabelBitmap.kt:55-87). */
.widget--android .widget__label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 800 calc(10 * var(--dp))/calc(13 * var(--dp)) var(--widget-display);
  letter-spacing: 0.12em;
}

.widget--android .widget__hero {
  font-family: var(--widget-display);
  font-weight: 800;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.widget .app-logo { border-radius: calc(8 * var(--dp)); }

/* Next payment: iOS systemSmall (NextPaymentWidget.swift:12-33), Android's 2 × 2 column at the
   Compact tier (NextPaymentWidget.kt:63-111). */
.widget-np.widget--ios {
  width: calc(158 * var(--dp));
  height: calc(158 * var(--dp));
}

.widget-np.widget--android {
  width: calc(130 * var(--dp));
  height: calc(130 * var(--dp));
}

.widget-np__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  height: 100%;
  padding: calc(16 * var(--dp));
}

.widget-np__service {
  display: flex;
  align-items: center;
  max-width: 100%;
}

.widget-np__name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.widget-np__when {
  display: flex;
  align-items: center;
  color: var(--widget-secondary);
}

.widget--ios .widget-np__service {
  gap: calc(9 * var(--dp));
  margin: auto 0 calc(8 * var(--dp));
  padding-top: calc(2 * var(--dp));
}

.widget--ios .widget-np__tile {
  width: calc(26 * var(--dp));
  height: calc(26 * var(--dp));
}

.widget--ios .widget-np__name { font: 600 calc(13 * var(--dp))/calc(16 * var(--dp)) var(--os-font); }

.widget--ios .widget-np__amount {
  font: 800 calc(30 * var(--dp))/calc(36 * var(--dp)) var(--widget-rounded);
  font-variant-numeric: tabular-nums;
  letter-spacing: calc(-1 * var(--dp));
  white-space: nowrap;
}

.widget--ios .widget-np__when {
  gap: calc(6 * var(--dp));
  margin-top: calc(9 * var(--dp));
}

.widget--ios .widget-np__days { font: 500 calc(11.5 * var(--dp))/calc(14 * var(--dp)) var(--os-font); }
.widget--ios .widget-np__date { font: 500 calc(11 * var(--dp))/calc(14 * var(--dp)) var(--os-font); }

.widget--android .widget-np__service {
  gap: calc(8 * var(--dp));
  margin-top: calc(8 * var(--dp));
}

.widget--android .widget-np__tile {
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

.widget--android .widget-np__name { font: 500 calc(12 * var(--dp))/calc(14 * var(--dp)) var(--os-font); }

/* The hero takes the height the column leaves and sits in its middle, as a weighted Glance image does. */
.widget--android .widget-np__amount {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  min-height: 0;
  font-size: calc(26 * var(--dp));
  line-height: calc(33 * var(--dp));
}

.widget--android .widget-np__days { font: 400 calc(12 * var(--dp))/calc(14 * var(--dp)) var(--os-font); }

/* Payments calendar: iOS systemMedium (PaymentsCalendarWidget.swift:12-76), Android 5 × 3 at the
   Expanded tier with the rows and their logos beside the grid (PaymentsCalendarWidget.kt:53-219). */
.widget-cal {
  display: flex;
  flex-direction: column;
  padding: calc(16 * var(--dp));
}

.widget-cal.widget--ios {
  width: calc(338 * var(--dp));
  height: calc(158 * var(--dp));
  gap: calc(8 * var(--dp));
}

.widget-cal.widget--android {
  width: calc(349 * var(--dp));
  height: calc(203 * var(--dp));
  gap: calc(8 * var(--dp));
}

.widget-cal__body {
  display: flex;
  align-items: flex-start;
  gap: calc(16 * var(--dp));
}

.widget-cal__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  justify-items: center;
}

.widget-cal__day { color: var(--widget-ink); }

.widget-cal__day--today {
  background: var(--widget-ink);
  color: var(--widget-bg);
}

.widget-cal__day--marked::after {
  content: "";
  background: var(--widget-ink);
}

.widget-cal__rows {
  display: flex;
  flex-direction: column;
}

.widget-cal__row-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.widget-cal__row-amount { white-space: nowrap; }

.widget--ios .widget-cal__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(8 * var(--dp));
}

.widget--ios .widget-cal__total {
  flex: none;
  font: 500 calc(11 * var(--dp))/calc(13 * var(--dp)) var(--os-font);
  font-variant-numeric: tabular-nums;
  color: var(--widget-secondary);
}

.widget--ios .widget-cal__grid {
  flex: none;
  width: calc(168 * var(--dp));
  row-gap: calc(2 * var(--dp));
}

.widget--ios .widget-cal__weekday {
  font: 700 calc(8 * var(--dp))/calc(10 * var(--dp)) var(--os-font);
  color: var(--widget-tertiary);
}

.widget--ios .widget-cal__day {
  position: relative;
  display: grid;
  place-items: center;
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
  border-radius: calc(5 * var(--dp));
  font: 400 calc(9 * var(--dp))/1 var(--os-font);
}

.widget--ios .widget-cal__day--today { font-weight: 700; }

/* A 3pt dot centred 8pt below the day's centre, on the cell's bottom edge. */
.widget--ios .widget-cal__day--marked::after {
  position: absolute;
  top: calc(14.5 * var(--dp));
  left: calc(50% - 1.5 * var(--dp));
  width: calc(3 * var(--dp));
  height: calc(3 * var(--dp));
  border-radius: 50%;
}

.widget--ios .widget-cal__rows {
  flex: 1;
  min-width: 0;
  gap: calc(6 * var(--dp));
}

/* HStack(spacing: 7) with a Spacer(minLength: 4) before the amount: 7 + 4 + 7 between name and amount. */
.widget--ios .widget-cal__row {
  display: flex;
  align-items: center;
  gap: calc(7 * var(--dp));
}

.widget--ios .widget-cal__tile {
  width: calc(22 * var(--dp));
  height: calc(22 * var(--dp));
}

.widget--ios .widget-cal__row-name {
  flex: 1;
  font: 500 calc(12 * var(--dp))/calc(15 * var(--dp)) var(--os-font);
}

.widget--ios .widget-cal__row-amount {
  margin-inline-start: calc(11 * var(--dp));
  font: 700 calc(12 * var(--dp))/calc(15 * var(--dp)) var(--widget-rounded);
  font-variant-numeric: tabular-nums;
}

.widget--android .widget-cal__head {
  display: grid;
  row-gap: calc(4 * var(--dp));
}

.widget--android .widget-cal__label {
  font-size: calc(11 * var(--dp));
  line-height: calc(14 * var(--dp));
}

.widget--android .widget-cal__total {
  font-size: calc(34 * var(--dp));
  line-height: calc(43 * var(--dp));
}

/* Glance rows are top-aligned: a day sits at the top of its week and its marker hangs 1dp below. */
.widget--android .widget-cal__grid {
  flex: 1;
  min-width: 0;
  align-items: start;
}

.widget--android .widget-cal__weekday {
  font: 700 calc(9 * var(--dp))/calc(11 * var(--dp)) var(--os-font);
  color: var(--widget-secondary);
}

.widget--android .widget-cal__day {
  display: flex;
  flex-direction: column;
  align-items: center;
  font: 400 calc(9 * var(--dp))/calc(11 * var(--dp)) var(--os-font);
}

.widget--android .widget-cal__day--today {
  padding: calc(1 * var(--dp)) calc(4 * var(--dp));
  border-radius: calc(5 * var(--dp));
  color: var(--app-bg);
  font-weight: 700;
}

.widget--android .widget-cal__day--marked::after {
  width: calc(4 * var(--dp));
  height: calc(4 * var(--dp));
  margin-top: calc(1 * var(--dp));
  border-radius: calc(2 * var(--dp));
}

.widget--android .widget-cal__blank { height: calc(18 * var(--dp)); }

.widget--android .widget-cal__rows { width: calc(128 * var(--dp)); }

.widget--android .widget-cal__row {
  display: grid;
  grid-template-columns: calc(24 * var(--dp)) minmax(0, 1fr);
  align-items: center;
  column-gap: calc(8 * var(--dp));
  padding-block: calc(3 * var(--dp));
}

.widget--android .widget-cal__tile {
  grid-row: 1 / span 2;
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

.widget--android .widget-cal__row-name { font: 500 calc(13 * var(--dp))/calc(15 * var(--dp)) var(--os-font); }

.widget--android .widget-cal__row-amount {
  font: 700 calc(11 * var(--dp))/calc(13 * var(--dp)) var(--os-font);
  color: var(--widget-secondary);
}

/* ---------- Payments calendar screen (the renewal stage) ----------
   CalendarViewScreen.kt on the app background, identical on both phones. The full scene paints the
   status band too. The static frame is the one after the beat: the heaviest day ahead selected, its
   row folded away and the list narrowed to that day. A scene that has not played undoes it. */
.cal {
  /* The app fades out-of-month digits to 30% ink and a past payday's row to 55%. Where that drops
     text below 4.5:1 on the card (spec §7), the text takes the lowest share that clears it in this
     theme. */
  --cal-out-ink: 58%;
  --cal-past-ink: 0.58;
  --cal-past-muted: 0.95;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--app-bg);
  color: var(--app-ink);
}

:root[data-theme="dark"] .cal:not(:is(.xp-layer[data-kind="app"], .bento__panel) .cal) {
  --cal-out-ink: 45%;
  --cal-past-ink: 0.55;
  --cal-past-muted: 0.91;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .cal:not(:is(.xp-layer[data-kind="app"], .bento__panel) .cal) {
    --cal-out-ink: 45%;
    --cal-past-ink: 0.55;
    --cal-past-muted: 0.91;
  }
}

.cal > * { flex: none; }

/* The frame's status band is a share of the screen's height, and so is this, so the top bar starts
   where the band ends on either phone. */
.cal::before {
  content: "";
  flex: 0 0 var(--device-band, 0px);
}

.cal .cal-num {
  font-variant-numeric: tabular-nums;
  letter-spacing: calc(0.2 * var(--dp));
}

/* SubyTopBar: 64dp tall, a 44dp back slot 16dp in, the title 12dp after it (TopAppBar.kt:22-28). */
.cal__bar {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
  height: calc(64 * var(--dp));
  padding-inline: calc(16 * var(--dp));
}

.cal__back {
  display: grid;
  place-items: center;
  width: calc(44 * var(--dp));
  height: calc(44 * var(--dp));
}

.cal__back svg {
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

.cal__title { font: 700 calc(20 * var(--dp))/calc(28 * var(--dp)) var(--app-font); }

.cal-week,
.cal-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(12 * var(--dp));
  padding: calc(12 * var(--dp)) calc(16 * var(--dp));
}

.cal-week { margin: calc(8 * var(--dp)) calc(16 * var(--dp)) 0; }
.cal-summary { margin: calc(8 * var(--dp)) calc(16 * var(--dp)); }

.cal-week__lead {
  display: grid;
  justify-items: start;
  gap: calc(8 * var(--dp));
}

.cal-week__label,
.cal-week__count,
.cal-stat__label {
  font: 700 calc(10 * var(--dp))/calc(14 * var(--dp)) var(--app-font);
  letter-spacing: calc(1.5 * var(--dp));
  color: var(--app-muted);
}

.cal-week__logos {
  display: flex;
  gap: calc(4 * var(--dp));
}

.cal-week__logo {
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
  border-radius: calc(8 * var(--dp));
}

.cal-week__sum {
  display: grid;
  justify-items: end;
  gap: calc(4 * var(--dp));
}

.cal-week__total { font: 700 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font); }

.cal-summary__total {
  font-size: calc(16 * var(--dp));
  font-weight: 700;
  line-height: calc(24 * var(--dp));
}

.cal-summary__stats {
  display: flex;
  gap: calc(12 * var(--dp));
}

.cal-stat {
  display: grid;
  justify-items: center;
}

.cal-stat__value { font: 700 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font); }

.cal-month {
  margin-inline: calc(16 * var(--dp));
  padding: calc(16 * var(--dp));
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: calc(24 * var(--dp));
  background: var(--app-surface);
}

.cal-month__weekdays,
.cal-month__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
}

.cal-month__weekdays { padding-bottom: calc(8 * var(--dp)); }

.cal-month__weekday {
  text-align: center;
  font: 500 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

/* DayCell.kt:73-95: a 38dp circle over an 8dp dot row, 4dp apart. Out-of-month days take the card's
   own surface, so they read as unfilled. */
.cal-day {
  --cal-fill: var(--app-surface-low);
  --cal-digit: var(--app-muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  height: calc(50 * var(--dp));
}

.cal-day--out {
  --cal-fill: transparent;
  --cal-digit: color-mix(in srgb, var(--app-ink) var(--cal-out-ink), transparent);
}

.cal-day--today,
.cal-day--paid { --cal-digit: var(--app-ink); }

.cal-day--int-1 { --cal-fill: var(--app-int-1); }
.cal-day--int-2 { --cal-fill: var(--app-int-2); }
.cal-day--int-3 { --cal-fill: var(--app-int-3); }

.cal-day__circle {
  display: grid;
  place-items: center;
  flex: none;
  width: calc(38 * var(--dp));
  height: calc(38 * var(--dp));
  border-radius: 50%;
  background-color: var(--cal-fill);
  color: var(--cal-digit);
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

.cal-day:is(.cal-day--today, .cal-day--paid) .cal-day__circle { font-weight: 700; }

.cal-day--today .cal-day__circle { box-shadow: inset 0 0 0 calc(2 * var(--dp)) var(--app-ink); }

.cal-day--beat .cal-day__circle {
  background-color: var(--app-ink);
  color: var(--app-on-ink);
}

.cal-day__dots {
  display: flex;
  align-items: center;
  gap: calc(2 * var(--dp));
  height: calc(8 * var(--dp));
  margin-top: calc(4 * var(--dp));
}

.cal-day__dot {
  width: calc(5 * var(--dp));
  height: calc(5 * var(--dp));
  border-radius: 50%;
}

.cal-day__dot--past { background: var(--app-muted); }
.cal-day__dot--upcoming { background: var(--app-green); }

.cal-heaviest {
  display: grid;
  grid-template-rows: 0fr;
  margin-inline: calc(16 * var(--dp));
  opacity: 0;
}

.cal-heaviest__clip {
  min-height: 0;
  overflow: hidden;
}

.cal-heaviest__row {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--dp));
  margin-top: calc(12 * var(--dp));
  padding: calc(12 * var(--dp));
  border-radius: calc(24 * var(--dp));
  background: var(--app-surface);
}

.cal-heaviest__label {
  flex: 1;
  min-width: 0;
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.cal-heaviest__value {
  white-space: nowrap;
  font: 600 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

.cal-list { padding-top: calc(12 * var(--dp)); }

.cal-group { padding-top: calc(8 * var(--dp)); }

.cal-group:not(.cal-group--beat) { display: none; }

/* DateHeader.kt:44-80: a 3 × 16dp pill, ink from today on and outline before it, 6dp from the text. */
.cal-group__head {
  display: flex;
  align-items: center;
  gap: calc(6 * var(--dp));
  padding: calc(8 * var(--dp)) calc(16 * var(--dp));
}

.cal-group__pill {
  flex: none;
  width: calc(3 * var(--dp));
  height: calc(16 * var(--dp));
  border-radius: 999px;
  background: var(--app-ink);
}

.cal-group--past .cal-group__pill { background: var(--app-outline); }

.cal-group__date {
  flex: 1;
  min-width: 0;
  font: 600 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.cal-group__total {
  white-space: nowrap;
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.cal-group__card { margin-inline: calc(16 * var(--dp)); }

.cal-row {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
  padding: calc(12 * var(--dp));
}

/* CalendarTimelineItem.kt:40,77: a past payday's row, not its card, drops to 55%. The logo keeps
   the app's 55%; each text line takes its floor from .cal. */
.cal-group--past .cal-row__logo { opacity: 0.55; }
.cal-group--past :is(.cal-row__name, .cal-row__price) { opacity: var(--cal-past-ink); }
.cal-group--past .cal-row__period { opacity: var(--cal-past-muted); }

.cal-row__logo {
  width: calc(36 * var(--dp));
  height: calc(36 * var(--dp));
  border-radius: calc(8 * var(--dp));
}

.cal-row__text {
  display: grid;
  gap: calc(2 * var(--dp));
  flex: 1;
  min-width: 0;
}

.cal-row__name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 600 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

.cal-row__period {
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.cal-row__price {
  white-space: nowrap;
  font: 700 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

/* Before the beat. From the converter step on the calendar keeps its selection, as the app does when
   you come back to it. */
:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--calendar .cal-day--beat .cal-day__circle {
  background-color: var(--cal-fill);
  color: var(--cal-digit);
}

:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--calendar .cal-heaviest {
  grid-template-rows: 1fr;
  opacity: 1;
}

:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--calendar .cal-group:not(.cal-group--beat) {
  display: block;
}

/* ---------- Currency converter screen (the renewal stage) ----------
   ConverterScreen.kt in portrait on the app background, identical on both phones: the amount rows,
   the rate card straight under them and the chart card 16dp below (ConverterLayout.kt:29-49). The
   full scene paints the status band too. The static frame is the loaded
   screen; a scene that has not played holds it at the moment before the rates and the chart arrive. */
.conv {
  /* AmountRow.kt:122-129 reserves the height of a "0" in the 56sp driver style for every row: Outfit's
     ascent and descent are 1.26em, Montserrat's 1.219em. */
  --conv-row-min: 70.56;
  /* The app's onSurfaceVariant on surfaceVariant (the passive chip) and on surfaceContainerHigh (the
     range labels) is 4.39:1 and 4.47:1 in light theme. Those two runs take the lowest share of ink
     that clears 4.5:1 (spec §7); dark theme already clears it. */
  --conv-muted-strong: color-mix(in srgb, var(--app-muted) 98%, var(--app-ink));
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--app-bg);
  color: var(--app-ink);
}

html[lang="ru"] .conv { --conv-row-min: 68.26; }

:root[data-theme="dark"] .conv:not(:is(.xp-layer[data-kind="app"], .bento__panel) .conv) { --conv-muted-strong: var(--app-muted); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .conv:not(:is(.xp-layer[data-kind="app"], .bento__panel) .conv) { --conv-muted-strong: var(--app-muted); }
}

.conv > * { flex: none; }

.conv::before {
  content: "";
  flex: 0 0 var(--device-band, 0px);
}

/* SubyTopBar, as on the calendar: 64dp, a 44dp back slot 16dp in, the title 12dp after it. */
.conv__bar {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
  height: calc(64 * var(--dp));
  padding-inline: calc(16 * var(--dp));
}

.conv__back {
  display: grid;
  place-items: center;
  width: calc(44 * var(--dp));
  height: calc(44 * var(--dp));
}

.conv__back svg {
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

.conv__title { font: 700 calc(20 * var(--dp))/calc(28 * var(--dp)) var(--app-font); }

.conv__body { padding: calc(16 * var(--dp)); }

.conv-row {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
  padding-block: calc(8 * var(--dp));
}

/* CurrencyPickerChip over SubyChip: the driver's is primary on onPrimary, the other surfaceVariant. */
.conv-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: calc(6 * var(--dp));
  box-sizing: border-box;
  min-height: calc(48 * var(--dp));
  padding: calc(8 * var(--dp)) calc(12 * var(--dp));
  border-radius: calc(20 * var(--dp));
  background: var(--app-surface-variant);
  color: var(--conv-muted-strong);
}

.conv-chip--driver {
  background: var(--app-ink);
  color: var(--app-on-ink);
}

.conv-chip__code { font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font); }

.conv-chip__chevron svg {
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

.app-flag {
  flex: none;
  width: calc(20 * var(--dp));
  height: calc(15 * var(--dp));
  border-radius: calc(4 * var(--dp));
  overflow: hidden;
}

.app-flag svg {
  width: 100%;
  height: 100%;
}

.conv-row__amount {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: calc(var(--conv-row-min) * var(--dp));
}

.conv-amount { white-space: nowrap; }

/* The driver is a text field in the locale's brand font; the passive value is SubyAmountText, which
   the .amount primitive already sets in Outfit (AmountRow.kt:146,204,226-229). */
.conv-amount--driver {
  font: 800 calc(56 * var(--dp))/calc(56 * var(--dp)) var(--app-font);
  font-variant-numeric: tabular-nums;
  letter-spacing: calc(0.2 * var(--dp));
}

.conv-amount--result {
  font-size: calc(38 * var(--dp));
  font-weight: 700;
  line-height: calc(56 * var(--dp));
  color: var(--app-muted);
}

/* AmountSkeleton: 0.7 of the 38sp font high and 120dp asked for, but its box is the width of an empty
   field's "0", which the app floors at 48dp, so the bar is 48dp (AmountRow.kt:161-194,280-316). */
.conv-shimmer {
  position: absolute;
  top: 50%;
  right: 0;
  width: calc(48 * var(--dp));
  height: calc(26.6 * var(--dp));
  margin-top: calc(-13.3 * var(--dp));
  border-radius: calc(8 * var(--dp));
  background:
    linear-gradient(90deg, var(--app-surface-high), var(--app-surface-highest), var(--app-surface-high))
    100% 0 / 300% 100% no-repeat;
  opacity: 0;
}

/* The hairline with the swap button on it: a 48dp pill in the background colour masks the line under
   a 48dp surfaceContainerHigh circle, whose arrows sit 9dp either side of centre (SwapButton.kt:47-92). */
.conv-swap {
  position: relative;
  display: grid;
  place-items: center;
  height: calc(48 * var(--dp));
}

.conv-swap::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: calc(23.5 * var(--dp));
  height: calc(1 * var(--dp));
  background: var(--app-outline-variant);
}

.conv-swap__pill,
.conv-swap__button {
  position: relative;
  display: grid;
  place-items: center;
  width: calc(48 * var(--dp));
  height: calc(48 * var(--dp));
  border-radius: 50%;
}

.conv-swap__pill { background: var(--app-bg); }
.conv-swap__button { background: var(--app-surface-high); }

.conv-swap__arrow {
  position: absolute;
  top: calc(15 * var(--dp));
  left: calc(15 * var(--dp));
  width: calc(18 * var(--dp));
  height: calc(18 * var(--dp));
}

.conv-swap__arrow--up { translate: calc(-9 * var(--dp)) 0; }
.conv-swap__arrow--down { translate: calc(9 * var(--dp)) 0; }

.conv-swap__arrow svg {
  width: 100%;
  height: 100%;
}

/* RateFooter: surface, 24dp corners and no border, 16dp/8dp padding, rows 4dp apart. */
.conv-rate {
  display: flex;
  flex-direction: column;
  gap: calc(4 * var(--dp));
  padding: calc(8 * var(--dp)) calc(16 * var(--dp));
  border-radius: calc(24 * var(--dp));
  background: var(--app-surface);
}

.conv-rate__summary {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
}

.conv-rate__line {
  flex: 1;
  min-width: 0;
  font: 700 calc(20 * var(--dp))/calc(28 * var(--dp)) var(--app-font);
  font-variant-numeric: tabular-nums;
  letter-spacing: calc(0.2 * var(--dp));
}

.conv-rate__share {
  flex: none;
  display: grid;
  place-items: center;
  width: calc(48 * var(--dp));
  height: calc(48 * var(--dp));
  border-radius: 50%;
  background: var(--app-surface-high);
}

.conv-rate__share svg {
  width: calc(20 * var(--dp));
  height: calc(20 * var(--dp));
}

.conv-rate__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  row-gap: calc(4 * var(--dp));
}

.conv-rate__age {
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.conv-rate__live {
  display: inline-flex;
  align-items: center;
  gap: calc(8 * var(--dp));
  box-sizing: border-box;
  min-height: calc(32 * var(--dp));
  padding: calc(4 * var(--dp)) calc(12 * var(--dp));
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: calc(20 * var(--dp));
  font: 700 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  white-space: nowrap;
}

.conv-rate__live svg {
  flex: none;
  width: calc(18 * var(--dp));
  height: calc(18 * var(--dp));
}

/* ChartCard: surfaceContainerLow, a 1dp outlineVariant border, 24dp corners, 16dp padding. */
.conv-chart {
  margin-top: calc(16 * var(--dp));
  padding: calc(16 * var(--dp));
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: calc(24 * var(--dp));
  background: var(--app-surface-low);
}

.conv-chart__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.conv-chart__pair {
  display: flex;
  align-items: center;
  font: 800 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

.conv-chart__arrow {
  display: grid;
  padding-inline: calc(8 * var(--dp));
  color: color-mix(in srgb, var(--app-muted) 55%, transparent);
}

.conv-chart__arrow svg,
.conv-chart__delta svg {
  width: calc(14 * var(--dp));
  height: calc(14 * var(--dp));
}

.conv-chart__delta {
  display: flex;
  align-items: center;
  gap: calc(4 * var(--dp));
  font: 700 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

.conv-chart__delta--up { color: var(--app-green); }
.conv-chart__delta--down { color: var(--app-red); }
.conv-chart__delta--flat { color: var(--app-muted); }

.conv-chart__plot {
  display: flex;
  flex-direction: column;
  gap: calc(4 * var(--dp));
  height: calc(120 * var(--dp));
  margin-top: calc(8 * var(--dp));
}

/* The plot keeps the chart's 6dp side padding; the line and fill are drawn inside it, the dashed
   baseline across the whole width (ChartLayout.kt:88-90, ChartPlot.kt:65-78). */
.conv-spark {
  position: relative;
  flex: none;
  height: calc(102 * var(--dp));
}

.conv-spark__baseline {
  position: absolute;
  inset-inline: 0;
  top: calc((var(--y) - 0.5) * var(--dp));
  height: calc(1 * var(--dp));
  background: repeating-linear-gradient(
    90deg,
    color-mix(in srgb, var(--app-ink) 18%, transparent) 0 calc(4 * var(--dp)),
    transparent calc(4 * var(--dp)) calc(8 * var(--dp))
  );
}

.conv-spark__reveal {
  position: absolute;
  inset: 0 calc(6 * var(--dp));
  clip-path: inset(0 0 0 0);
}

.conv-spark__fill {
  position: absolute;
  inset: 0;
  background: linear-gradient(color-mix(in srgb, var(--app-ink) 30%, transparent), transparent);
}

.conv-spark__line svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.conv-spark__line polyline { stroke-width: calc(2 * var(--dp)); }

.conv-spark__dot {
  position: absolute;
  left: calc(6 * var(--dp) + var(--x) * (100% - 12 * var(--dp)));
  top: calc(var(--y) * var(--dp));
  width: calc(6 * var(--dp));
  height: calc(6 * var(--dp));
  translate: -50% -50%;
  border-radius: 50%;
}

.conv-spark__dot--high { background: var(--app-green); }
.conv-spark__dot--low { background: var(--app-red); }

.conv-spark__dot--end {
  width: calc(8 * var(--dp));
  height: calc(8 * var(--dp));
  background: var(--app-ink);
}

/* ChartEndpointChip: labelSmall bold on surfaceContainerHighest, 8dp/2dp in a pill. Its right edge sits
   6dp left of the last point's centre. */
.conv-spark__chip {
  position: absolute;
  left: calc(6 * var(--dp) + var(--x) * (100% - 12 * var(--dp)));
  top: calc(var(--top) * var(--dp));
  translate: calc(-100% - 6 * var(--dp)) 0;
  padding: calc(2 * var(--dp)) calc(8 * var(--dp));
  border-radius: 999px;
  background: var(--app-surface-highest);
  color: var(--app-ink);
  font: 700 calc(10 * var(--dp))/calc(14 * var(--dp)) var(--app-font);
  letter-spacing: calc(1.5 * var(--dp));
  white-space: nowrap;
}

.conv-chart__dates {
  display: flex;
  justify-content: space-between;
}

.conv-chart__date,
.conv-stat__label {
  font: 700 calc(10 * var(--dp))/calc(14 * var(--dp)) var(--app-font);
  letter-spacing: calc(1.5 * var(--dp));
  color: var(--app-muted);
}

.conv-chart__hl {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
  margin-top: calc(8 * var(--dp));
}

.conv-stat {
  display: flex;
  align-items: baseline;
  gap: calc(4 * var(--dp));
}

.conv-stat svg {
  align-self: center;
  width: calc(12 * var(--dp));
  height: calc(12 * var(--dp));
}

.conv-stat--high svg { color: color-mix(in srgb, var(--app-green) 80%, transparent); }
.conv-stat--low svg { color: color-mix(in srgb, var(--app-red) 80%, transparent); }

.conv-stat__value {
  font: 700 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  font-variant-numeric: tabular-nums;
  letter-spacing: calc(0.2 * var(--dp));
}

/* PillSegmentedControl through RangeTabsRow: a surfaceContainerHigh track, the primary thumb 2dp in,
   equal segments with 12dp/8dp padding, and a 12dp lock before a Premium range. */
.conv-tabs {
  display: flex;
  margin-top: calc(8 * var(--dp));
  padding: calc(2 * var(--dp));
  border-radius: 999px;
  background: var(--app-surface-high);
}

.conv-tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(4 * var(--dp));
  padding: calc(8 * var(--dp)) calc(12 * var(--dp));
  border-radius: 999px;
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--conv-muted-strong);
}

.conv-tab--selected {
  background: var(--app-ink);
  color: var(--app-on-ink);
}

.conv-tab svg {
  flex: none;
  width: calc(12 * var(--dp));
  height: calc(12 * var(--dp));
}

/* Before the beat: the passive row still waits on the rates and the chart has not drawn in. */
:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--converter .conv-shimmer {
  opacity: 1;
}

:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--converter :is(.conv-amount--result, .conv-spark__dot, .conv-spark__chip) {
  opacity: 0;
}

:is(.xp-layer, .renewal-step__visual)[data-play="0"] .scene--converter .conv-spark__reveal {
  clip-path: inset(0 100% 0 0);
}

/* ---------- Statement import review (features explorer, statement import page) ----------
   StatementReviewContent.kt over ReviewContent.kt, drawn in the app's dp: the count and the monthly
   total of the ticked rows, two grouped cards, and the bar that adds what is ticked. A static frame:
   nothing in it plays. The full scene paints the status band too. */
.stmt {
  /* ReviewCtaBar pads the system bar: the iPhone's 34dp home-indicator inset or a Pixel's 24dp gesture bar. */
  --stmt-nav-inset: 34;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--app-bg);
  color: var(--app-ink);
  text-align: left;
}

.device--android .stmt { --stmt-nav-inset: 24; }

.stmt > * { flex: none; }

.stmt::before {
  content: "";
  flex: 0 0 var(--device-band, 0px);
}

/* SubyTopBar with no title: a 64dp bar and a 44dp back slot 16dp in. */
.stmt__bar {
  display: flex;
  align-items: center;
  height: calc(64 * var(--dp));
  padding-inline: calc(16 * var(--dp));
}

.stmt__back {
  display: grid;
  place-items: center;
  width: calc(44 * var(--dp));
  height: calc(44 * var(--dp));
}

.stmt__back svg {
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

/* ReviewSummaryHeader: headlineMedium semibold over the bodyMedium total, 16dp around. */
.stmt-head {
  display: grid;
  gap: calc(4 * var(--dp));
  padding: calc(16 * var(--dp));
}

.stmt-head__count { font: 600 calc(24 * var(--dp))/calc(32 * var(--dp)) var(--app-font); }

.stmt-head__total {
  font: 400 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.stmt > .stmt__list {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: calc(16 * var(--dp));
  padding: calc(12 * var(--dp)) calc(16 * var(--dp));
  overflow: hidden;
}

.stmt-group {
  display: grid;
  gap: calc(8 * var(--dp));
}

.stmt-group__label {
  padding-left: calc(4 * var(--dp));
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.stmt-group__hint {
  padding-left: calc(4 * var(--dp));
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

/* SubyGroupCard: surfaceContainer, a 1dp outline, 24dp corners. */
.stmt-card {
  box-sizing: border-box;
  overflow: hidden;
  background: var(--app-surface);
  border: calc(1 * var(--dp)) solid var(--app-outline);
  border-radius: calc(24 * var(--dp));
}

.stmt-row + .stmt-row { border-top: calc(1 * var(--dp)) solid var(--app-outline-variant); }

/* ReviewRowItem: 16dp in, 14dp above and below, 12dp between the checkbox, logo, text and pencil; a
   ticked row takes selectedBackgroundColor, ink at 10%. */
.stmt-row {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
  padding: calc(14 * var(--dp)) calc(16 * var(--dp));
}

.stmt-row--ticked { background: color-mix(in srgb, var(--app-ink) 10%, transparent); }

.stmt-check {
  flex: none;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
  border: calc(1 * var(--dp)) solid var(--app-outline);
  border-radius: 50%;
}

.stmt-check--on {
  background: var(--app-ink);
  border-color: var(--app-ink);
  color: var(--app-on-ink);
}

.stmt-check svg {
  width: calc(18 * var(--dp));
  height: calc(18 * var(--dp));
}

.stmt-row__logo {
  width: calc(32 * var(--dp));
  height: calc(32 * var(--dp));
}

.stmt-row__text {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: calc(4 * var(--dp));
}

.stmt-row__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
}

.stmt-row__sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

/* The 18dp pencil in a 48dp icon button. */
.stmt-row__edit {
  flex: none;
  display: grid;
  place-items: center;
  width: calc(48 * var(--dp));
  height: calc(48 * var(--dp));
  color: var(--app-muted);
}

.stmt-row__edit svg {
  width: calc(18 * var(--dp));
  height: calc(18 * var(--dp));
}

/* ReviewCtaBar: a hairline over the surface, the ready count, then the 56dp primary button. */
.stmt-cta {
  display: grid;
  gap: calc(8 * var(--dp));
  padding: calc(16 * var(--dp)) calc(16 * var(--dp)) calc((16 + var(--stmt-nav-inset)) * var(--dp));
  background: var(--app-surface);
  border-top: calc(1 * var(--dp)) solid var(--app-outline-variant);
}

.stmt-cta__ready {
  padding-left: calc(4 * var(--dp));
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.stmt-cta__button {
  display: grid;
  place-items: center;
  min-height: calc(56 * var(--dp));
  border-radius: calc(20 * var(--dp));
  background: var(--app-ink);
  color: var(--app-on-ink);
  font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
}

/* ---------- Dashboard screen (home hero, features explorer) ----------
   DashboardScreen at 1.5.10, drawn in the app's dp. Compose draws a Surface's border inside its
   bounds, so a bordered box here takes one dp off its padding. */
.dash {
  /* MainScreenContainer.kt: the floating nav sits 12dp above the system bar, which is the iPhone's
     34dp home-indicator inset or a Pixel's 24dp gesture bar. */
  --dash-nav-inset: 34;
  --dash-accent: var(--app-ink);
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--app-bg);
  color: var(--app-ink);
  text-align: left;
}

.device--android .dash { --dash-nav-inset: 24; }

.dash > * { flex: none; }

.dash::before {
  content: "";
  flex: 0 0 var(--device-band, 0px);
}

.dash svg {
  width: calc(24 * var(--dp));
  height: calc(24 * var(--dp));
}

/* DashboardTopBar.kt: a 64dp bar, the 45dp mark padded 8dp on its end, two 48dp action slots. */
.dash-bar {
  display: flex;
  align-items: center;
  height: calc(64 * var(--dp));
  padding-inline: calc(16 * var(--dp)) calc(4 * var(--dp));
}

.dash-bar__logo {
  display: grid;
  place-items: center;
  width: calc(45 * var(--dp));
  height: calc(45 * var(--dp));
  padding-right: calc(8 * var(--dp));
  box-sizing: border-box;
}

.dash-bar__logo svg {
  width: calc(37 * var(--dp));
  height: calc(37 * var(--dp));
}

.dash-bar__title {
  flex: 1;
  font: 700 calc(20 * var(--dp))/calc(28 * var(--dp)) var(--app-font);
}

.dash-bar__action {
  position: relative;
  display: grid;
  place-items: center;
  width: calc(48 * var(--dp));
  height: calc(48 * var(--dp));
}

.dash-bar__badge {
  position: absolute;
  top: calc(8 * var(--dp));
  right: calc(8 * var(--dp));
  box-sizing: border-box;
  min-width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
  padding-inline: calc(4 * var(--dp));
  border-radius: calc(8 * var(--dp));
  background: var(--app-red);
  color: #fff;
  font: 700 calc(10 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  text-align: center;
}

.dash__body {
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--dp));
  padding: calc(16 * var(--dp));
}

.dash-summary,
.dash-runway {
  box-sizing: border-box;
  background: var(--app-surface);
  border: calc(1 * var(--dp)) solid var(--app-outline);
  border-radius: calc(24 * var(--dp));
}

.dash-summary { padding: calc(15 * var(--dp)); }

.dash-summary__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(8 * var(--dp));
}

.dash-summary__total {
  font-size: calc(36 * var(--dp));
  font-weight: 800;
  line-height: calc(44 * var(--dp));
  white-space: nowrap;
}

/* CurrencyPickerChip, compact and unselected: surfaceVariant, a 40dp minimum, 6dp between parts. */
.dash-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: calc(6 * var(--dp));
  box-sizing: border-box;
  min-height: calc(40 * var(--dp));
  margin-top: calc(8 * var(--dp));
  padding: calc(8 * var(--dp));
  border-radius: calc(20 * var(--dp));
  background: var(--app-surface-variant);
  color: var(--app-muted);
}

.dash-chip__code { font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font); }

.dash-chip__chevron svg {
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

.dash-summary__caption {
  display: block;
  margin-top: calc(4 * var(--dp));
  font: 500 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.dash-progress {
  height: calc(6 * var(--dp));
  margin-top: calc(16 * var(--dp));
  border-radius: 999px;
  background: var(--app-outline-variant);
  overflow: hidden;
}

.dash-progress__fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--app-ink);
}

.dash-summary__split {
  display: flex;
  justify-content: space-between;
  margin-top: calc(12 * var(--dp));
}

.dash-split {
  display: inline-flex;
  align-items: center;
  gap: calc(4 * var(--dp));
  font: 500 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.dash-split__amount {
  font-weight: 700;
  color: var(--app-ink);
}

.dash-summary__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(8 * var(--dp));
  margin-top: calc(12 * var(--dp));
  padding-top: calc(12 * var(--dp));
  border-top: calc(1 * var(--dp)) solid var(--app-outline-variant);
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.dash-summary__active {
  display: inline-flex;
  align-items: center;
  gap: calc(4 * var(--dp));
}

.dash-summary__dot {
  width: calc(6 * var(--dp));
  height: calc(6 * var(--dp));
  border-radius: 50%;
  background: var(--app-green);
}

.dash-summary__count {
  font-weight: 700;
  color: var(--app-ink);
}

.dash-summary__window { font-weight: 600; }

.dash-runway {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--dp));
  padding: calc(11 * var(--dp)) calc(15 * var(--dp));
}

.dash-runway__text {
  flex: 1;
  display: grid;
  gap: calc(4 * var(--dp));
}

.dash-runway__label {
  font: 700 calc(10 * var(--dp))/calc(14 * var(--dp)) var(--app-font);
  letter-spacing: calc(1.5 * var(--dp));
  color: var(--app-muted);
}

.dash-runway__count { font: 500 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font); }

.dash-runway__total { font: 700 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font); }

.dash-runway__chevron svg {
  width: calc(18 * var(--dp));
  height: calc(18 * var(--dp));
  color: var(--app-muted);
}

.dash-actions {
  display: flex;
  gap: calc(8 * var(--dp));
  margin-top: calc(8 * var(--dp));
}

.dash-action {
  flex: 1;
  display: grid;
  justify-items: center;
  gap: calc(4 * var(--dp));
  box-sizing: border-box;
  padding: calc(11 * var(--dp)) calc(7 * var(--dp));
  background: var(--app-surface-low);
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: calc(16 * var(--dp));
}

.dash-action__label {
  font: 600 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  white-space: nowrap;
}

.dash-group {
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--dp));
}

.dash-group--next { --dash-accent: var(--app-outline); }

.dash-group__head {
  display: flex;
  align-items: center;
  padding: calc(12 * var(--dp)) 0 calc(4 * var(--dp)) calc(4 * var(--dp));
  color: var(--dash-accent);
}

.dash-group__dot {
  flex: none;
  width: calc(8 * var(--dp));
  height: calc(8 * var(--dp));
  margin-right: calc(10 * var(--dp));
  border-radius: 50%;
  background: var(--dash-accent);
}

.dash-group__label {
  font: 800 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  letter-spacing: calc(1.4 * var(--dp));
  white-space: nowrap;
}

.dash-group__count {
  margin-left: calc(10 * var(--dp));
  padding: calc(2 * var(--dp)) calc(8 * var(--dp));
  border-radius: calc(8 * var(--dp));
  background: var(--app-surface-high);
  color: var(--app-muted);
  font: 700 calc(10 * var(--dp))/calc(14 * var(--dp)) var(--app-font);
  letter-spacing: calc(1.5 * var(--dp));
}

.dash-group__rule {
  flex: 1;
  height: calc(1 * var(--dp));
  margin-left: calc(12 * var(--dp));
  background: linear-gradient(90deg, color-mix(in srgb, var(--dash-accent) 35%, transparent), transparent);
}

.dash-row {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--dp));
  box-sizing: border-box;
  padding: calc(11 * var(--dp)) calc(15 * var(--dp)) calc(11 * var(--dp)) calc(7 * var(--dp));
  background: var(--app-surface);
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: calc(24 * var(--dp));
}

.dash-row__date {
  flex: none;
  display: grid;
  align-content: center;
  justify-items: center;
  min-width: calc(36 * var(--dp));
  min-height: calc(48 * var(--dp));
}

.dash-row__day { font: 700 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font); }

.dash-row__month {
  font: 600 calc(9 * var(--dp))/calc(12 * var(--dp)) var(--app-font);
  letter-spacing: calc(0.3 * var(--dp));
  color: var(--app-muted);
}

.dash-row__logo {
  width: calc(48 * var(--dp));
  height: calc(48 * var(--dp));
}

.dash-row__text {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: calc(2 * var(--dp));
}

.dash-row__name-line {
  display: flex;
  align-items: center;
  min-width: 0;
}

.dash-row__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 500 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
}

.dash-row__badge {
  flex: none;
  margin-left: calc(4 * var(--dp));
  padding: calc(1 * var(--dp)) calc(4 * var(--dp));
  border-radius: calc(4 * var(--dp));
  background: var(--app-surface-high);
  color: var(--app-muted);
  font: 700 calc(9 * var(--dp))/calc(12 * var(--dp)) var(--app-font);
  letter-spacing: calc(0.3 * var(--dp));
  white-space: nowrap;
}

.dash-row__when {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  color: var(--app-muted);
}

.dash-row__price {
  flex: none;
  font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
}

/* SubyFloatingNavBar.kt: a pill on surfaceContainerHigh, the selected tab tinted and labelled, a 24dp
   divider, then the round primary add button. */
.dash-nav {
  position: absolute;
  left: 50%;
  bottom: calc((12 + var(--dash-nav-inset)) * var(--dp));
  translate: -50% 0;
  display: flex;
  width: max-content;
  align-items: center;
  box-sizing: border-box;
  padding: calc(7 * var(--dp));
  background: var(--app-surface-high);
  border: calc(1 * var(--dp)) solid var(--app-outline);
  border-radius: 999px;
}

.dash-nav__tab {
  display: flex;
  align-items: center;
  padding: calc(12 * var(--dp)) calc(16 * var(--dp));
  border-radius: 999px;
  color: var(--app-muted);
}

.dash-nav__tab--active {
  background: color-mix(in srgb, var(--app-ink) 12%, transparent);
  color: var(--app-ink);
}

.dash-nav__label {
  margin-left: calc(8 * var(--dp));
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  white-space: nowrap;
}

.dash-nav__divider {
  width: calc(1 * var(--dp));
  height: calc(24 * var(--dp));
  margin-inline: calc(8 * var(--dp));
  background: var(--app-outline-variant);
}

.dash-nav__add {
  display: grid;
  place-items: center;
  padding: calc(12 * var(--dp));
  border-radius: 999px;
  background: var(--app-ink);
  color: var(--app-on-ink);
}

/* ---------- Section intro (home) ----------
   One header for every home section: eyebrow, heading, lead and an optional link, stacked at the
   container's left edge, so the eye finds a heading in the same place each time. */
.section-intro {
  display: grid;
  justify-items: start;
  margin-bottom: var(--space-8);
}

.section-intro .eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  text-align: start;
}

.eyebrow__pill {
  padding: 2px var(--space-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  color: var(--ink);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps-tight);
}

.section-intro h2 {
  margin: 0 0 var(--space-4);
  text-align: start;
  text-wrap: balance;
}

.section-intro__lead {
  max-width: var(--measure-narrow);
  margin: 0;
  color: var(--muted);
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}

.section-intro .inline-link { margin-top: var(--space-5); }

.shared__copy .section-intro { margin-bottom: 0; }

@media (max-width: 719px) {
  .section-intro { margin-bottom: var(--space-7); }
}

/* ---------- Renewals (#renewals) ----------
   "Nothing renews by surprise." one step at a time, text always on the left and the picture always
   where the eye last left it. From 1024px up the four steps are tall rows beside one pinned phone and
   a script marks which row is at the middle of the screen (data-active on the section, see
   RenewalStepsController.kt); the stage shows that scene and the other rows step back. Below that each
   step is its own block, text over a slice of the real app, one iPhone wide and cropped by the card's
   bottom edge, so the app's text keeps the size it has in the app. Both visuals are in the markup and
   the stylesheet shows one; without the script the stage is on its first scene and every row is at
   full strength. */
.renewals__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.renewal-steps {
  display: grid;
  gap: var(--space-96);
  margin: 0;
  padding: 0;
  list-style: none;
}

.renewal-step {
  display: grid;
  gap: var(--space-6);
}

.renewal-step__index {
  display: block;
  margin-bottom: var(--space-3);
  color: var(--muted);
  font-size: var(--text-eyebrow);
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps-wide);
}

.renewal-step h3 {
  margin: 0 0 var(--space-3);
  font-size: clamp(28px, 3vw, 40px);
  font-weight: 650;
  letter-spacing: var(--tracking-heading);
  line-height: 1.08;
  text-wrap: balance;
}

.renewal-step p {
  max-width: 34ch;
  margin: 0;
  color: var(--muted);
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}

.renewal-step__visual {
  --bento-pad: 0px;
  container-type: inline-size;
  display: flex;
  justify-content: center;
  padding-top: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: clamp(20px, 2.4vw, 28px);
  overflow: clip;
}

.renewal-stage { display: none; }

@media (min-width: 1024px) {
  .renewals__body {
    --stage-h: min(78svh, 720px);
    --step-dim: 0.45;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: var(--space-64);
    align-items: start;
  }

  /* The room under the last row keeps the stage pinned until that row has passed the middle of the
     screen, where it would otherwise be pushed up by the end of the list. */
  .renewal-steps {
    display: block;
    padding-bottom: var(--space-8);
  }

  /* A row is never shorter than the stage: the last row's middle is the middle of the screen, and the
     stage can only stay pinned while the list under it is as tall as the stage. */
  .renewal-step {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: max(clamp(520px, 72svh, 760px), var(--stage-h));
  }

  .renewal-step__visual { display: none; }

  .renewal-stage {
    --xp-phone-h: calc(var(--stage-h) - var(--space-5) - 2px);
    position: sticky;
    top: max(calc(var(--header-shell-height) + var(--space-2)), calc(50svh - var(--stage-h) / 2));
    display: grid;
    justify-items: center;
    row-gap: var(--space-5);
  }

  .renewal-stage__glow {
    position: absolute;
    inset: 0 -8% calc(var(--space-5) + 2px);
    z-index: 0;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--ink) 6%, transparent), transparent);
    pointer-events: none;
  }

  .renewal-stage__phone {
    position: relative;
    z-index: 1;
  }

  /* The explorer's phone is sized for its own stage, and below 1080px it switches to a width; here
     the height is always the stage's. */
  .renewal-stage .xp-phone {
    grid-area: auto;
    height: var(--xp-phone-h);
    width: auto;
  }

  /* The layers are the explorer's, but nothing here drives poses: a layer shows when the section's
     step is its own, and the first one shows when no step is marked. */
  .renewal-stage .xp-layer {
    z-index: 0;
    visibility: hidden;
    opacity: 0;
    translate: 0 12px;
    scale: 0.985;
  }

  .renewals:not([data-active]) .renewal-stage .xp-layer:nth-child(1),
  .renewals[data-active="0"] .renewal-stage .xp-layer:nth-child(1),
  .renewals[data-active="1"] .renewal-stage .xp-layer:nth-child(2),
  .renewals[data-active="2"] .renewal-stage .xp-layer:nth-child(3),
  .renewals[data-active="3"] .renewal-stage .xp-layer:nth-child(4) {
    z-index: 1;
    visibility: visible;
    opacity: 1;
    translate: 0 0;
    scale: 1;
  }

  .renewal-stage__progress {
    position: relative;
    z-index: 1;
    display: flex;
    width: 176px;
    gap: var(--space-2);
  }

  .renewal-stage__bar {
    flex: 1 1 0;
    height: 2px;
    border-radius: var(--radius-pill);
    background: var(--border);
  }

  .renewals:not([data-active]) .renewal-stage__bar:nth-child(1),
  .renewals[data-active="0"] .renewal-stage__bar:nth-child(1),
  .renewals[data-active="1"] .renewal-stage__bar:nth-child(2),
  .renewals[data-active="2"] .renewal-stage__bar:nth-child(3),
  .renewals[data-active="3"] .renewal-stage__bar:nth-child(4) {
    flex-grow: 2;
    background: var(--ink);
  }

  .renewals[data-active] .renewal-step { opacity: var(--step-dim); }

  .renewals[data-active="0"] .renewal-step:nth-child(1),
  .renewals[data-active="1"] .renewal-step:nth-child(2),
  .renewals[data-active="2"] .renewal-step:nth-child(3),
  .renewals[data-active="3"] .renewal-step:nth-child(4) { opacity: 1; }
}

.renewals__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-6);
  margin-top: var(--space-6);
}

.renewals__note { margin: 0; padding-inline: 0; }

.renewals__foot .inline-link { margin-top: 0; }

/* ---------- Panels (#renewals on a narrow screen, #expenses) ----------
   A panel is one iPhone wide, rising from the bottom edge of the card that holds it and cropped by
   it. It is sized from that card (--bento-w), and every length in the scenes is a share of it (--dp). */
.bento__card {
  --bento-pad: var(--space-7);
  --bento-radius: clamp(20px, 2.4vw, 28px);
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  padding: var(--bento-pad) var(--bento-pad) 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--bento-radius);
  overflow: clip;
}

.bento__panel {
  --bento-w: clamp(346px, 100cqi, 460px);
  --dp: calc(var(--bento-w) / 393);
  position: relative;
  flex: none;
  width: var(--bento-w);
  height: calc(400 * var(--dp));
  margin-bottom: -8px;
  overflow: hidden;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  box-shadow: 0 0 0 1px var(--hairline);
}

.bento__panel > .scene {
  position: relative;
  inset: auto;
  height: 100%;
}

/* The calendar and converter panels are taller: what each step claims has to sit above the fade. The
   calendar opens on the month bar and the grid, with the heavier days shaded and the selected day
   inked, so the summary cards the app puts above them are left out. The converter's title is the
   step's, so its app bar is left out and the swap divider is tightened, which brings the chart's
   line above the fade. */
.bento__panel--calendar,
.bento__panel--multi-currency { height: calc(560 * var(--dp)); }

.bento__panel--calendar > .scene,
.bento__panel--multi-currency > .scene {
  -webkit-mask-image: linear-gradient(#000 86%, transparent);
  mask-image: linear-gradient(#000 86%, transparent);
}

.bento__panel--calendar :is(.cal-week, .cal-summary),
.bento__panel--multi-currency .conv__bar { display: none; }

.bento__panel--multi-currency .conv-swap { height: calc(32 * var(--dp)); }

.bento__panel--multi-currency .conv-swap::before { top: calc(15.5 * var(--dp)); }

/* In the light theme the app's grey panels sit on a card of nearly the same grey, so they take the
   page's border and its soft lift. The dark theme keeps its hairline alone. */
.bento__panel--calendar,
.bento__panel--multi-currency,
.expenses__panel { box-shadow: 0 0 0 1px var(--border), var(--lift-1); }

:root[data-theme="dark"] :is(.bento__panel--calendar, .bento__panel--multi-currency, .expenses__panel) {
  box-shadow: 0 0 0 1px var(--hairline);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) :is(.bento__panel--calendar, .bento__panel--multi-currency, .expenses__panel) {
    box-shadow: 0 0 0 1px var(--hairline);
  }
}

/* The lock screen's head and the reminder under it. */
.bento__panel--reminders .lock__head { padding-top: calc(48 * var(--dp)); }

.bento__panel--reminders .lock__stack {
  position: static;
  margin: calc(28 * var(--dp)) calc(16 * var(--dp)) 0;
}

/* The home screen's top: the small widget beside the icons, the medium one under them. The search
   pill and the dock belong to the bottom of a phone, which the card has cropped away. */
.bento__panel--widgets .home--ios .home__grid { padding-top: calc(24 * var(--dp)); }

.bento__panel--widgets :is(.home__search, .home__dock) { display: none; }

@media (max-width: 719px) {
  .bento__card { --bento-pad: var(--space-5); }

  .bento__panel { --bento-w: clamp(346px, 100cqi + 2 * var(--bento-pad), 460px); }
}

/* Narrower than a phone, the app's smallest captions would fall under 10px. They keep that floor and
   every other length stays a share of the panel. */
.bento__panel .widget-cal__weekday { font-size: max(10px, calc(8 * var(--dp))); }

.bento__panel :is(.widget-cal__day, .dash-row__month, .dash-row__badge) {
  font-size: max(10px, calc(9 * var(--dp)));
}

.bento__panel :is(
  .widget__label, .conv-spark__chip, .conv-chart__date, .conv-stat__label, .dash-group__count
) { font-size: max(10px, calc(10 * var(--dp))); }

.bento__panel :is(.widget-np__date, .widget-cal__total) { font-size: max(10px, calc(11 * var(--dp))); }

/* ---------- Expenses (#expenses, behind MarketingRelease.EXPENSES_LIVE) ----------
   One card, two panels in the app's own palette: the Log an expense sheet at the moment of Save, and
   the Dashboard under it with the saved toast. Sizes are the app's dp values (ExpenseSheetContent.kt,
   ExpenseMetaLine.kt, ExpensesRowCard.kt, SubyFilterChip, SubyChip, SubyPrimaryButton), one dp to the
   --dp of the panel. The sheet's rows are a little tighter than the app's, so that Save stays whole
   above the card's crop. */
.expenses__card { padding-bottom: 0; }

.expenses__visual {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-end;
  gap: var(--space-6);
}

.expenses__panel {
  --bento-w: clamp(346px, calc((100cqi - var(--space-6)) / 2), 460px);
  height: calc(572 * var(--dp));
}

.expenses__note { margin-top: var(--space-4); padding-inline: 0; }

@media (max-width: 899px) {
  .expenses__panel { --bento-w: clamp(346px, 100cqi, 460px); }
}

@media (max-width: 719px) {
  .expenses__visual { gap: var(--space-5); margin-inline: calc(-1 * var(--bento-pad)); }

  .expenses__panel { --bento-w: clamp(346px, 100cqi + 2 * var(--bento-pad), 460px); }
}

.xs {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--app-surface);
}

.xs__grab {
  flex: none;
  width: calc(32 * var(--dp));
  height: calc(4 * var(--dp));
  margin: calc(10 * var(--dp)) auto calc(4 * var(--dp));
  border-radius: calc(2 * var(--dp));
  background: var(--app-outline-variant);
}

.xs__bar {
  display: flex;
  flex: none;
  align-items: center;
  gap: calc(12 * var(--dp));
  min-height: calc(44 * var(--dp));
  padding: calc(2 * var(--dp)) calc(16 * var(--dp)) 0;
}

.xs__close {
  display: grid;
  place-items: center;
  width: calc(32 * var(--dp));
  height: calc(32 * var(--dp));
  border-radius: 50%;
  background: var(--app-surface-highest);
}

.xs__close svg {
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

.xs__title { font: 600 calc(20 * var(--dp))/calc(28 * var(--dp)) var(--app-font); }

.xs__amount-row {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  gap: calc(12 * var(--dp));
  padding: calc(8 * var(--dp)) calc(16 * var(--dp)) calc(6 * var(--dp));
}

.xs__amount {
  display: flex;
  align-items: center;
  min-height: calc(56 * var(--dp));
  font: 800 calc(52 * var(--dp))/1.08 "Outfit", "Outfit Cyrillic", system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.035em;
  white-space: nowrap;
}

.xs__caret {
  width: calc(3 * var(--dp));
  height: calc(40 * var(--dp));
  margin-left: calc(4 * var(--dp));
  border-radius: calc(2 * var(--dp));
  background: var(--app-ink);
}

/* The currency chip, the category chip and the day token. The meta line is one row: the category at
   the start, the day at the end (ExpenseMetaLine.kt). */
.xs__chip {
  display: inline-flex;
  flex: none;
  align-items: center;
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: 999px;
  white-space: nowrap;
}

.xs__chip svg {
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

.xs__chip--currency {
  height: calc(40 * var(--dp));
  padding: 0 calc(12 * var(--dp));
  background: var(--app-surface);
  font: 500 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

.xs__meta {
  display: flex;
  flex: none;
  align-items: center;
  gap: calc(8 * var(--dp));
  height: calc(44 * var(--dp));
  padding: 0 calc(16 * var(--dp));
}

.xs__chip--category {
  gap: calc(8 * var(--dp));
  height: calc(40 * var(--dp));
  margin-right: auto;
  padding: 0 calc(16 * var(--dp)) 0 calc(12 * var(--dp));
  background: var(--app-surface-variant);
  font: 700 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

.xs__chip--category svg {
  width: calc(18 * var(--dp));
  height: calc(18 * var(--dp));
}

.xs__chip--when {
  gap: calc(8 * var(--dp));
  height: calc(32 * var(--dp));
  padding: 0 calc(12 * var(--dp));
  background: var(--app-surface);
  font: 600 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

.xs__chip--when svg { color: var(--app-muted); }

/* The slot under the category chip: where the app says why the category is the one it is. */
.xs__slot {
  display: flex;
  flex: none;
  align-items: center;
  height: calc(24 * var(--dp));
  padding: 0 calc(16 * var(--dp));
  color: var(--app-muted);
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

.xs__note {
  display: flex;
  flex: none;
  align-items: center;
  height: calc(56 * var(--dp));
  margin: 0 calc(16 * var(--dp));
  padding: 0 calc(16 * var(--dp));
  border-radius: calc(28 * var(--dp));
  background: color-mix(in srgb, var(--app-surface-variant) 40%, var(--app-surface));
  font: 400 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
  white-space: nowrap;
}

.xs__keypad {
  display: grid;
  flex: none;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: calc(8 * var(--dp));
  padding: calc(4 * var(--dp)) calc(16 * var(--dp)) 0;
}

.xs__key {
  display: grid;
  place-items: center;
  height: calc(52 * var(--dp));
  border-radius: calc(16 * var(--dp));
  background: var(--app-surface-high);
  font: 700 calc(20 * var(--dp))/calc(28 * var(--dp)) var(--app-font);
}

.xs__key svg {
  width: calc(22 * var(--dp));
  height: calc(22 * var(--dp));
}

.xs__save {
  display: grid;
  flex: none;
  place-items: center;
  height: calc(56 * var(--dp));
  margin: calc(8 * var(--dp)) calc(16 * var(--dp)) 0;
  border-radius: calc(20 * var(--dp));
  background: var(--app-ink);
  color: var(--app-on-ink);
  font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font);
}

/* The surface is the container its own dp is measured against, so it carries no length of its own: the
   padding and the gap live on the screen inside it. */
.xd {
  height: 100%;
  background: var(--app-bg);
}

.xd__screen {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--dp));
  height: 100%;
  padding: calc(16 * var(--dp)) calc(16 * var(--dp)) 0;
  overflow: hidden;
}

.xd__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(12 * var(--dp)) calc(4 * var(--dp)) calc(4 * var(--dp));
}

.xd__title { font: 600 calc(16 * var(--dp))/calc(24 * var(--dp)) var(--app-font); }

.xd__trailing {
  color: var(--app-muted);
  font: 500 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

/* The Expenses tile (ExpensesRowCard.kt) follows the timeline, 24dp below its last row. A bordered
   box takes one dp off its padding, as Compose draws the border inside the bounds. */
.xt {
  display: flex;
  flex-direction: column;
  gap: calc(12 * var(--dp));
  margin-top: calc(24 * var(--dp));
  padding: calc(15 * var(--dp));
  background: var(--app-surface);
  border: calc(1 * var(--dp)) solid var(--app-outline-variant);
  border-radius: calc(24 * var(--dp));
}

.xt__title {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--dp));
  color: var(--app-muted);
  font: 600 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  white-space: nowrap;
}

.xt__title svg {
  flex: none;
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

/* The month before the save and after it sit in one grid cell, so the tile never changes size when one
   replaces the other. */
.xt__states { display: grid; }

.xt__state {
  display: flex;
  flex-direction: column;
  grid-area: 1 / 1;
  gap: calc(12 * var(--dp));
  min-width: 0;
}

.xt__state--before { opacity: 0; }

.xt__total {
  display: flex;
  align-items: flex-end;
  gap: calc(8 * var(--dp));
}

.xt__amount { font: 700 calc(24 * var(--dp))/calc(32 * var(--dp)) "Outfit", "Outfit Cyrillic", system-ui, sans-serif; }

.xt__count {
  min-width: 0;
  padding-bottom: calc(4 * var(--dp));
  overflow: hidden;
  color: var(--app-muted);
  font: 500 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.xt__split {
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--dp));
}

/* ExpenseShareBar.kt: ink, stepping down through five alphas, a segment as wide as its share. */
.xt__bar {
  display: flex;
  gap: calc(2 * var(--dp));
  height: calc(10 * var(--dp));
  overflow: hidden;
  border-radius: 999px;
}

.xt__seg {
  min-width: 0;
  background: var(--app-ink);
}

.xt__seg:nth-child(2) { opacity: 0.6; }

.xt__seg:nth-child(3) { opacity: 0.38; }

.xt__seg:nth-child(4) { opacity: 0.24; }

.xt__seg:nth-child(5) { opacity: 0.15; }

.xt__caption {
  overflow: hidden;
  color: var(--app-muted);
  font: 400 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.xt__foot {
  display: flex;
  justify-content: flex-end;
}

.xt__log {
  display: inline-flex;
  align-items: center;
  gap: calc(6 * var(--dp));
  height: calc(40 * var(--dp));
  padding: 0 calc(12 * var(--dp));
  border-radius: 999px;
  background: var(--app-surface-variant);
  font: 500 calc(12 * var(--dp))/calc(16 * var(--dp)) var(--app-font);
}

.xt__log svg {
  width: calc(16 * var(--dp));
  height: calc(16 * var(--dp));
}

.xd__toast {
  position: absolute;
  inset-inline: calc(16 * var(--dp));
  bottom: calc(16 * var(--dp));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(8 * var(--dp));
  min-height: calc(48 * var(--dp));
  padding: calc(6 * var(--dp)) calc(6 * var(--dp)) calc(6 * var(--dp)) calc(16 * var(--dp));
  border-radius: calc(16 * var(--dp));
  background: var(--app-ink);
  color: var(--app-on-ink);
  font: 400 calc(14 * var(--dp))/calc(20 * var(--dp)) var(--app-font);
}

.xd__toast-action {
  padding: calc(9 * var(--dp)) calc(12 * var(--dp));
  font-weight: 700;
}

.home-section-head {
  max-width: 820px;
  margin: 0 auto var(--stack-gap);
}

.home-section-head h2 {
  margin-bottom: 0;
  text-wrap: balance;
}

.pricing-page {
  padding-block: clamp(32px, 5vw, 64px) var(--section-y);
}

.pricing-page h1 {
  max-width: 18ch;
  margin: 0 auto var(--space-4);
  font-size: clamp(36px, 5.2vw, 64px);
  line-height: 1;
  letter-spacing: var(--tracking-heading);
  text-align: center;
  text-wrap: balance;
}

html[lang="ru"] .pricing-page h1 { font-size: clamp(32px, 4.4vw, 54px); }

.pricing-page .lead {
  max-width: 58ch;
  margin: 0 auto var(--space-7);
  color: var(--muted);
  font-size: var(--text-lg);
  text-align: center;
}

.pricing-page .pricing-grid { max-width: 900px; }

.pricing__fine {
  margin: var(--space-6) 0 var(--space-5);
  color: var(--muted);
  font-size: var(--text-sm);
  text-align: center;
}

.more__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
}

.more-tile {
  display: grid;
  align-content: start;
  gap: var(--space-2);
  padding: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}

.more-tile:hover {
  opacity: 1;
  border-color: color-mix(in srgb, var(--ink) 45%, var(--border));
}

.more-tile:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.more-tile__icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-bottom: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-icon);
  background: var(--bg);
}

.more-tile__icon svg {
  width: 22px;
  height: 22px;
}

.more-tile__title {
  font-size: var(--text-title);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-snug);
}

.more-tile__line {
  color: var(--muted);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

.more__cta {
  display: flex;
  justify-content: flex-start;
  margin-top: var(--space-7);
}

@media (max-width: 900px) {
  .more__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 600px) {
  .more__grid {
    display: flex;
    overflow-x: auto;
    gap: var(--space-3);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-block: 4px;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .more__grid::-webkit-scrollbar { display: none; }
  .more-tile {
    flex: 0 0 74%;
    scroll-snap-align: start;
  }
}

/* ---------- Features explorer (/features) ----------
   The page is the demo: a grouped list drives one phone, tap-driven instead of scroll-driven. Every
   scene is a layer in the phone's screen. Layer poses, stacking and data-play belong to
   ExplorerController; Compose owns the selection state on other elements. Without any of it (export,
   no script) the first layer, the dashboard, is the only one showing and every scene rests complete. */
.xp,
.xp-trust {
  --xp-h2: clamp(32px, 3.6vw, 48px);
}

.xp {
  min-height: 100svh;
  padding-block: 20px clamp(32px, 4vw, 56px);
}

.xp__layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  column-gap: clamp(32px, 5vw, 72px);
  align-items: start;
}

.xp__title {
  max-width: 14ch;
  margin: 0 0 var(--space-3);
  font-size: var(--xp-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-heading);
  text-align: start;
}

.xp__lead {
  max-width: 40ch;
  margin: 0 0 var(--space-5);
  color: var(--muted);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

.xp__list {
  position: relative;
  padding-left: 16px;
}

.xp__group + .xp__group { margin-top: var(--space-3); }

.xp__group-label {
  position: relative;
  margin: 0 0 2px;
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps-wide);
  line-height: 20px;
  text-transform: uppercase;
}

.xp__tab {
  display: block;
  width: 100%;
  padding: 5px 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font: var(--weight-semibold) 17px/1.25 var(--font);
  letter-spacing: var(--tracking-tight);
  text-align: start;
  cursor: pointer;
}

.xp__tab:hover,
.xp__item.is-active .xp__tab { color: var(--ink); }

.xp__tab:focus-visible,
.xp__chip:focus-visible,
.xp__switch-option:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.xp__more {
  display: grid;
  grid-template-rows: 0fr;
}

.xp__item.is-active .xp__more { grid-template-rows: 1fr; }

.xp__more-inner {
  min-height: 0;
  overflow: hidden;
}

.xp__desc {
  max-width: 42ch;
  margin: 0 0 var(--space-2);
  color: var(--muted);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

.xp__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding-bottom: var(--space-2);
}

.xp__tag {
  display: inline-flex;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 12px;
  font-weight: var(--weight-semibold);
  line-height: 18px;
}

.xp__link {
  margin-top: 0;
  font-size: 14px;
}

.xp__indicator {
  position: absolute;
  left: 0;
  top: 0;
  width: 2px;
  height: 1px;
  border-radius: 1px;
  background: var(--ink);
  transform: translateY(0) scaleY(0);
  transform-origin: top left;
}

.xp__chips { display: none; }

.xp__chips-track { position: relative; }

.xp__stage {
  --xp-phone-h: min(78svh, 720px);
  --xp-stage-h: calc(var(--xp-phone-h) + 64px);
  position: sticky;
  top: max(calc(var(--header-shell-height) + 8px), calc((100svh - var(--xp-stage-h)) / 2));
  display: grid;
  justify-items: center;
  gap: var(--space-4);
}

.xp__glow {
  position: absolute;
  inset: 4% 4% 12%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--phone-glow), var(--phone-glow-mid) 60%, transparent);
  pointer-events: none;
}

.xp__screen {
  position: relative;
  z-index: 1;
  touch-action: pan-y;
}

.xp__frames {
  display: grid;
  place-items: center;
}

.xp-phone {
  --xp-notif-origin: 50% 77%;
  --shot-bg: #f5f5f5;
  --status-fg: #111;
  --home-indicator: rgba(17, 17, 17, 0.28);
  grid-area: 1 / 1;
  height: var(--xp-phone-h);
  width: auto;
}

.xp-phone[data-kind="os"] {
  --status-fg: #fff;
  --home-indicator: rgba(255, 255, 255, 0.72);
}

.xp-phone[data-layer="reminders"]:not(.device--android) .device__time { visibility: hidden; }

.xp__screen--ios .xp-phone--android,
.xp__screen--android .xp-phone--ios {
  opacity: 0;
  visibility: hidden;
}

.xp__under {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  min-height: 40px;
}

.xp__note {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm);
}

.xp__switch {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}

.xp__switch-option {
  min-height: 32px;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  font: var(--weight-semibold) 14px/1 var(--font);
  cursor: pointer;
}

.xp__switch-option[aria-pressed="true"] {
  background: var(--ink);
  color: var(--on-ink);
}

.xp__detail { display: none; }

.xp-stack {
  position: absolute;
  inset: 0;
  z-index: 0;
  isolation: isolate;
  overflow: hidden;
  border-radius: inherit;
}

.xp-layer {
  --xp-dur: 500ms;
  --xp-ease: var(--ease-out-expo);
  --xp-dim: 0;
  --xp-vis-delay: 0s;
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--shot-bg);
  transform-origin: var(--xp-origin, 50% 50%);
  translate: 0 0;
  scale: 1;
  opacity: 1;
  border-radius: 0;
  visibility: visible;
}

.xp-layer:not([data-pose], :first-child) { visibility: hidden; }

.xp-layer::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--scrim-strong);
  opacity: calc(var(--xp-dim) / 0.55);
  pointer-events: none;
}

.xp-layer--shot picture {
  position: absolute;
  inset: var(--device-band) 0 0 0;
  display: block;
}

.xp-layer--shot .device__shot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

.xp-layer[data-pose="left"]    { translate: -28% 0; --xp-dim: 0.25; --xp-vis-delay: var(--xp-dur); visibility: hidden; }
.xp-layer[data-pose="right"]   { translate: 100% 0; --xp-vis-delay: var(--xp-dur); visibility: hidden; }
.xp-layer[data-pose="launch"]  { scale: 0.2; opacity: 0; border-radius: 30cqi; --xp-vis-delay: var(--xp-dur); visibility: hidden; }
.xp-layer[data-pose="under"]   { scale: 1.06; --xp-dim: 0.35; --xp-vis-delay: var(--xp-dur); visibility: hidden; }
.xp-layer[data-pose="lifted"]  { translate: 0 -8%; opacity: 0; --xp-vis-delay: var(--xp-dur); visibility: hidden; }
.xp-layer[data-pose="covered"] { --xp-vis-delay: var(--xp-dur); visibility: hidden; }

.xp-layer.xp-snap,
.xp-layer.xp-snap::after { transition: none !important; }

.xp-layer.xp-noanim,
.xp-layer.xp-noanim * {
  transition: none !important;
  animation: none !important;
}

.xp-scan {
  position: absolute;
  inset: var(--device-band) 0 0 0;
  overflow: hidden;
  pointer-events: none;
}

.xp-scan::after {
  content: "";
  position: absolute;
  inset-inline: 6%;
  top: 0;
  height: 16%;
  opacity: 0;
  border-bottom: 2px solid color-mix(in srgb, #111 70%, transparent);
  background: linear-gradient(to bottom, transparent, color-mix(in srgb, #111 10%, transparent));
}

@media (prefers-reduced-motion: reduce) {
  .xp-layer { --xp-dur: 150ms !important; --xp-ease: linear !important; }
  .xp-layer[data-pose]:not([data-pose="rest"]) {
    translate: 0 0;
    scale: 1;
    border-radius: 0;
    opacity: 0;
    --xp-dim: 0;
  }
}

.xp-trust {
  padding-block: clamp(64px, 8vw, 112px);
}

.xp-trust .section-head {
  display: block;
  margin-bottom: var(--space-7);
}

.xp-trust .section-head h2 {
  max-width: none;
  margin-bottom: 0;
  font-size: var(--xp-h2);
  line-height: 1.04;
  letter-spacing: var(--tracking-heading);
}

.xp-trust__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-7);
}

.xp-trust__col h3 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-title);
  line-height: var(--leading-snug);
}

.xp-trust__col p {
  max-width: 32ch;
  margin: 0;
  color: var(--muted);
}

.xp-trust__link { margin-top: var(--space-7); }

@media (max-width: 1079px) {
  .xp {
    min-height: 0;
    padding-top: 12px;
  }

  .xp__layout {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0;
  }

  .xp__side { display: contents; }
  .xp__list { display: none; }
  .xp__title { max-width: none; }
  .xp__lead { margin-bottom: var(--space-4); }

  .xp__chips {
    display: block;
    position: sticky;
    top: calc(var(--header-shell-height) - 4px);
    z-index: 40;
    margin-inline: calc(var(--gutter) * -1);
    background: color-mix(in srgb, var(--bg) 86%, transparent);
    -webkit-backdrop-filter: saturate(165%) blur(18px);
    backdrop-filter: saturate(165%) blur(18px);
    border-block: 1px solid var(--hairline);
  }

  .xp__chips-track {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 10px var(--gutter);
    overflow-x: auto;
    scrollbar-width: none;
  }

  .xp__chips-track::-webkit-scrollbar { display: none; }

  .xp__chip {
    flex: none;
    padding: 8px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--ink);
    font: var(--weight-semibold) 14px/1.2 var(--font);
    white-space: nowrap;
    cursor: pointer;
  }

  .xp__chip[aria-selected="true"] {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--on-ink);
  }

  .xp__chip-div {
    flex: none;
    width: 1px;
    height: 20px;
    margin-inline: 4px;
    background: var(--rule);
  }

  .xp__stage {
    position: static;
    top: auto;
    margin-top: var(--space-6);
  }

  .xp-phone {
    height: auto;
    width: min(78vw, 340px);
  }

  .xp__detail {
    display: block;
    width: min(100%, 420px);
    text-align: start;
  }

  .xp__detail-title {
    margin: 0 0 var(--space-2);
    font-size: var(--text-h3);
    line-height: var(--leading-snug);
  }

  .xp__detail-desc {
    margin: 0 0 var(--space-3);
    color: var(--muted);
  }

  .xp__detail .inline-link { margin-top: 0; }

  .xp-trust__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-6);
  }
}

} /* end @layer suby.components */

@layer suby.motion {

/* ========================================================================
   Animations & Transitions (subtle, respect reduced-motion)
   ======================================================================== */

@media (prefers-reduced-motion: no-preference) {
  .more-tile {
    transition:
      transform 200ms var(--ease),
      border-color 200ms var(--ease);
  }
  .more-tile:hover { transform: translateY(-2px); }
  .more-tile:active { transform: scale(0.98); }
}

/* Allow smooth height interpolation on <details> open/close */
:root { interpolate-size: allow-keywords; }

/* Smoother default for interactive elements */
.btn,
.pricing-card,
.press-asset,
.screenshots img,
.faq summary {
  transition:
    transform var(--dur-slow) var(--ease),
    background var(--dur-slow) var(--ease),
    color var(--dur-slow) var(--ease),
    border-color var(--dur-slow) var(--ease),
    box-shadow var(--dur-slow) var(--ease),
    opacity var(--dur-slow) var(--ease);
}

/* FAQ marker rotates smoothly instead of swapping characters */
.faq summary::after {
  content: "+";
  display: inline-block;
  transform-origin: center;
  transition: transform var(--dur-emphasis) var(--ease);
}
.faq details[open] summary::after {
  content: "+";
  transform: rotate(45deg);
}

/* FAQ open/close: opacity-only fade (no translateY to avoid jumps) */
.faq details > *:not(summary) {
  opacity: 0;
  transition: opacity 280ms ease-out;
}
.faq details[open] > *:not(summary) {
  opacity: 1;
}

/* Card hover: lift + soft shadow instead of just translate */
.press-asset:hover {
  transform: translateY(-6px);
  box-shadow: var(--card-hover-shadow);
  border-color: var(--ink);
  opacity: 1;
}

/* No hover treatment on the pricing cards or the gallery frames: neither is a link
   or a control, and lifting something that cannot be clicked promises an interaction
   that does not exist. */

/* Screenshot hover (desktop only) */
@media (hover: hover) and (min-width: 769px) {
  .screenshots img {
    transition: transform var(--dur-emphasis) var(--ease),
                box-shadow var(--dur-emphasis) var(--ease);
  }
}

/* Button press feedback */
.btn:active:not(.btn--disabled) {
  transform: translateY(0) scale(0.96);
  transition-duration: var(--suby-duration-instant);
}

/* ---- Scroll-driven section reveals (Chrome/Edge/Firefox) ---- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes reveal-up {
      /* Floors at 0.6, not 0: a view() timeline is scrubbed, not one-shot, so a
         parked page holds whatever opacity the scroll position implies. At 0 a
         centred heading sat at 2.17:1, under the 3:1 AA floor. The 40px rise
         carries the reveal regardless. */
      from { opacity: 0.6; transform: translateY(40px); }
      to   { opacity: 1; transform: translateY(0); }
    }

    .reveal,
    .screenshots img,
    .legal h2,
    .legal__toc,
    .support-email-card,
    .support-tip,
    .press-grid,
    .press-bio {
      animation: reveal-up linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 25%;
    }

    /* Per-child animation-delay is inert under animation-timeline: view()
       (progress-based, not time-based), so no stagger rules here. */
  }
}

/* ---- Store badges: hover lift + subtle scale ---- */
.store-badge {
  display: inline-block;
  transition: transform var(--dur-slow) var(--ease),
              filter var(--dur-slow) ease;
}
@media (hover: hover) {
  .store-badge:hover {
    transform: translateY(-2px) scale(1.025);
    filter: brightness(1.04);
  }
  .store-badge:active {
    transform: translateY(0) scale(0.96);
    transition-duration: var(--suby-duration-instant);
  }
}

/* ---- Cursor spotlight (desktop only, monochrome, very subtle) ----
   Kotlin/JS updates --mx / --my as pixel offsets on body.
   The gradient is baked at the element's own center and moved via
   transform, so pointer moves composite (GPU) instead of repainting
   the full viewport. `isolation` scopes the negative z-index so the
   spotlight sits behind content without forcing a stacking context on
   every body child. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  body {
    isolation: isolate;
  }
  body::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    width: 1200px;
    height: 1200px;
    margin: -600px 0 0 -600px;
    pointer-events: none;
    z-index: -1;
    opacity: 0;
    transform: translate(var(--mx, 50vw), var(--my, 50vh));
    transition: opacity 600ms ease;
    will-change: transform;
    background: radial-gradient(
      600px circle at center,
      color-mix(in srgb, var(--ink) 5%, transparent),
      transparent 50%
    );
  }
  body[data-cursor-active="true"]::before {
    opacity: 1;
  }
}

/* ---- FAQ smooth open/close via grid trick ---- */
.faq details summary {
  list-style: none;
  cursor: pointer;
}
.faq details summary::-webkit-details-marker {
  display: none;
}

/* Modern: animate to intrinsic size when supported */
@supports (interpolate-size: allow-keywords) {
  .faq details::details-content {
    height: 0;
    overflow: hidden;
    transition: height var(--dur-emphasis) var(--ease),
                content-visibility var(--dur-emphasis) allow-discrete;
    transition-behavior: allow-discrete;
  }
  .faq details[open]::details-content {
    height: auto;
    /* First open animates from 0 instead of snapping open. */
    @starting-style {
      height: 0;
    }
  }
}

/* ---- Phone screenshot subtle parallax on scroll ---- */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 769px) {
    @keyframes hero-phone-parallax {
      from { transform: translateY(0); }
      to   { transform: translateY(-40px); }
    }
    /* Both animations have to be named here: `animation` is a shorthand, so
       declaring only the parallax in this later layer reset animation-name and
       silently cancelled the entrance fade from suby.components. */
    .hero__phone {
      animation: hero-fade 900ms ease 140ms both, hero-phone-parallax linear both;
      animation-timeline: auto, scroll(root);
      animation-range: normal, 0 80vh;
    }
  }
}

/* ---- Reduced motion: kill all entry animations ---- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}


/* ---------- Theme reveal ----------
   A View Transition wipes the real new-themed page in from the toggle. The old
   snapshot holds still while the new one is revealed by a clip-path circle
   grown from the click point (set as --reveal-x/y/r by MarketingThemeController).
   overflow-x:clip on body keeps the snapshot the size of the viewport, so the
   origin resolves correctly. */
@keyframes suby-theme-reveal {
  from { clip-path: circle(0px at var(--reveal-x, 50%) var(--reveal-y, 50%)); }
  /* Overshoot past the exact corner distance so the circle always clears the whole
     viewport from any origin (the exact radius stopped short on tall snapshots). */
  to { clip-path: circle(max(var(--reveal-r, 0px), 150vmax) at var(--reveal-x, 50%) var(--reveal-y, 50%)); }
}
@keyframes suby-theme-fade {
  from { opacity: 1; }
  to { opacity: 0; }
}
::view-transition-image-pair(root) { isolation: auto; }
::view-transition-old(root) {
  animation: suby-theme-fade var(--dur-entrance) ease forwards;
  z-index: 0;
}
::view-transition-new(root) {
  animation: suby-theme-reveal var(--dur-entrance) cubic-bezier(0.33, 0, 0.2, 1) forwards;
  z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }
}


/* ---------- Orchestrated motion ----------
   Three moments, not a scattering of effects: the grid enters in reading order, the
   set-piece device drifts against its copy, and pressable things settle rather than
   snap. Everything below is compositor-only (transform/opacity), so none of it can
   shift layout or fight scroll restoration. */

/* The catalogue is a steady belt, not a scroll progress indicator. Each of the two
   equal runs owns its trailing gap, so -50% lands on the next run at the same pixel. */
@keyframes rail-travel {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

@media (prefers-reduced-motion: no-preference) {
  .rail__track {
    animation: rail-travel 48s linear infinite;
    will-change: transform;
  }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* The set piece: the device drifts slower than the page it sits on, so the band
       gains depth as it passes. The stage moves as one unit, keeping the scan
       overlay registered to the phone. */
    @keyframes snap-drift {
      from { translate: 0 36px; }
      to   { translate: 0 -36px; }
    }

    .snap__stage {
      animation: snap-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
  }
}

/* A new notification slides up into the lock screen's stack. */
@media (prefers-reduced-motion: no-preference) {
  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--lock .notif {
    transition:
      opacity 420ms var(--app-ease-enter) 150ms,
      translate 420ms var(--app-ease-enter) 150ms;
  }
}

/* Unlocking: the home screen settles in from 1.04 while its widgets and icons fade in one after
   another, SubyMotion's 40ms StaggerStep apart. */
@media (prefers-reduced-motion: no-preference) {
  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--home .home__layer {
    transition:
      opacity 450ms var(--app-ease-move),
      scale 450ms var(--app-ease-move);
  }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--home .home__item {
    transition: opacity 220ms var(--app-ease-enter) calc(var(--i) * 40ms);
  }
}

/* Tapping the heaviest day ahead, 950ms after the screen lands: after the 450ms launch zoom. The day
   fills with ink over DayCell's 220ms colour spec while its digit snaps; the row folds away with
   SubyMotion.Section.exit's timings; once the fold is done the list narrows to that day with no
   crossfade, as the app swaps it (CalendarViewScreen.kt:326-341). */
@media (prefers-reduced-motion: no-preference) {
  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--calendar { --cal-beat: 950ms; }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--calendar .cal-day--beat .cal-day__circle {
    transition:
      background-color 220ms var(--app-ease-enter) var(--cal-beat),
      color 0s var(--cal-beat);
  }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--calendar .cal-heaviest {
    transition:
      opacity 150ms var(--app-ease-fosi) var(--cal-beat),
      grid-template-rows 220ms var(--app-ease-fosi) var(--cal-beat);
  }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--calendar .cal-group:not(.cal-group--beat) {
    transition: display 0s calc(var(--cal-beat) + 220ms) allow-discrete;
  }
}

/* Opening the converter: once the 350ms push has landed, the passive row shimmers for 400ms as the
   rates load. Then the result fades in over the standard 220ms while the shimmer leaves on the quick
   150ms exit (AmountRow.kt:179-189), and the sparkline draws in from the left over
   ChartMotion.Default's 700ms FastOutSlowIn, each dot appearing as the draw-in reaches it and the
   endpoint chip with the end dot (--reveal-at is that moment as a share of the 700ms). */
@media (prefers-reduced-motion: no-preference) {
  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--converter { --conv-beat: 750ms; }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--converter .conv-shimmer {
    transition: opacity 150ms var(--app-ease-exit) var(--conv-beat);
    animation: conv-shimmer-sweep var(--conv-beat) linear;
  }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--converter .conv-amount--result {
    transition: opacity 220ms var(--app-ease-enter) var(--conv-beat);
  }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--converter .conv-spark__reveal {
    transition: clip-path 700ms var(--app-ease-fosi) var(--conv-beat);
  }

  :is(.xp-layer, .renewal-step__visual)[data-play="1"] .scene--converter :is(.conv-spark__dot, .conv-spark__chip) {
    transition: opacity 220ms var(--app-ease-enter) calc(var(--conv-beat) + var(--reveal-at) * 700ms);
  }

  @keyframes conv-shimmer-sweep {
    from { background-position: 100% 0; }
    to { background-position: 0 0; }
  }
}

/* ---------- Features explorer motion ---------- */
@media (prefers-reduced-motion: no-preference) {
  .xp__tab { transition: color 200ms var(--ease); }

  .xp__more { transition: grid-template-rows 320ms var(--ease-out-expo); }

  .xp__indicator { transition: transform 420ms var(--ease-out-expo); }
  .xp__indicator.is-instant { transition: none; }

  .xp-phone {
    transition: opacity 220ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s;
  }

  .xp__screen--ios .xp-phone--android,
  .xp__screen--android .xp-phone--ios {
    transition: opacity 220ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s 220ms;
  }

  .xp-phone .device__status { transition: color 450ms var(--suby-ease-standard); }
  .xp-phone .device__screen::after { transition: background-color 450ms var(--suby-ease-standard); }

  .xp__switch-option {
    transition: background 200ms var(--ease), color 200ms var(--ease);
  }

  .xp-layer {
    transition:
      translate var(--xp-dur) var(--xp-ease),
      scale var(--xp-dur) var(--xp-ease),
      opacity var(--xp-dur) var(--xp-ease),
      border-radius var(--xp-dur) var(--xp-ease),
      visibility 0s var(--xp-vis-delay);
  }

  .xp-layer::after { transition: opacity var(--xp-dur) var(--xp-ease); }

  @keyframes xp-scan {
    from { transform: translateY(-100%); opacity: 0; }
    12% { opacity: 1; }
    88% { opacity: 1; }
    to { transform: translateY(525%); opacity: 0; }
  }

  .xp-layer[data-layer="import"][data-play="1"] .xp-scan::after {
    animation: xp-scan 1500ms var(--suby-ease-standard) 380ms both;
  }

  @keyframes xp-detail-in {
    from { opacity: 0; }
    to { opacity: 1; }
  }

  .xp__detail-body { animation: xp-detail-in 140ms var(--ease) both; }
}

@media (max-width: 1079px) and (prefers-reduced-motion: no-preference) {
  .xp__chip {
    transition: background 200ms var(--ease), color 200ms var(--ease), border-color 200ms var(--ease);
  }
}

/* ---------- Home entrances: one shot, in every browser ----------
   components/Reveal.kt marks a block that starts below the fold with data-reveal-pending and
   takes the mark off when the block comes into view. Only that mark hides anything, so a block
   the script never reached, a printout, an export and a visitor who asked for less motion all see
   the finished page. The block eases up once, a beat after its neighbour in the row (--i), and
   hands its scene the play mark (data-play) the scenes above are keyed on. */
@media (prefers-reduced-motion: no-preference) {
  [data-reveal][data-reveal-pending] {
    opacity: 0;
    translate: 0 16px;
  }

  /* A block the keyboard has reached is shown whatever the script has caught up with. */
  [data-reveal][data-reveal-pending]:focus-within {
    opacity: 1;
    translate: none;
  }

  [data-reveal][data-revealed] {
    animation: reveal-in 560ms var(--ease-out-expo) calc(var(--i, 0) * 70ms) backwards;
  }

  @keyframes reveal-in {
    from {
      opacity: 0;
      translate: 0 16px;
    }
  }

  /* On a wide screen the reveal belongs to the stage, not to the rows: a row's text is always there and
     only steps back (opacity below), so nothing hides or eases in with the scroll. */
  @media (min-width: 1024px) {
    .renewal-step__copy[data-reveal] {
      opacity: 1;
      translate: none;
      animation: none;
    }
  }

  /* Save: the sheet's button is pressed, the toast rises, and the tile's month goes from two expenses to
     three. Only after a reveal: a card already in view at load is the finished frame and stays it. */
  .expenses__card[data-play="0"] .xd__toast {
    opacity: 0;
    translate: 0 calc(16 * var(--dp));
  }

  .expenses__card[data-play="0"] .xt__state--before { opacity: 1; }

  .expenses__card[data-play="0"] .xt__state--after { opacity: 0; }

  .expenses__card[data-play="1"][data-revealed] .xs__save { animation: xs-press 160ms var(--app-ease-move) 300ms; }

  .expenses__card[data-play="1"][data-revealed] .xd__toast {
    transition:
      opacity 280ms var(--app-ease-enter) 500ms,
      translate 280ms var(--app-ease-enter) 500ms;
  }

  .expenses__card[data-play="1"][data-revealed] .xt__state { transition: opacity 220ms var(--app-ease-enter) 650ms; }

  @keyframes xs-press {
    50% { scale: 0.96; }
  }
}

/* ---------- Renewal steps ----------
   The scene that arrives rises 12px and settles in over 480ms on the page's expo ease. The one that
   leaves holds for that long underneath it, so the phone never shows its own screen between two
   scenes, then fades over 240ms and goes back to its starting pose for the next arrival. */
@media (prefers-reduced-motion: no-preference) and (min-width: 1024px) {
  .renewal-stage .xp-layer {
    transition:
      opacity 240ms var(--ease) 480ms,
      translate 0s 720ms,
      scale 0s 720ms,
      visibility 0s 720ms;
  }

  .renewals:not([data-active]) .renewal-stage .xp-layer:nth-child(1),
  .renewals[data-active="0"] .renewal-stage .xp-layer:nth-child(1),
  .renewals[data-active="1"] .renewal-stage .xp-layer:nth-child(2),
  .renewals[data-active="2"] .renewal-stage .xp-layer:nth-child(3),
  .renewals[data-active="3"] .renewal-stage .xp-layer:nth-child(4) {
    transition:
      opacity 480ms var(--ease-out-expo),
      translate 480ms var(--ease-out-expo),
      scale 480ms var(--ease-out-expo),
      visibility 0s;
  }

  .renewal-step { transition: opacity 400ms var(--ease); }

  .renewal-stage__bar {
    transition:
      flex-grow 300ms var(--ease-out-expo),
      background-color 300ms var(--ease);
  }
}

} /* end @layer suby.motion */

/* ------------------------------------------------------------------------
   suby.overrides — a holding pen, not a home.

   These rules used to sit outside the layer system entirely, which meant they
   beat every layered rule no matter where it lived. That is why editing a
   component inside suby.components could silently do nothing. They keep the
   last word here, but the precedence is now declared instead of implicit, and
   rules leave for their real layer as each move is shown to leave computed
   styles untouched.
   ------------------------------------------------------------------------ */
@layer suby.overrides {

/* The stake is evidence, not a second lead. Set bold and in full ink it fought
   the lead directly above it for the same job; set as a margin note it reads as
   the reason the lead matters. */
.hero__stakes {
  position: relative;
  max-width: var(--measure-narrow);
  margin: 0 0 32px;
  padding-inline-start: 18px;
  color: var(--muted);
  font-size: var(--text-base);
  font-weight: var(--weight-regular);
}

.hero__stakes::before {
  content: "";
  position: absolute;
  inset-block: 0.3em;
  inset-inline-start: 0;
  width: 2px;
  border-radius: 2px;
  background: var(--rule);
}

.social-proof {
  padding-block: clamp(48px, 6vw, 80px);
  border-block: 1px solid var(--hairline);
}

/* The band is one table: the figures and the quotes share a three-column grid, so the
   rule between the two figures lands on the same vertical as the rule between the
   first two quotes. Two separate blocks with unrelated dividers is what made it read
   as two things stacked rather than one claim. */
.social-proof__inner {
  --band-gutter: clamp(20px, 3vw, 40px);
  display: grid;
  gap: 0;
}

.social-proof__metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.proof-metric {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.proof-metric__value {
  color: var(--ink);
  line-height: 1;
  letter-spacing: var(--tracking-display);
  font-variant-numeric: tabular-nums;
}

.proof-metric__label {
  color: var(--muted);
  font-size: 13px;
  letter-spacing: 0.01em;
}

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

/* No box. Three bordered cards inside a band that is already ruled top and bottom was
   a container too many for a system whose rule is flat surfaces and hairlines; as
   columns of the same table the quotes read as evidence rather than as marketing
   furniture, and the attributions line up on one baseline. */
.review-quote {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding-block: var(--space-28) 0;
  padding-inline-end: var(--band-gutter);
}

.review-quote__stars {
  display: flex;
  gap: 4px;
  color: var(--ink);
}

.review-quote__star svg {
  width: 17px;
  height: 17px;
  display: block;
}

.review-quote__text {
  margin: 0;
  flex: 1;
  color: var(--ink);
  font-size: 16.5px;
  line-height: 1.6;
  letter-spacing: -0.005em;
}

/* Attribution pinned to the bottom so ragged quote lengths still line up. */
.review-quote__author {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: auto;
  padding-top: 18px;
  border-top: 1px solid var(--hairline);
}

.review-quote__avatar {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  color: var(--on-ink);
  font-size: 14px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.01em;
}

.review-quote__byline {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.review-quote__name {
  font-size: 14.5px;
  font-weight: var(--weight-semibold);
}

.review-quote__source {
  color: var(--muted);
  font-size: 12.5px;
}

.get-page__inner {
  display: grid;
  justify-items: center;
  gap: var(--space-28);
  text-align: center;
}

.service-directory h2 {
  margin-bottom: var(--space-7);
}

.service-directory__section + .service-directory__section {
  margin-top: var(--space-7);
}

.service-directory__heading {
  margin-bottom: var(--space-3);
  font-size: var(--text-eyebrow);
}

.service-directory__groups {
  display: grid;
  gap: var(--space-3);
}

.service-directory__group {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
  overflow: hidden;
}

.service-directory__group summary {
  padding: 18px var(--space-5);
  cursor: pointer;
  font-weight: var(--weight-semibold);
}

.service-directory__links {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr));
  gap: 10px 18px;
  padding: 0 var(--space-5) var(--space-5);
}

.service-directory__links a {
  color: var(--muted);
  font-size: var(--text-sm);
}

.service-directory__links a:hover {
  color: var(--ink);
}

.service-directory__links .service-directory__hub {
  grid-column: 1 / -1;
  color: var(--ink);
  font-weight: var(--weight-semibold);
  text-decoration: underline;
}

.screenshots picture {
  display: block;
}

.screenshots picture img {
  width: 100%;
}

@media (max-width: 900px) and (min-width: 601px) {
  .social-proof__metrics,
  .review-quotes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .review-quote:nth-child(odd) {
    padding-inline-start: 0;
    border-left: 0;
  }
  .review-quote:nth-child(n + 3) {
    margin-top: var(--band-gutter);
    padding-top: var(--band-gutter);
    border-top: 1px solid var(--rule);
  }
}

@media (max-width: 600px) {
  .review-quotes {
    grid-template-columns: minmax(0, 1fr);
  }
  .review-quote + .review-quote {
    margin-top: var(--band-gutter);
    padding-top: var(--band-gutter);
    border-top: 1px solid var(--rule);
  }
}

/* ---- Join invite ---- */
.join-card {
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
  padding: var(--space-40) var(--space-28) var(--space-7);
  box-sizing: border-box;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.join-card__logo {
  position: relative;
  width: 72px;
  height: 72px;
  margin: 0 auto var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
  overflow: hidden;
}

.join-card__logo-placeholder {
  position: absolute;
  inset: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  font-size: 30px;
  font-weight: var(--weight-extrabold);
}

.join-card__logo-img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: var(--space-3);
  box-sizing: border-box;
  background: var(--bg);
}

.join-card__logo-img[data-failed="true"] {
  display: none;
}

.join-card h1 {
  margin: 0 0 var(--space-5);
}

.join-card__amount {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 2px;
}

.join-card__amount-value {
  font-size: 44px;
  font-weight: var(--weight-extrabold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.1;
  color: var(--ink);
}

.join-card__amount-period {
  font-size: 18px;
  font-weight: var(--weight-semibold);
  color: var(--muted);
}

.join-card__amount-caption {
  display: block;
  margin-top: var(--space-1);
  font-size: 13px;
  color: var(--muted);
}

.join-card__benefits {
  list-style: none;
  margin: var(--space-6) 0 var(--space-28);
  padding: 0;
  display: grid;
  gap: 10px;
  text-align: left;
}

.join-card__benefits li {
  position: relative;
  padding-left: 26px;
  color: var(--ink);
}

.join-card__benefits li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.55em;
  width: 10px;
  height: 6px;
  border-left: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
  transform: rotate(-45deg);
}

.join-card__cta {
  display: inline-flex;
  width: 100%;
  justify-content: center;
}

.join-card__note {
  margin: var(--space-4) 0 var(--space-2);
  font-size: 14px;
}

@media (max-width: 520px) {
  .join-card {
    padding: var(--space-7) var(--space-5) 26px;
  }

  .join-card__amount-value {
    font-size: 38px;
  }
}

.join-card .store-badges {
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  max-width: 100%;
}

/* ---- Person page ---- */
.person-status,
.person-header,
.person-footer {
  width: 100%;
  max-width: 460px;
  margin-inline: auto;
  text-align: center;
}

.person-status {
  display: grid;
  justify-items: center;
  gap: var(--space-5);
  padding-top: var(--space-6);
}

.person-status__heading {
  margin: 0;
  font-size: var(--text-title);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  letter-spacing: normal;
  text-wrap: balance;
}

.person-header {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.person-header .eyebrow {
  margin: 0;
}

.person-header .store-badges {
  gap: 10px;
}

.person-header .store-badge img {
  height: 40px;
}

.person-header__open {
  margin-top: 0;
}

.person-footer {
  margin-top: var(--space-5);
  font-size: var(--text-sm);
}

.person-card__context,
.person-card__shared-by,
.join-card .person-card__label,
.person-card__standing,
.person-card__fact,
.person-pay__note {
  overflow-wrap: anywhere;
}

.person-card__context {
  margin: 0 0 var(--space-5);
  font-size: var(--text-sm);
  color: var(--muted);
}

.person-card__shared-by {
  margin-bottom: var(--space-4);
}

.join-card .person-card__label {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  letter-spacing: normal;
  color: var(--muted);
}

.person-card__standing {
  margin: var(--space-2) 0 0;
  font-weight: var(--weight-semibold);
}

.person-pay {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  margin: var(--space-6) 0 var(--space-2);
}

.person-pay:empty {
  display: none;
}

.person-pay__cta[disabled] {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

.person-pay__done {
  pointer-events: none;
  cursor: default;
}

.person-check {
  flex: none;
  width: 12px;
  height: 7px;
  margin-top: -3px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
}

.person-pay__note {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--muted);
}

.person-pay__undo {
  min-height: var(--tap-min);
  padding: 0 var(--space-3);
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}

.person-pay__undo[disabled] {
  opacity: 0.6;
  cursor: default;
}

.person-pay__error {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.person-card__fact {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--muted);
  text-wrap: balance;
}

.person-history {
  margin-top: var(--space-28);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  text-align: left;
}

.person-history + .person-card__fact {
  margin-top: var(--space-5);
}

.join-card .person-history__heading {
  margin: 0 0 var(--space-2);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  letter-spacing: normal;
  text-align: left;
}

.person-history__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.person-history__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border);
  font-size: var(--text-sm);
}

.person-history__row:first-child {
  border-top: 0;
}

.person-history__date {
  flex: 1 1 auto;
}

.person-history__amount {
  font-variant-numeric: tabular-nums;
}

.person-history__status {
  font-weight: var(--weight-semibold);
  text-align: right;
}

.person-history__status--paid {
  font-weight: var(--weight-regular);
  color: var(--muted);
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes person-settle {
    from { opacity: 0; transform: scale(0.97); }
    to { opacity: 1; transform: none; }
  }

  .person-pay__done {
    animation: person-settle var(--dur-press) var(--ease) both;
  }

  .person-pay__note,
  .person-pay__undo {
    animation: fade-in var(--dur-press) var(--ease) both;
  }
}

[data-theme="dark"] .device {
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.16) inset,
    0 1px 0 rgba(255, 255, 255, 0.24) inset,
    0 0 0 1px rgba(255, 255, 255, 0.06),
    var(--suby-elevation-hero);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .device {
    box-shadow:
      0 0 0 1px rgba(255, 255, 255, 0.16) inset,
      0 1px 0 rgba(255, 255, 255, 0.24) inset,
      0 0 0 1px rgba(255, 255, 255, 0.06),
      var(--suby-elevation-hero);
  }
}

.hero__phone::before,
.feature-detail-shot::before {
  content: "";
  position: absolute;
  z-index: -2;
  inset: -14% -22% -10%;
  border-radius: 50%;
  background: radial-gradient(
    55% 45% at 50% 45%,
    var(--phone-glow) 0%,
    var(--phone-glow-mid) 45%,
    transparent 72%
  );
}

.hero__phone::after,
.feature-detail-shot::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 8%;
  right: 8%;
  bottom: -3.5%;
  height: 4%;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(17, 17, 17, 0.26), transparent 70%);
  filter: blur(7px);
}


/* Two centred figures floating above three left-aligned cards read as two
   unrelated things. Sharing the cards' left edge, and a rule underneath, makes
   the band one claim: here are the numbers, here is what people said about them.
   The divider between the figures is what the padding is for — remove one and
   the other stops working. */
.social-proof__metrics {
  gap: 0;
  align-items: stretch;
  padding-bottom: var(--space-28);
  border-bottom: 1px solid var(--hairline);
}

@media (min-width: 601px) {
  .proof-metric {
    padding-inline-end: var(--band-gutter);
  }
  .proof-metric + .proof-metric,
  .review-quote + .review-quote {
    padding-inline-start: var(--band-gutter);
    border-left: 1px solid var(--rule);
  }
}

@media (max-width: 600px) {
  .social-proof__metrics {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-4);
  }
  .proof-metric + .proof-metric {
    padding-top: var(--space-4);
    border-top: 1px solid var(--rule);
  }
}

.proof-metric__value {
  font-weight: var(--weight-extrabold);
  font-size: clamp(36px, 4.6vw, 56px);
  letter-spacing: var(--tracking-tight);
}

/* ---------- "What you get" bullets ----------
   Four unlabelled paragraphs in boxes read as unstyled placeholder text. A
   marker and tighter measure make them scan as a list of capabilities. */
.feature-bullets li {
  position: relative;
  padding: 22px var(--space-6) 22px 54px;
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  text-wrap: pretty;
}

.feature-bullets li::before {
  content: "";
  position: absolute;
  left: 22px;
  top: 26px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--ink);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>") center / 12px 12px no-repeat,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>") center / 12px 12px no-repeat,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
}

/* ---------- Feature detail: in-detail / how-it-works / questions ---------- */
.detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 18px;
}

.detail-card {
  padding: var(--space-28);
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
}

.detail-card h3 {
  font-size: 19px;
  letter-spacing: -0.015em;
  margin-bottom: 10px;
}

.detail-card p {
  color: var(--muted);
  font-size: 16px;
  line-height: 1.62;
}

/* A counter rather than a glyph: the number is the affordance, so it should be
   typographic and sit in the margin like a proper numbered sequence. */
.step-list {
  counter-reset: step;
  display: grid;
  gap: 14px;
  list-style: none;
  padding: 0;
  margin: 0;
}

.step {
  counter-increment: step;
  position: relative;
  display: grid;
  grid-template-columns: 46px 1fr;
  align-items: start;
  gap: var(--space-5);
  padding: var(--space-6) var(--space-28);
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-step);
}

.step::before {
  content: counter(step);
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-chip);
  background: var(--ink);
  color: var(--on-ink);
  font-size: 17px;
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.step__body h3 {
  font-size: 18px;
  letter-spacing: -0.012em;
  margin-bottom: 6px;
}

.step__body p {
  color: var(--muted);
  font-size: 16px;
  line-height: 1.6;
}

.faq-list {
  display: grid;
  gap: 14px;
}

.faq-item {
  padding: var(--space-6) var(--space-28);
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-step);
}

.faq-item h3 {
  font-size: 17.5px;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-2);
}

.faq-item p {
  color: var(--muted);
  font-size: 16px;
  line-height: 1.62;
}

@media (max-width: 560px) {
  .step { grid-template-columns: 38px 1fr; gap: 14px; padding: 20px; }
  .step::before { width: 38px; height: 38px; font-size: 15px; }
}

.hero__phone::before,
.feature-detail-shot::before {
  inset: -20% -34% -14%;
  background:
    radial-gradient(48% 42% at 50% 42%, var(--phone-glow) 0%, var(--phone-glow-mid) 52%, transparent 76%);
}

.hero__phone::after,
.feature-detail-shot::after {
  left: 12%;
  right: 12%;
  bottom: -2.6%;
  height: 5.2%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(17, 17, 17, 0.34), transparent 72%);
  filter: blur(11px);
}

@media (max-width: 768px) {
  .hero__phone, .feature-detail-shot { width: min(100%, 300px); }
}


/* ---- Catalogue rail ----
   The section answers "will it know MY subscriptions" with the asset itself.
   Logos stay in full colour on a monochrome page on purpose: that is exactly
   what the app looks like — a black-and-white surface with the real brands as
   the only colour in it — so the rail is a preview of the product, not a
   trusted-by strip borrowed from someone else's site. */
.rail-section {
  /* The track is deliberately wider than the viewport. */
  overflow: clip;
  /* Tight to the hero above: the device and the catalogue are one thought —
     here is the app, here is everything it already knows. Full rhythm below. */
  padding-block: clamp(16px, 2vw, 32px) clamp(56px, 7vw, 96px);
}

.rail {
  position: relative;
  padding-block: clamp(18px, 2.2vw, 30px);
  background: var(--surface);
  margin-block: clamp(24px, 3vw, 40px);
  /* Both ends dissolve into the page, so the line reads as continuing past the
     screen rather than as a list that happens to be cut off. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}

/* Keep targets still for keyboard use, and suspend the nonessential loop when its
   visibility controller marks it offscreen or the document is hidden. */
.rail:focus-within .rail__track,
.rail[data-rail-paused] .rail__track {
  animation-play-state: paused;
  will-change: auto;
}

.rail__track {
  display: flex;
  align-items: flex-start;
  gap: 0;
  width: max-content;
}

/* The trailing gap is inside each equal run. That makes half the track width exactly
   one complete run, unlike a gap between 56 direct flex children, which is 7px off. */
.rail__run {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  flex: 0 0 auto;
  padding-inline-end: 14px;
}

/* The tile is the link. The name sits under it in reserved space, so revealing it on
   hover cannot shift the row. */
.rail__tile {
  --rail-tile: 76px;
  position: relative;
  flex: 0 0 auto;
  display: block;
  width: var(--rail-tile);
  text-decoration: none;
}

.rail__tile-face {
  position: relative;
  width: var(--rail-tile);
  height: var(--rail-tile);
  /* 28% is the app's own logo radius (DESIGN.md `rounded.logo`), so a tile here is
     the same shape as the row it came from. */
  border-radius: 28%;
  overflow: hidden;
  background: var(--bg);
  /* An outline pulled inside, not a border: these are app icons that fill the tile
     edge to edge, so the ring has to sit ON the artwork the way it does on the
     service rows in the app. --img-outline was too faint to survive the shelf. */
  outline: 1px solid var(--border);
  outline-offset: -1px;
  transition:
    transform var(--dur-emphasis) var(--ease-settle),
    box-shadow var(--dur-emphasis) var(--ease-out-expo);
}

/* The rail only ever holds services that have a logo, so the shared component's
   initial-letter fallback is never the right answer here: a rail whose whole argument
   is the logos cannot make that argument with a letter. A tile whose image fails
   leaves the strip instead. Both runs carry the same services, so the loop's exact
   -50% survives the removal. */
.rail__tile-placeholder {
  display: none;
}

.rail__tile:has(.rail__tile-img[data-failed="true"]) {
  display: none;
}

/* Edge to edge. Every one of these assets is already a square app icon with its own
   background and its own corner treatment, so insetting it inside a second rounded
   plate drew a sharp-cornered tile floating in a squircle — two plates where the
   product shows one. The tile's radius clips the icon instead. */
.rail__tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
  /* Roughly a third of these marks are transparent SVGs. Without a ground they
     floated with no tile behind them while the opaque ones read as proper icons —
     the row looked like two different components. */
  background: var(--bg);
}

.rail__tile-img[data-failed="true"] {
  display: none;
}

.rail__tile-name {
  display: block;
  margin-top: 8px;
  height: 1.2em;
  overflow: hidden;
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1.2;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .rail__tile:hover {
    opacity: 1;
  }
  .rail__tile:hover .rail__tile-face {
    transform: scale(1.08);
    box-shadow: var(--lift-2);
  }
  .rail__tile:hover .rail__tile-name {
    opacity: 1;
  }
}

/* The ring belongs on the tile face, not on the link box that also contains the
   invisible name. */
.rail__tile:focus-visible {
  outline: none;
}

/* A second ring outside the artwork, so it reads against both the icon and the page.
   The resting outline is inset and cannot double as a focus indicator. */
.rail__tile:focus-visible .rail__tile-face {
  box-shadow: 0 0 0 3px var(--focus-ring);
  outline-color: var(--ink);
}

.rail__tile:focus-visible .rail__tile-name {
  opacity: 1;
}

.rail__tile:active .rail__tile-face {
  transform: scale(0.97);
  transition-duration: var(--suby-duration-instant);
}

@media (max-width: 600px) {
  .rail__tile {
    --rail-tile: 64px;
  }
  /* The label was shown here because touch has no hover. At a 64px tile most names
     do not fit — "PlayStati…", "Crunchyr…" — and a truncated name cannot do the job
     it was added for. The logos are the argument; the heading and the catalogue link
     carry it in words, the same reason the second run is aria-hidden. It leaves
     rather than reserving a strip of empty shelf under every icon. */
  .rail__tile-name {
    display: none;
  }
}

/* ---- Snap Import: the page's one authored set piece ----
   Inverts to ink because a peak in the scroll needs a shift in density, not a new
   colour. `.pricing-card--featured` already establishes the inverted surface, so this
   is the system at full strength rather than an addition to it. */
.snap {
  background: var(--ink);
  color: var(--on-ink);
}

.snap__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  gap: clamp(32px, 6vw, 88px);
  align-items: center;
}

/* `.snap .` and not a bare class: `.section h2` centres every section heading and
   sets its own margin at (0,1,1), which outranks a single class. In a two-column
   band the heading has to sit flush with the copy under it. */
.snap .snap__heading {
  font-size: var(--text-h2);
  letter-spacing: var(--tracking-heading);
  text-align: start;
  margin: 0 0 var(--space-4);
  text-wrap: balance;
}

.snap__lede {
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  /* Tinted from the inverted foreground, never grey: on an ink ground a neutral grey
     drops secondary text under the contrast floor. */
  color: color-mix(in oklab, var(--on-ink) 78%, transparent);
  max-width: 46ch;
  margin: 0 0 var(--space-6);
  text-wrap: pretty;
}

.snap__beats {
  list-style: none;
  counter-reset: beat;
  margin: 0 0 var(--space-6);
  padding: 0;
  display: grid;
  gap: var(--space-4);
}

/* The sequence is the mechanism — screenshot, read, confirm — so the numbers carry
   information rather than decorating a list. */
.snap__beats li {
  counter-increment: beat;
  position: relative;
  padding-inline-start: 44px;
  line-height: var(--leading-normal);
  color: color-mix(in oklab, var(--on-ink) 86%, transparent);
}

.snap__beats li::before {
  content: counter(beat);
  position: absolute;
  inset-inline-start: 0;
  top: -2px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--on-ink) 34%, transparent);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--on-ink);
}

.snap__link { color: var(--on-ink); }

.snap__stage { position: relative; }
.snap__device { max-width: 300px; }

/* The scan is the reading, made visible. It rides the same view() timeline the rest of
   the page uses, so it scrubs with the scroll instead of firing on a timer. */
.snap__scan {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: 15.5%/7.15%;
  overflow: hidden;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes snap-scan {
      from { transform: translateY(-12%); opacity: 0; }
      12%  { opacity: 1; }
      88%  { opacity: 1; }
      to   { transform: translateY(112%); opacity: 0; }
    }

    .snap__scan::after {
      content: "";
      position: absolute;
      inset-inline: 6%;
      top: 0;
      height: 22%;
      border-radius: 999px;
      background:
        linear-gradient(
          to bottom,
          transparent 0%,
          color-mix(in oklab, var(--on-ink) 10%, transparent) 55%,
          color-mix(in oklab, var(--on-ink) 62%, transparent) 96%,
          color-mix(in oklab, var(--on-ink) 72%, transparent) 100%
        );
      animation: snap-scan linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 55%;
    }
  }
}

@media (max-width: 900px) {
  .snap__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-7);
  }
  .snap__device { max-width: 236px; }
}

/* Shared converter snapshots */
.rate-snapshot-page { padding: clamp(32px, 7vw, 88px) 0; }
.rate-snapshot-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  align-items: center;
  gap: clamp(32px, 7vw, 96px);
  max-width: 1080px;
}
.rate-snapshot-card {
  overflow: hidden;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.rate-snapshot-card__body { padding: clamp(24px, 4vw, 40px); }
.rate-snapshot-card__brand { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.rate-snapshot-wordmark { display: flex; align-items: center; gap: 8px; font-size: var(--text-title); font-weight: 700; letter-spacing: -.03em; }
.rate-snapshot-wordmark .header__brand-icon { width: 24px; height: 24px; }
.rate-snapshot-card__brand h1 { margin: 0; color: var(--muted); font-size: var(--text-sm); font-weight: 400; letter-spacing: 0; text-align: right; }
.rate-snapshot-amounts { margin-top: 40px; }
.rate-snapshot-amount { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-width: 0; }
.rate-snapshot-currency { flex-shrink: 0; padding: 8px 12px; border-radius: var(--radius-sm); background: var(--bg); font-size: var(--text-base); font-weight: 600; }
.rate-snapshot-value { min-width: 0; font-size: clamp(1.75rem, 3.5vw, 2.25rem); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.025em; line-height: 1.15; text-align: right; overflow-wrap: anywhere; }
.rate-snapshot-amount--result .rate-snapshot-value { font-size: clamp(2.25rem, 4.5vw, 3rem); font-weight: 800; }
.rate-snapshot-amount--long { flex-direction: column; align-items: flex-start; gap: 12px; }
.rate-snapshot-amount--long .rate-snapshot-value { width: 100%; font-size: clamp(.875rem, 1.8vw, 1.5rem); }
.rate-snapshot-direction { display: flex; align-items: center; gap: 16px; margin: 20px 0; color: var(--muted); }
.rate-snapshot-direction::before, .rate-snapshot-direction::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.rate-snapshot-direction__arrow { display: block; width: 18px; height: 18px; background: currentColor; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14m-7-7 7 7 7-7'/%3E%3C/svg%3E") center / contain no-repeat; }
.rate-snapshot-details { margin-top: 32px; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.rate-snapshot-details p { margin: 0; font-size: var(--text-base); }
.rate-snapshot-details .rate-snapshot-details__date { margin-top: 6px; color: var(--muted); font-size: var(--text-sm); }
.rate-snapshot-card__footer { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 16px; padding: 20px clamp(24px, 4vw, 40px); background: var(--bg); border-top: 1px solid var(--border); color: var(--muted); font-size: var(--text-sm); }
.rate-snapshot-card__footer a { text-decoration: underline; text-underline-offset: 3px; }
.rate-snapshot-context h2 { margin: 0 0 16px; font-size: var(--text-h3); letter-spacing: -.025em; line-height: 1.15; }
.rate-snapshot-context p { margin: 0; color: var(--muted); font-size: var(--text-base); line-height: 1.6; }
.rate-snapshot-context .rate-snapshot-context__note { margin-top: 12px; font-size: var(--text-sm); }
.rate-snapshot-app { margin-top: 32px; padding-top: 32px; border-top: 1px solid var(--border); }
.rate-snapshot-app h2 { font-size: var(--text-lg); letter-spacing: -.02em; }
.rate-snapshot-app .store-badges { margin-top: 20px; }
.rate-snapshot-more { display: inline-flex; align-items: center; min-height: 44px; margin-top: 12px; text-decoration: underline; text-underline-offset: 4px; font-size: var(--text-sm); }
.rate-snapshot-unavailable { grid-column: 1 / -1; max-width: 640px; margin: 0 auto; padding-block: 32px; }
.rate-snapshot-unavailable h1 { font-size: var(--text-h2); }
.rate-snapshot-unavailable p { margin: 20px 0 28px; color: var(--muted); }
@media (max-width: 760px) {
  .rate-snapshot-layout { grid-template-columns: minmax(0, 1fr); max-width: 520px; gap: 32px; }
  .rate-snapshot-page { padding-block: 24px 48px; }
  .rate-snapshot-context { padding-inline: 8px; }
}
@media (max-width: 360px) {
  .rate-snapshot-card__body { padding: 20px; }
  .rate-snapshot-card__brand { gap: 12px; align-items: flex-start; }
  .rate-snapshot-card__brand h1 { max-width: 90px; }
  .rate-snapshot-amount { gap: 12px; }
  .rate-snapshot-currency { padding: 6px 8px; }
}


/* ---- Home hierarchy ----
   One display size (the hero headline) and one H2 size for every section head, left-aligned
   except the closing call. Scoped to the page that has the hero; kept in this layer because the
   rail rules it retunes live here. */
main:has(.hero) {
  --home-h2-size: clamp(32px, 3.6vw, 48px);
  --home-section-y: clamp(40px, 5vw, 72px);
}

main:has(.hero) :is(
  .section h2, .section-intro h2,
  .home-section-head h2, .section-head h2, #faq h2, .cta-strip h2
) {
  max-width: none;
  font-size: var(--home-h2-size);
  line-height: 1.04;
  letter-spacing: var(--tracking-heading);
}

main:has(.hero) .section h2 { text-align: start; }
main:has(.hero) .cta-strip h2 { text-align: center; }
main:has(.hero) .home-section-head {
  max-width: none;
  margin-inline: 0;
}
main:has(.hero) > section.section:not(.hero) { padding-block: var(--home-section-y); }
main:has(.hero) > .rail-section { padding-block: clamp(8px, 1.4vw, 20px) var(--home-section-y); }
main:has(.hero) .rail { margin-block: 0; }
main:has(.hero) .section-head__lead { max-width: 52ch; }

@media (min-width: 900px) {
  .hero { padding-top: clamp(24px, 3.4vw, 48px); }
  .hero__layout { align-items: start; }
  .hero__copy { padding-top: clamp(16px, 5vh, 56px); }
}

} /* end @layer suby.overrides */
