/* Vidorio static pages (privacy, terms, help) - shared styling.
   Matches the app: near-black bg, cream text, purple accent, Inter. */
/* Brand wordmark font, same as the home nav/footer ("Vidorio" in Unbounded). */
@import url('https://fonts.googleapis.com/css2?family=Unbounded:wght@600;700;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');

:root {
  --bg: #07080a;
  --surface: #121419;
  --surface-2: #181b22;
  --border: rgba(245, 241, 232, 0.1);
  --border-strong: rgba(245, 241, 232, 0.18);
  --text: #f5f1e8;
  --muted: rgba(245, 241, 232, 0.62);
  --muted-soft: rgba(245, 241, 232, 0.45);
  --accent: #968bff;
  --accent-bright: #b4a6ff;
  --accent-soft: rgba(150, 139, 255, 0.16);
  --accent-line: rgba(150, 139, 255, 0.4);
  --line-accent: rgba(150, 139, 255, 0.5);
  --focus-ring: 0 0 0 3px var(--accent-soft);
  /* One headline scale for every article hero (news + blog). Kept here so the
     same title can never come out a different size on one page than another,
     and so the measure below it is expressed in em - the line length then
     tracks the type size instead of drifting on a wide screen.
     Each measure is min(100%, ...): the headline uses the whole column it is
     given and only stops once a line would get too long to scan. A hard em cap
     left a wide dead gutter beside every hero and broke short titles onto two
     cramped lines. */
  --headline: clamp(30px, 3.6vw, 46px);
  --headline-index: clamp(28px, 3.2vw, 42px);
  --headline-measure: min(100%, 20em);
  --headline-measure-centred: min(100%, 19em);
  --dek-measure: min(100%, 44em);
  --radius: 14px;
  --radius-sm: 9px;
  --radius-lg: 18px;
  --radius-pill: 999px;
  --dur-fast: 140ms;
  --dur-base: 160ms;
  --dur-mid: 220ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --shadow-card: 0 24px 70px -48px rgba(0, 0, 0, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.035);
  --maxw: 1080px;
  --maxw-wide: 1640px;
  /* Page CONTENT keeps its comfortable centred reading column (unchanged). */
  --shell: min(1200px, calc(100% - clamp(20px, 4vw, 64px) - clamp(20px, 4vw, 64px)));
  /* The top bar + bottom bar instead span the full width like the home nav /
     footer, sitting at the home page's gutter. */
  --bar-gutter: clamp(20px, 4vw, 72px);
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

/* Ambient gradient glow */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(60% 50% at 50% -10%, rgba(150, 139, 255, 0.16), transparent 70%),
    radial-gradient(40% 40% at 100% 0%, rgba(150, 139, 255, 0.08), transparent 70%);
  z-index: 0;
}

/* ---------- Launch offer bar (blog index + blog posts only) ----------
   Twin of the app's `.launch-promo` (apps/web/src/styles/home-page-nav.css) with
   the dismiss control removed: on the blog it is permanent, so there is nothing
   to close and no snooze to remember. Sits above the sticky header and scrolls
   away with the page, exactly like the home bar. */
.site-promo {
  position: relative;
  z-index: 11;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  min-height: 36px;
  overflow: hidden;
  color: #fff;
  background:
    radial-gradient(120% 200% at 50% -60%, rgba(176, 164, 255, 0.5), transparent 62%),
    linear-gradient(90deg, #6c5ce7, #7c6cf6 52%, #8b5cf6);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
}
/* Slow, occasional specular sheen gliding across for a premium feel. */
.site-promo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 32%,
    rgba(255, 255, 255, 0.12) 47%,
    rgba(255, 255, 255, 0.32) 50%,
    rgba(255, 255, 255, 0.12) 53%,
    transparent 68%
  );
  transform: translateX(-130%);
  animation: site-promo-sheen 7s ease-in-out 2s infinite;
}
@keyframes site-promo-sheen {
  0% { transform: translateX(-130%); }
  16%, 100% { transform: translateX(130%); }
}
@media (prefers-reduced-motion: reduce) {
  .site-promo::before { animation: none; opacity: 0; }
}
.site-promo-inner {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  max-width: 100%;
  padding: 5px 8px;
  color: inherit;
}
.site-promo-inner:hover {
  text-decoration: none;
}
.site-promo-badge {
  flex: 0 0 auto;
  padding: 2px 10px;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.32), rgba(255, 255, 255, 0.14));
  border: 1px solid rgba(255, 255, 255, 0.45);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  /* Tracking leaves a 0.09em gap after the final W that the left side has no
     match for, so the word sits a pixel left of the pill. Indent by the same
     amount to put the ink - not the box - in the centre. */
  text-indent: 0.09em;
  box-shadow:
    0 0 14px rgba(255, 255, 255, 0.28),
    inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
.site-promo-text {
  min-width: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
}
.site-promo-text strong {
  color: #fff;
  font-weight: 800;
  letter-spacing: -0.005em;
}
/* Lead-in and tail sit behind the offer they wrap, and must read as one voice:
   same colour, same weight, same 4px gap on either side of the bold offer.
   Spacing lives here, not in markup whitespace (the template joins the tags). */
.site-promo-lead,
.site-promo-sub {
  color: rgba(255, 255, 255, 0.76);
  font-weight: 600;
}
.site-promo-lead {
  margin-right: 4px;
}
.site-promo-sub {
  margin-left: 4px;
}
.site-promo-cta {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 11px;
  border-radius: 999px;
  background: linear-gradient(180deg, #ffffff, #f1ecff);
  color: #6d28d9;
  font-size: 10.5px;
  font-weight: 800;
  line-height: 1.1;
  white-space: nowrap;
  box-shadow:
    0 2px 10px -3px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: transform 150ms ease, box-shadow 200ms ease;
}
.site-promo-inner:hover .site-promo-cta {
  transform: translateX(2px);
  box-shadow:
    0 8px 20px -8px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(255, 255, 255, 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.site-promo-cta svg {
  display: block;
  transition: transform 150ms ease;
}
.site-promo-inner:hover .site-promo-cta svg {
  transform: translateX(2px);
}
@media (max-width: 600px) {
  .site-promo {
    min-height: 40px;
    padding: 0 8px;
  }
  .site-promo-inner {
    width: 100%;
    justify-content: center;
    gap: 8px;
    padding: 6px 0;
  }
  .site-promo-badge {
    padding: 2px 8px;
    font-size: 9.5px;
  }
  .site-promo-sub {
    display: none;
  }
  .site-promo-text {
    flex: 0 1 auto;
    font-size: 12px;
  }
  .site-promo-cta {
    display: none;
  }
}
@media (max-width: 380px) {
  .site-promo-inner {
    gap: 6px;
  }
  .site-promo-every {
    display: none;
  }
  .site-promo-text {
    font-size: 11.5px;
  }
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  /* Match the home nav's glassy, lifted finish (stronger blur + saturation, an
     inset top highlight and a soft drop shadow) - kept on the page's black
     surface rather than the home nav's indigo tint. */
  backdrop-filter: blur(18px) saturate(135%);
  background: rgba(7, 8, 10, 0.72);
  border-bottom: 1px solid var(--border);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.045),
    0 18px 42px -36px rgba(0, 0, 0, 0.75);
}
.site-header .bar {
  width: auto;
  max-width: none;
  margin: 0;
  /* Full-bleed with the home page's gutter (the bar spans the screen like the
     home nav, even though the page content below stays in its narrow column). */
  padding: 13px var(--bar-gutter);
  /* Three tracks - brand | links | CTA - with equal-width side tracks, so the
     link group is centred on the bar itself. (A flex row centred the links in
     the space left over between the brand and the CTA, which pulled them half
     the CTA's width to the left of true centre.) */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  /* Same brand-to-nav gap as the home nav so "Help" isn't crowding the
     wordmark. */
  gap: clamp(24px, 3.2vw, 42px);
  min-height: 60px;
}
.site-header .bar > .brand {
  justify-self: start;
}
.site-header .bar > .cta {
  justify-self: end;
}
/* Brand: favicon tile + "Vidorio" wordmark in Unbounded - identical to the
   home nav brand. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text);
}
.brand:hover {
  text-decoration: none;
}
.brand-icon {
  display: block;
  width: 30px;
  height: 30px;
  border-radius: 8px;
}
.brand span {
  font-family: 'Unbounded', sans-serif;
  font-weight: 700;
  font-size: 21px;
  letter-spacing: -0.015em;
  line-height: 1;
  color: #fff;
}
.site-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 2.2vw, 28px);
  flex-wrap: nowrap;
}
/* Nav links styled like the home nav: plain text with an animated underline on
   hover (no pill background), same colour/weight/size. */
.site-nav a {
  position: relative;
  color: rgba(245, 241, 232, 0.66);
  padding: 6px 2px;
  font-size: 13.5px;
  font-weight: 720;
  white-space: nowrap;
}
.site-nav a::after {
  content: '';
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 1px;
  height: 1px;
  border-radius: 999px;
  background: currentColor;
  opacity: 0;
  transform: scaleX(0.42);
  transform-origin: center;
  transition: opacity 150ms ease, transform 180ms ease;
}
.site-nav a:hover {
  color: #fff;
  text-decoration: none;
}
.site-nav a:hover::after {
  opacity: 0.42;
  transform: scaleX(1);
}
/* Primary "Open app" CTA: premium purple gradient pill with a soft glow, a
   glassy top highlight, and an arrow that slides on hover. */
.site-header .cta {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  padding: 6px 13px;
  border-radius: 9px;
  background: linear-gradient(135deg, #b4a6ff 0%, #7c6cf6 100%);
  box-shadow:
    0 6px 16px -8px rgba(124, 108, 246, 0.8),
    inset 0 1px 0 rgba(255, 255, 255, 0.32);
  transition: transform 160ms ease, box-shadow 220ms ease, filter 200ms ease;
}
.site-header .cta::after {
  content: '→';
  font-size: 12px;
  line-height: 1;
  transition: transform 160ms ease;
}
.site-header .cta:hover {
  color: #fff;
  text-decoration: none;
  transform: translateY(-1px);
  filter: brightness(1.07);
  box-shadow:
    0 14px 30px -10px rgba(124, 108, 246, 0.95),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
.site-header .cta:hover::after {
  transform: translateX(3px);
}

/* ---------- Layout ---------- */
.page {
  position: relative;
  z-index: 1;
  width: var(--shell);
  max-width: none;
  margin: 0 auto;
  padding: 56px 0 90px;
}
.page.wide {
  max-width: none;
}
h1 {
  font-size: clamp(30px, 5vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.025em;
  margin: 0 0 14px;
  font-weight: 800;
}
.lede {
  font-size: 18px;
  color: var(--muted);
  margin: 0 0 8px;
  max-width: 70ch;
}
.updated {
  color: var(--muted-soft);
  font-size: 14px;
  margin: 0 0 36px;
}

.legal h2 {
  font-size: 22px;
  letter-spacing: -0.01em;
  margin: 46px 0 12px;
  padding-top: 8px;
  font-weight: 700;
}
.legal h3 {
  font-size: 17px;
  margin: 26px 0 8px;
  font-weight: 700;
  color: var(--text);
}
.legal p:not(.eyebrow),
.legal li {
  color: rgba(245, 241, 232, 0.82);
}
.legal ul {
  padding-left: 20px;
}
.legal li {
  margin: 6px 0;
}
.legal strong {
  color: var(--text);
}

/* table of contents */
.toc {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 22px;
  margin: 0 0 18px;
}
.toc h2 {
  margin: 0 0 10px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
}
.toc ol {
  margin: 0;
  padding-left: 18px;
  columns: 2;
  column-gap: 28px;
}
.toc li {
  margin: 4px 0;
  break-inside: avoid;
}
.toc a {
  color: rgba(245, 241, 232, 0.78);
  font-size: 14px;
}

.note {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 10px 10px 0;
  padding: 12px 16px;
  margin: 18px 0;
  color: rgba(245, 241, 232, 0.86);
  font-size: 15px;
}

/* ---------- Legal documents (Terms, Privacy) ---------- */
/* Upgrade the long legal docs into a sticky-sidebar layout with scroll-spy
   navigation and clean section dividers. Scoped to .legal-doc so the plain
   .legal styling stays available elsewhere. */
.legal-doc {
  max-width: none;
}
.legal-head {
  max-width: none;
}
/* The intro is a single sentence - let it sit on one line on desktop (the page
   is wide now). It only wraps on smaller screens, and balances cleanly when it does. */
.legal-head .lede {
  max-width: none;
  text-wrap: balance;
}
/* Turn the "last updated" line into a small meta pill. */
.legal-doc .updated {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  padding: 5px 13px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 13px;
}
.legal-doc .updated::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
}

/* Two-column grid: sticky table of contents + article. */
.legal-grid {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 52px;
  align-items: start;
  margin-top: 38px;
}
.legal-aside {
  position: sticky;
  top: 84px;
  align-self: start;
  max-height: calc(100vh - 104px);
  overflow-y: auto;
}

/* TOC restyle: vertical nav with active-section highlight. */
.legal-doc .toc {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
}
.legal-doc .toc h2 {
  margin: 0 0 12px;
  font-size: 11.5px;
  letter-spacing: 0.14em;
  color: var(--muted-soft);
}
.legal-doc .toc ol {
  columns: 1;
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: toc;
}
.legal-doc .toc li {
  counter-increment: toc;
  margin: 1px 0;
}
.legal-doc .toc a {
  display: flex;
  gap: 9px;
  padding: 7px 12px;
  border-radius: 9px;
  border-left: 2px solid transparent;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.4;
  transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
}
.legal-doc .toc a::before {
  content: counter(toc);
  flex: 0 0 auto;
  color: var(--muted-soft);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}
.legal-doc .toc a:hover {
  color: var(--text);
  background: rgba(245, 241, 232, 0.05);
  text-decoration: none;
}
.legal-doc .toc a.active {
  color: var(--text);
  background: var(--accent-soft);
  border-left-color: var(--accent);
}
.legal-doc .toc a.active::before {
  color: var(--accent);
}

/* Article column: clean section dividers + anchor offset under sticky header. */
.legal-body {
  max-width: 880px;
}
.legal-doc .legal-body > h2 {
  scroll-margin-top: 96px;
  margin: 40px 0 14px;
  padding-top: 26px;
  border-top: 1px solid var(--border);
  font-size: 23px;
}
.legal-doc .legal-body > h2:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.legal-doc .legal-body > h3 {
  scroll-margin-top: 96px;
}
.legal-doc .legal-body p,
.legal-doc .legal-body li {
  font-size: 15.5px;
}

@media (max-width: 880px) {
  .legal-grid {
    grid-template-columns: 1fr;
    gap: 0;
    margin-top: 24px;
  }
  .legal-aside {
    position: static;
    max-height: none;
    overflow: visible;
    margin-bottom: 26px;
  }
  /* Restore the card look for the TOC when it sits inline; cap its height with
     a gentle internal scroll so a 13-item list doesn't bury the content. */
  .legal-doc .toc {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 14px 14px;
    max-height: 44vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .legal-doc .toc a {
    padding: 8px 12px;
    font-size: 14px;
  }
  .legal-body {
    max-width: none;
  }
  /* Bump reading size + tighten section dividers on smaller screens. */
  .legal-doc .legal-body > h2 {
    font-size: 21px;
    margin-top: 32px;
    padding-top: 22px;
  }
  .legal-doc .legal-body p,
  .legal-doc .legal-body li {
    font-size: 16px;
    line-height: 1.7;
  }
}

/* ---------- Help center ---------- */
.help-hero {
  text-align: center;
  padding: 18px 0 8px;
}
.help-hero h1 {
  margin-bottom: 10px;
}
/* The lede is one short line - give it the full hero width instead of the 70ch
   measure so it does not wrap on desktop. */
.help-hero .lede {
  max-width: none;
}
.search-wrap {
  max-width: 560px;
  margin: 22px auto 6px;
  position: relative;
}
.search-wrap input {
  width: 100%;
  padding: 15px 18px;
  font-size: 16px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  outline: none;
  font-family: inherit;
}
.search-wrap input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.search-count {
  text-align: center;
  color: var(--muted-soft);
  font-size: 14px;
  min-height: 20px;
  margin: 10px 0 0;
}

.cat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
  margin: 30px 0 50px;
}
.cat-card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 18px 16px;
  transition: border-color 140ms ease, transform 140ms ease, background 140ms ease;
}
.cat-card:hover {
  text-decoration: none;
  border-color: rgba(150, 139, 255, 0.5);
  background: var(--surface-2);
  transform: translateY(-2px);
}
.cat-card .ic {
  font-size: 22px;
  margin-bottom: 8px;
}
.cat-card .t {
  font-weight: 700;
  color: var(--text);
  margin: 0 0 4px;
}
.cat-card .d {
  color: var(--muted);
  font-size: 14px;
  margin: 0;
}

.faq-section {
  margin: 44px 0 0;
}
.faq-section > h2 {
  font-size: 24px;
  letter-spacing: -0.01em;
  margin: 0 0 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 10px;
}
details.faq {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin: 10px 0;
  overflow: hidden;
}
details.faq[open] {
  border-color: rgba(150, 139, 255, 0.4);
  background: var(--surface-2);
}
details.faq summary {
  cursor: pointer;
  padding: 16px 18px;
  font-weight: 600;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
}
details.faq summary::-webkit-details-marker {
  display: none;
}
details.faq summary::after {
  content: '+';
  color: var(--accent);
  font-size: 22px;
  font-weight: 400;
  line-height: 1;
  transition: transform 160ms ease;
}
details.faq[open] summary::after {
  transform: rotate(45deg);
}
details.faq .body {
  padding: 0 18px 18px;
  color: rgba(245, 241, 232, 0.8);
}
details.faq .body p:first-child {
  margin-top: 0;
}
.no-results {
  text-align: center;
  color: var(--muted);
  padding: 40px 0;
  display: none;
}

/* ---------- News articles ---------- */
.page.news-articles {
  max-width: 1060px;
}

/* ---------- Headline accent ----------
   Every article headline marks two words the way somebody would annotate a
   printout - a block behind them, a loop around them, or a rule under them.
   One mark to a headline, always over a pair: a gesture over a phrase is
   somebody pointing, the same gesture over a single word is a proofreading
   mark. The rule also sets the words purple and one weight heavier; the block
   and the loop leave the type alone, because something drawn round or behind
   the words is already the whole gesture. The build picks one style per
   headline (scripts/title-accent.mjs) and it never moves.

   All three are painted as backgrounds on the mark itself, never as a
   pseudo-element or a transform. Vertical padding grows the paint area without
   touching the line box. Horizontal padding does push the neighbouring words
   apart, so a gesture that needs side room takes it back with a negative margin:
   the drawing spreads into the word space that is already there instead of
   adding a second one. */
.title-accent {
  color: var(--accent-bright);
  font-weight: 700;
  text-shadow: 0 0 26px rgba(150, 139, 255, 0.3);
  background-repeat: no-repeat;
}

/* The block and the loop are painted as one shape across the whole mark, so
   neither survives a line break - the drawing would be sliced in half and each
   piece stretched back to full width. Those two hold their line. The rule
   is drawn by text-decoration, which follows the type across a break by itself,
   so an underlined mark is free to wrap and can span several words. */
.title-accent--highlight,
.title-accent--circle {
  white-space: nowrap;
}

/* A rule under the word. Drawn with text-decoration so it follows the type
   exactly; skip-ink lifts it around descenders. */
.title-accent--underline {
  text-decoration: underline;
  text-decoration-color: rgba(150, 139, 255, 0.7);
  text-decoration-thickness: 0.045em;
  text-underline-offset: 0.17em;
  text-decoration-skip-ink: auto;
}

/* A block behind the word: one even rectangle, the same amount of colour on
   every side of the type. Square corners, like a marker pulled across a line -
   a rounded pill reads as a UI chip sitting in the headline instead.

   The block never recolours what it goes behind, for the same reason the loop
   does not (see .title-accent--circle): a filled panel is already the whole
   gesture, and purple type inside a purple panel is a second mark fighting the
   first. So this style carries the plain tone itself.

   The block is drawn on the loop's own tone, so the two annotations agree on
   what purple means and only the shape of the gesture changes. The loop can
   carry that tone nearly solid because it is a stroke with the page behind it;
   a filled band has the headline on top of it, so it takes the same purple at
   half strength and the type stays the brightest thing in the line.

   Height is set by hand rather than taken from the padding box, and it is set
   from the type rather than from the font. The padding box is the font's whole
   content area, ascent to descent, which leaves an obvious slab of colour under
   a word with nothing hanging below the baseline. So the band is measured off
   the cap line and the baseline instead: 0.1em of colour above the capitals and
   0.1em below, the same on both edges.

   Space Grotesk sets its capitals 0.708em tall on a 1em ascent and a 0.292em
   descent, so cap line to baseline is 0.708em, plus 0.1em either side is
   0.908em - and because the gap above the capitals happens to equal the font's
   descent, that band is already centred in the content box (Inter, the
   fallback, lands within a hundredth of the same place). Side padding is given
   back exactly by the negative margin, so the block widens into the word spaces
   already there and the headline sets identically marked or not. */
.title-accent--highlight {
  color: inherit;
  font-weight: inherit;
  text-shadow: none;
  padding: 0 0.1em;
  margin-inline: -0.1em;
  background-image: linear-gradient(rgba(180, 166, 255, 0.5), rgba(180, 166, 255, 0.5));
  background-position: center;
  background-size: 100% 0.91em;
}

/* A phrase with a tail in it - a g, a y, a comma - would have that tail cut off
   by the band above, so it gets a deeper one: still 0.1em of colour above the
   capitals, but the bottom edge now clears the descenders (0.208em under the
   baseline in Space Grotesk) by the same 0.1em. That is 1.117em of band, and
   its centre drops 0.104em below the content box's own centre. Only the bottom
   edge moves; the top edge sits exactly where it does on a phrase without a
   tail, so two marks in one headline line up along the top. */
.title-accent--highlight.title-accent--descends {
  background-position: center calc(50% + 0.104em);
  background-size: 100% 1.117em;
}

/* A loop drawn around the two words, closing past where it started. Only a
   short pair gets this style, so the loop never stretches into a flat oval.

   The loop never recolours what it goes round. A ring drawn by hand is already
   the whole gesture - turning the words purple and one weight heavier as well
   is a second mark on top of the first, and the two fight. So this style
   carries the plain tone itself (see .title-accent--plain) rather than waiting
   to be asked for it: whatever the headline is set in, the looped words stay
   in it and the ring is the only thing pointing at them.

   A mark is always two words wide (scripts/title-accent.mjs), and a ring drawn
   round a pair at one word's proportions closes in on the words either side and
   cuts through the ascenders and the descenders. So it is drawn well clear of
   the type: taller than the line, wider than the words, and giving back only
   most of its horizontal padding - a loop this far out from the type needs the
   room it keeps. */
.title-accent--circle {
  color: inherit;
  font-weight: inherit;
  text-shadow: none;
  padding: 0.5em 0.62em;
  margin-inline: -0.34em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 96' preserveAspectRatio='none'%3E%3Cpath d='M158 8C86 1 30 12 17 32 8 46 14 63 36 74c30 14 124 16 164 2 28-10 36-30 22-46C212 15 186 9 158 8' fill='none' stroke='%23b4a6ff' stroke-opacity='0.88' stroke-width='2.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-position: center calc(50% + 0.02em);
  background-size: 100% 1.46em;
}

/* A headline that is read as a title rather than as a sentence takes the mark
   on its own: the annotation is drawn and the words keep the headline's own
   colour and weight, so the drawing is the only thing pointing at them. The
   loop takes this by itself; a swipe or a rule takes it when asked. */
.title-accent--plain {
  color: inherit;
  font-weight: inherit;
  text-shadow: none;
}

/* With no purple type behind it the rule is the whole mark, so it is drawn on
   the loop's own stroke tone and a shade heavier - a thin, dim line under a
   headline at this size reads as an underlined link, not as an annotation. */
.title-accent--underline.title-accent--plain {
  text-decoration-color: rgba(180, 166, 255, 0.85);
  text-decoration-thickness: 0.055em;
}


/* ---------- Vidorio blog ----------
   Same house style as the rest of the static pages: near-black surfaces, Inter
   for reading, Space Grotesk for display, purple accent. No paper cosplay, no
   ghost numerals, no drop caps - the posts do the talking. */
.blog-body {
  --blog-body-text: rgba(245, 241, 232, 0.8);
}

.blog-dot {
  display: inline-block;
  flex: 0 0 auto;
  width: 3px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: currentColor;
  opacity: 0.5;
}

/* ---------- Index ---------- */
/* Same measure as the news index (.page.news-articles), so three blog cards
   across land on exactly the width of three news cards. */
/* The tail padding mirrors the closing CTA's own margin-top, so the card sits in
   an even band of space instead of a thin gap above and a deep one below. */
.page.blog-index {
  max-width: 1060px;
  padding-bottom: clamp(42px, 5vw, 60px);
}

/* Centred title over a thin meta strip - the card grid below carries the
   content. */
.blog-hero {
  padding: 6px 0 0;
  text-align: center;
}
.blog-hero h1 {
  max-width: var(--headline-measure-centred);
  margin: 0 auto;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: var(--headline-index);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.034em;
  text-wrap: balance;
}
.blog-hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  /* The headline's 1.1 line-height parks almost no half-leading under it, so
     this box gap is nearly the whole optical gap down to the strip. */
  margin-top: 20px;
  color: var(--muted-soft);
  font-size: 12.5px;
  font-weight: 600;
}

/* Columns sit 20px apart, rows further: a row break crosses a whole card - the
   meta rule of one post to the next post's cover plate - so it needs more air
   than the sliver between two covers, or the grid reads as one dense block. */
.blog-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 36px 20px;
  margin: clamp(30px, 4vw, 44px) 0 0;
  padding: 0;
  list-style: none;
}
/* The meta strip is optically centred in its band: 16px above (padded out by
   the lede's half-leading) and 24px below (the card edge carries none), so the
   ink sits ~27px clear on both sides - the clamp margin above is for the
   related list at the foot of a post, where the gap follows a heading instead. */
.blog-index .blog-list {
  margin-top: 24px;
}
/* The related list at the foot of a post now sits on the index's own measure,
   so it runs three across exactly like the index grid. */
/* Declared here rather than in the shared 880px block further down: the 760px
   block that drops both lists to one column comes first in the file, so this
   has to sit above it to lose to it. */
/* Three across only once the grid is wide enough to carry them: the columns
   stop growing at the page's 1060px measure, and below ~1120px of viewport a
   third column costs the title more size than a second column costs the grid. */
@media (max-width: 1120px) {
  .blog-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* The card is not a surface. Only the cover plate is drawn as a tile; the
   title and meta sit under it as plain type on the page itself, so a post
   reads as art-plus-caption rather than one sealed bubble. The radius stays
   for the focus outline, which wraps the whole card. */
.blog-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100%;
  padding: 0;
  color: inherit;
  border-radius: var(--radius-lg);
  transition: transform var(--dur-base) var(--ease-out);
}
.blog-card:hover {
  text-decoration: none;
}
.blog-card:focus-visible,
.blog-cta-link:focus-visible,
.blog-breadcrumb a:focus-visible,
.blog-sources a:focus-visible,
.blog-table-wrap:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Plain type on the page, with a small inset so the words line up with the
   tile's ink rather than its curved edge. This stays a column with a growing
   gap-free tail: the meta row still pushes itself to the bottom with
   margin-top:auto. */
.blog-card-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 10px;
  padding: 2px 4px 0;
  /* The title below sizes itself against this column, so the text box - not the
     card, whose fixed padding does not scale with it - is the query container. */
  container-type: inline-size;
}

/* h2 on the index, h3 in the related list: the same card, so the same type. */
.blog-card h2,
.blog-card h3 {
  margin: 0;
  font-family: 'Space Grotesk', Inter, sans-serif;
  /* Two lines, whatever the post is called. The longest title we publish wraps
     to exactly two lines at 5.8% of the text column, and because the type and
     the column scale together that ratio holds at every grid width. Past a
     336px column two lines already fit at the design size, so the cap takes
     over and wide cards - two across, or one on a phone - stay at 19.5px. */
  font-size: min(19.5px, 5.8cqw);
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.026em;
  /* The card column is narrow, and both balance and pretty spend that width on
     evening the lines out - a three-line title ends up three-thirds empty.
     Plain greedy wrapping fills each line to the edge, which is what a card
     title wants even when it leaves a short last line. */
  text-wrap: wrap;
}
.blog-card-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  /* The rule wants to sit centred between the title and this row. Above it there
     is only the 10px column gap plus the title's descender slack; 14px below
     overshot that, because the meta's own line box adds its ascender slack on
     top. 10px lands the tag pill the same distance under the rule as the
     title's ink sits above it. */
  padding-top: 10px;
  border-top: 1px solid var(--border);
  color: var(--muted-soft);
  font-size: 12.5px;
}

/* The category opens that footer row - it is what the card is about, so it reads
   before the date rather than trailing the row. It wears the same flat tag the
   editor's project status pills wear - barely-broken corners, a hair over its
   own cap height - only tinted accent instead of the app's green. */
.blog-card-tag {
  --tag-rgb: 150, 139, 255;
  display: inline-flex;
  align-items: center;
  min-height: 17px;
  padding: 0 6px;
  border-radius: 3px;
  border: 1px solid rgba(var(--tag-rgb), 0.26);
  background: linear-gradient(180deg, rgba(var(--tag-rgb), 0.2), rgba(var(--tag-rgb), 0.075));
  color: var(--accent-bright);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  transition:
    border-color var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}
.blog-card:hover .blog-card-tag {
  border-color: rgba(var(--tag-rgb), 0.44);
  background: linear-gradient(180deg, rgba(var(--tag-rgb), 0.29), rgba(var(--tag-rgb), 0.12));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.09),
    0 8px 18px -12px rgba(var(--tag-rgb), 0.85);
}
@media (prefers-reduced-motion: reduce) {
  .blog-card-tag {
    transition: none;
  }
}

/* ---------- Cover plate ----------
   Every card opens with its own plate: a short line of cover type over art
   drawn from the product's own interface (build-blog paints it as inline SVG,
   so there is no image to load and nothing to go stale). The art is deliberately
   quiet - it is a texture behind a headline, not a picture competing with one -
   and every colour it uses is named here rather than in the markup, so the
   plates follow the palette. The type is sized in cqw against the plate, so the
   same plate reads correctly in a wide card, a narrow one, and on a phone. */
.blog-cover {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  container-type: inline-size;
  aspect-ratio: 16 / 9;
  padding: 0 9%;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 130% at 50% 0%, rgba(150, 139, 255, 0.15), transparent 62%),
    linear-gradient(180deg, #14171f, #0a0c10);
  box-shadow: var(--shadow-card);
  transition:
    border-color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}
/* Only the plate lifts. The title and meta line under it are plain type on the
   page, sitting on the same baselines as the cards either side, so lifting the
   whole card pulled those words off their row for the length of a hover. */
.blog-card:hover .blog-cover {
  border-color: rgba(150, 139, 255, 0.34);
  transform: translateY(-2px);
}

.blog-cover-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.85;
  transition:
    opacity var(--dur-mid) var(--ease-out),
    transform var(--dur-mid) var(--ease-out);
}
.blog-cover .cv-ink {
  fill: rgba(245, 241, 232, 0.13);
}
.blog-cover .cv-soft {
  fill: rgba(245, 241, 232, 0.07);
}
.blog-cover .cv-accent {
  fill: rgba(150, 139, 255, 0.42);
}
.blog-cover .cv-frame {
  fill: none;
  stroke: rgba(245, 241, 232, 0.11);
  stroke-width: 1.2;
}
.blog-cover .cv-accent-frame {
  fill: rgba(150, 139, 255, 0.07);
  stroke: rgba(150, 139, 255, 0.3);
  stroke-width: 1.2;
}
.blog-cover .cv-frame-dot {
  fill: none;
  stroke: rgba(245, 241, 232, 0.17);
  stroke-width: 1.5;
}

/* The scrim sits between the art and the type: darkest where the words are,
   so a busy plate can never cost the cover line its contrast. */
.blog-cover::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(56% 60% at 50% 52%, rgba(7, 8, 10, 0.88), rgba(7, 8, 10, 0.4) 72%, rgba(7, 8, 10, 0.12)),
    linear-gradient(180deg, rgba(7, 8, 10, 0.26), rgba(7, 8, 10, 0.5));
}

.blog-cover-title {
  position: relative;
  z-index: 1;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: clamp(17px, 7.2cqw, 31px);
  font-weight: 600;
  line-height: 1.17;
  letter-spacing: -0.024em;
  text-align: center;
  text-wrap: balance;
  color: var(--text);
  text-shadow: 0 2px 20px rgba(7, 8, 10, 0.75);
}

.blog-card:hover .blog-cover-art {
  opacity: 1;
  transform: scale(1.02);
}

.blog-list-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin: clamp(46px, 6vw, 70px) 0 0;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
.blog-list-head h2 {
  margin: 0;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
/* ---------- Shared closing call to action ---------- */
.blog-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px 32px;
  margin-top: clamp(42px, 5vw, 60px);
  padding: clamp(16px, 1.7vw, 20px) clamp(22px, 2.6vw, 30px);
  background:
    radial-gradient(80% 160% at 0% 0%, rgba(150, 139, 255, 0.1), transparent 60%),
    var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
}
/* The copy block takes the row's leftover width, and its flex-basis is 0 so the
   lede's natural length can never push the button onto a row of its own - on a
   narrow shell (the post page) the sentence wraps instead of the button moving. */
.blog-cta > div {
  flex: 1 1 0;
  min-width: 0;
}
.blog-cta h2 {
  margin: 0 0 4px;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: clamp(20px, 2.2vw, 24px);
  line-height: 1.28;
  font-weight: 600;
  letter-spacing: -0.028em;
}
.blog-cta p {
  max-width: none;
  margin: 0;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.5;
}
/* Same pill as the header's "Open app" CTA: purple gradient, glassy top
   highlight, soft glow, and an arrow that slides on hover. */
.blog-cta-link {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 5px;
  padding: 6px 13px;
  color: #fff;
  background: linear-gradient(135deg, #b4a6ff 0%, #7c6cf6 100%);
  border-radius: var(--radius-sm);
  box-shadow:
    0 6px 16px -8px rgba(124, 108, 246, 0.8),
    inset 0 1px 0 rgba(255, 255, 255, 0.32);
  font-size: 13px;
  font-weight: 700;
  transition:
    transform var(--dur-base) var(--ease-out),
    box-shadow var(--dur-mid) var(--ease-out),
    filter var(--dur-mid) var(--ease-out);
}
.blog-cta-link span {
  font-size: 12px;
  line-height: 1;
  transition: transform var(--dur-base) var(--ease-out);
}
.blog-cta-link:hover {
  color: #fff;
  text-decoration: none;
  transform: translateY(-1px);
  filter: brightness(1.07);
  box-shadow:
    0 14px 30px -10px rgba(124, 108, 246, 0.95),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
.blog-cta-link:hover span {
  transform: translateX(3px);
}

/* ---------- Post ---------- */
/* A post runs on the same measure as the blog and news indexes (1060px), so a
   card grid and the article it opens keep identical side margins.
   There is no contents rail beside it and no narrower reading column inside it:
   the headline, the dek and the prose all run the full measure of the column so
   a line stops at the column edge rather than partway across it. */
.page.blog-post-page {
  max-width: 1060px;
  padding-top: 30px;
  padding-bottom: clamp(72px, 8vw, 110px);
}

.blog-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 20;
  width: 100%;
  height: 2px;
  pointer-events: none;
}
.blog-progress i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.blog-breadcrumb {
  grid-area: crumb;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  margin-bottom: 22px;
  color: var(--muted-soft);
  font-size: 13.5px;
}
.blog-breadcrumb a {
  color: var(--muted);
}
.blog-breadcrumb a:hover {
  color: var(--text);
  text-decoration: none;
}

/* The hero is one grid so the caption line can sit where each width has room
   for it: stacked under the headline on a phone, and out on the breadcrumb's
   own line - hard right - on a desktop, where that row is otherwise empty. The
   areas do the moving, so both layouts read from a single copy of the markup. */
.blog-post-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    'crumb'
    'title'
    'meta';
  max-width: none;
  padding-bottom: clamp(28px, 3.4vw, 38px);
}
/* Ranged left against a hard column edge, so it sets like a printed headline:
   every line runs to the edge of the column and only the last one is short.
   `balance` is for a centred title, where the eye reads the two halves as a
   pair; here it evened the lines out by pulling the first one in and left a
   dead gutter down the right of the page. `pretty` keeps the widow control and
   gives the space back. */
.blog-post-hero h1 {
  grid-area: title;
  max-width: none;
  /* Grid rows do not collapse margins: the breadcrumb's own 22px sets this gap. */
  margin: 0 0 18px;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: var(--headline);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.035em;
  text-wrap: pretty;
}
/* The post opens with its own cover instead of a summary line: the plate the
   card carried on the index, shaped as a letterbox the way a news article's
   lead cover is, so the wall and the page show a reader the same picture. It
   runs the full column and stays shallow, so the key points under it are still
   in view when the page lands. */
.blog-cover-lead {
  aspect-ratio: 16 / 5;
  margin: 0 0 clamp(34px, 4.2vw, 44px);
}
.blog-cover-lead .blog-cover-title {
  font-size: clamp(20px, 4.6cqw, 40px);
}
.blog-post-meta {
  grid-area: meta;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  color: var(--muted-soft);
  font-size: 13.5px;
}

/* Desktop: the date and read time ride the breadcrumb line, ranged right. No
   rule under the headline then - the caption is no longer closing the hero, so
   the line would only be separating the headline from itself. */
@media (min-width: 761px) {
  .blog-post-hero {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      'crumb meta'
      'title title';
  }
  .blog-post-meta {
    align-self: start;
    justify-self: end;
    flex-wrap: nowrap;
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
  }
}

.blog-takeaways {
  margin: 0 0 clamp(40px, 5vw, 58px);
  padding: 22px 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent-line);
  border-radius: var(--radius);
}
/* Not the tiny wide-tracked purple eyebrow: a plain small heading in the page
   text colour, sentence case, sitting on a hairline that separates it from the
   points below. The card keeps its accent - only the label changed. */
.blog-takeaways-label {
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: 16.5px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.02em;
}
.blog-takeaways ol {
  display: grid;
  gap: 9px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: takeaway;
}
.blog-takeaways li {
  position: relative;
  padding-left: 26px;
  color: var(--blog-body-text);
  font-size: 15px;
  line-height: 1.62;
  counter-increment: takeaway;
}
.blog-takeaways li::before {
  content: counter(takeaway);
  position: absolute;
  top: 1px;
  left: 0;
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.blog-post-body > section {
  scroll-margin-top: 92px;
}
/* Left-aligned against the column edge, same as .blog-post-hero h1 above:
   `balance` pulls the first line in short to even out the wrap, leaving a
   dead gutter on the right. `pretty` keeps widow control without that. */
.blog-post-body > section > h2,
.blog-sources h2 {
  margin: clamp(38px, 4.6vw, 54px) 0 16px;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: clamp(21px, 2.4vw, 26px);
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.028em;
  text-wrap: pretty;
}
.blog-post-body > section:first-child > h2 {
  margin-top: 0;
}
.blog-post-body > section > p {
  margin: 0 0 16px;
  color: var(--blog-body-text);
  font-size: 16.5px;
  line-height: 1.76;
}
.blog-post-body > section > p:last-child {
  margin-bottom: 0;
}

.blog-steps {
  display: grid;
  gap: 0;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}
.blog-steps li {
  display: flex;
  gap: 16px;
  padding: 18px 0;
  border-top: 1px solid var(--border);
}
.blog-steps li:last-child {
  border-bottom: 1px solid var(--border);
}
.blog-steps li > span {
  flex: 0 0 auto;
  /* The number labels the whole step, not its first line, so it sits on the
     row's centre rather than riding the heading's baseline. */
  align-self: center;
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.blog-steps h3 {
  margin: 0 0 5px;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: -0.012em;
}
.blog-steps p {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.68;
}

/* Comparison board.
   A board is read two ways at once - across one platform's row, and down one
   question's column - so both directions get a guide. A hairline grid keeps a
   cell that wrapped onto three lines tied to the heading above it, and the
   label column sits on its own tinted track so the eye always knows where a
   row begins. The cells also carry more contrast than the paragraphs around
   them: this is the densest reading on the page, not an aside. */
.blog-table-wrap {
  /* One faint rule for the whole grid. The heavier --border is kept for the
     board's own outline and for the line under the headings, so the frame
     stays a step stronger than the ruling inside it. */
  --board-rule: rgba(245, 241, 232, 0.06);
  --board-track: rgba(245, 241, 232, 0.032);
  --board-band: rgba(245, 241, 232, 0.02);
  margin: 26px 0 0;
  overflow-x: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.blog-table-wrap table {
  width: 100%;
  /* Fixed widths, from the colgroup the builder writes. Left automatic, the
     longest sentence claimed the row and the other columns wrapped beside a
     dead gutter; declared widths give every answer the same measure. */
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 14.5px;
}
.blog-table-wrap th,
.blog-table-wrap td {
  padding: 14px 18px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--board-rule);
  overflow-wrap: break-word;
}
/* Column rules: one line per gutter. The label column draws its own right edge
   below, so the cell that follows it does not draw a second line on top. */
.blog-table-wrap thead th + th,
.blog-table-wrap td + td {
  border-left: 1px solid var(--board-rule);
}
.blog-table-wrap thead th:nth-child(2),
.blog-table-wrap tbody td:first-of-type {
  border-left: 0;
}
.blog-table-wrap thead th {
  color: rgba(245, 241, 232, 0.56);
  background: var(--board-track);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  line-height: 1.35;
  text-transform: uppercase;
  border-bottom-color: var(--border);
}
.blog-table-wrap tbody th {
  color: var(--text);
  background: var(--board-track);
  border-right: 1px solid var(--board-rule);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.5;
}
.blog-table-wrap tbody tr:nth-child(even) td {
  background: var(--board-band);
}
.blog-table-wrap td {
  color: rgba(245, 241, 232, 0.76);
  line-height: 1.6;
  text-wrap: pretty;
}
/* Following one row across a wide board is the hard part, so the pointer marks
   the row it is on. */
@media (hover: hover) {
  .blog-table-wrap tbody tr:hover th,
  .blog-table-wrap tbody tr:hover td {
    background: rgba(245, 241, 232, 0.055);
  }
  .blog-table-wrap tbody tr:hover td {
    color: var(--text);
  }
}
/* Scoped to the body: `tr:last-child` also matches the headings row, which
   would drop the line the board hangs from. */
.blog-table-wrap tbody tr:last-child th,
.blog-table-wrap tbody tr:last-child td {
  border-bottom: 0;
}
/* Narrow screens: columns of prose need about 200px each before they stop
   wrapping into ragged towers, and a board that scrolls sideways hides the
   comparison instead of showing it. So under a tablet's width each row becomes
   its own card - the label as the card's title, then one labelled line per
   column, read top to bottom. The headings row is dropped because every value
   now carries its own heading, printed from the cell's data-label. */
@media (max-width: 880px) {
  .blog-table-wrap {
    overflow: visible;
    background: none;
    border: 0;
    border-radius: 0;
  }
  .blog-table-wrap table,
  .blog-table-wrap tbody,
  .blog-table-wrap tr,
  .blog-table-wrap th,
  .blog-table-wrap td {
    display: block;
    width: auto;
  }
  .blog-table-wrap colgroup,
  .blog-table-wrap thead {
    display: none;
  }
  .blog-table-wrap tr {
    margin-top: 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
  }
  .blog-table-wrap tbody tr:first-child {
    margin-top: 0;
  }
  /* The last row is a card like any other here, so the rules the board dropped
     from its final row are put back and only the card's own last line goes
     without one. */
  .blog-table-wrap tbody th,
  .blog-table-wrap tbody tr:last-child th {
    padding: 12px 16px;
    background: var(--board-track);
    border-right: 0;
    border-bottom: 1px solid var(--border);
    font-size: 15.5px;
  }
  .blog-table-wrap td,
  .blog-table-wrap tbody tr:nth-child(even) td,
  .blog-table-wrap tbody tr:last-child td {
    padding: 12px 16px;
    background: none;
    border-left: 0;
    border-bottom: 1px solid var(--board-rule);
  }
  .blog-table-wrap tbody tr td:last-child {
    border-bottom: 0;
  }
  .blog-table-wrap td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    color: rgba(245, 241, 232, 0.5);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
  }
}

.blog-faq {
  margin-top: clamp(46px, 5.5vw, 64px);
}
.blog-faq details {
  border-top: 1px solid var(--border);
}
.blog-faq details:last-child {
  border-bottom: 1px solid var(--border);
}
.blog-faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 0;
  cursor: pointer;
  list-style: none;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.45;
}
.blog-faq summary::-webkit-details-marker {
  display: none;
}
.blog-faq summary::after {
  content: '+';
  flex: 0 0 auto;
  color: var(--accent);
  font-size: 20px;
  font-weight: 400;
  line-height: 1;
  transition: transform var(--dur-base) var(--ease-out);
}
.blog-faq details[open] summary::after {
  transform: rotate(45deg);
}
.blog-faq details p {
  max-width: none;
  margin: 0 0 18px;
  color: var(--muted);
  font-size: 15.5px;
  line-height: 1.72;
}

/* The reference list that closes a post. It is the last thing on the page, so
   it is built as a proper panel rather than a paragraph of links: a titled
   head, then one tappable row per source carrying its number, its title and
   the domain it points at. */
.blog-sources {
  margin-top: clamp(46px, 5.5vw, 64px);
  padding: 20px;
  background:
    radial-gradient(120% 150% at 0% 0%, rgba(150, 139, 255, 0.08), transparent 58%),
    var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
.blog-sources-head {
  display: flex;
  align-items: center;
  gap: 10px;
}
.blog-sources-mark {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 26px;
  height: 26px;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: 8px;
}
.blog-sources h2 {
  margin: 0;
  font-size: 17px;
  letter-spacing: -0.012em;
}
.blog-sources-note {
  max-width: 54ch;
  margin: 10px 0 16px;
  color: var(--muted-soft);
  font-size: 13.5px;
  line-height: 1.6;
}
.blog-sources-list {
  display: grid;
  gap: 7px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}
.blog-source {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding: 11px 14px;
  color: var(--text);
  background: rgba(255, 255, 255, 0.018);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition:
    background var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}
.blog-source:hover {
  background: rgba(150, 139, 255, 0.07);
  border-color: var(--accent-line);
  text-decoration: none;
}
.blog-source-index {
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.blog-source-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.blog-source-label {
  color: var(--text);
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.45;
}
.blog-source:hover .blog-source-label {
  color: var(--accent-bright);
}
.blog-source-host {
  color: var(--muted-soft);
  font-size: 12px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.blog-source-arrow {
  color: var(--muted-soft);
  transition:
    color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}
.blog-source:hover .blog-source-arrow {
  color: var(--accent);
  transform: translate(2px, -2px);
}
.blog-source:hover .blog-source-arrow.is-internal {
  transform: translateX(3px);
}
@media (prefers-reduced-motion: reduce) {
  .blog-source-arrow {
    transition: color var(--dur-base) var(--ease-out);
  }
  .blog-source:hover .blog-source-arrow,
  .blog-source:hover .blog-source-arrow.is-internal {
    transform: none;
  }
}
@media (max-width: 640px) {
  .blog-sources {
    padding: 18px 16px;
  }
  .blog-source {
    gap: 12px;
    padding: 11px 12px;
  }
}

.blog-related {
  margin-top: clamp(56px, 7vw, 84px);
}
/* The related cards sit closer to the rule than the index list does to its hero
   - the line already does the separating, so the wide gap only made the section
   read as detached from its own heading. */
.blog-related .blog-list {
  margin-top: clamp(22px, 2.6vw, 28px);
}

/* ---------- Responsive ---------- */
@media (max-width: 760px) {
  /* Stacked full-width cards get a wider row gap than the desktop grid: with
     no columns beside them, the 36px band read as one continuous feed. */
  .blog-list {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 46px;
  }
  .blog-cta {
    flex-direction: column;
    align-items: flex-start;
  }
  .blog-post-body > section > p {
    font-size: 16px;
  }
}

/* Phones: the card carries a headline and a button, nothing else. The lede is
   dropped (it only repeats what the page already said), the heading stays on a
   single line at every phone width, and the pill runs the card's full width as
   a real tap target - the same shape the news article CTA takes on a phone. */
@media (max-width: 560px) {
  .page.blog-index {
    padding-bottom: 48px;
  }
  .blog-cta {
    align-items: stretch;
    gap: 14px;
    margin-top: 48px;
    padding: 20px 18px;
  }
  /* The headline never wraps: below ~394px it shrinks with the viewport
     instead. The factor is the string's own width-per-font-pixel ratio (~14.3
     at 600 weight, and Inter as fallback measures within half a percent of
     Space Grotesk), minus the card's 78px of side padding and room for a
     desktop scrollbar. */
  .blog-cta h2 {
    margin: 0;
    font-size: min(20px, calc(6.5vw - 5.6px));
    line-height: 1.3;
    white-space: nowrap;
  }
  .blog-cta p {
    display: none;
  }
  .blog-cta-link {
    justify-content: center;
    width: 100%;
    min-height: 36px;
    padding: 8px 16px;
    font-size: 14px;
    line-height: 1.15;
  }
  .blog-cta-link span {
    font-size: 13px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .blog-card,
  .blog-cover,
  .blog-cover-art,
  .blog-cta-link,
  .blog-faq summary::after {
    transition-duration: 0.01ms;
  }
  .blog-card:hover .blog-cover-art,
  .blog-card:hover .blog-cover,
  .blog-cta-link:hover,
  .blog-cta-link:hover span {
    transform: none;
  }
  .blog-progress {
    display: none;
  }
}
/* ---------- News index: hero, release feed, living topics ----------
   The index reads as the dated changelog it always was, but every change now
   links to the article that explains it in full. */
.news-article-hero {
  max-width: 820px;
  padding: 10px 0 42px;
}
.news-article-hero h1 {
  max-width: var(--headline-measure);
  margin: 0 0 16px;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: var(--headline-index);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.034em;
  text-wrap: balance;
}
/* The index hero is a centred title over a thin stat strip - the list below
   carries the content. */
.news-index-hero {
  max-width: none;
  padding: 6px 0 0;
  text-align: center;
}
.news-index-hero h1 {
  max-width: var(--headline-measure-centred);
  margin-inline: auto;
}
/* The strip is a caption on the headline, not a band of its own, so it sits in
   an even band: 24px of box above and 24px below. Measured as ink rather than
   as boxes that lands within ~2px of the blog index's own meta strip, which
   reads 16px under a line of 1.6 body copy - the headline's tight 1.1 leading
   gives the strip none of that half-leading back, and the rule under the
   wordmark spends a few more pixels of it. The margin collapses with the
   headline's own 16px rather than adding to it, so it carries the gap alone. */
.news-index-hero .news-hero-stat {
  justify-content: center;
  margin-top: 24px;
}
/* And 24px below it, the same as the blog index leaves between its meta strip
   and the first cover plate: the strip is optically centred in its band because
   the card below is a hard edge carrying no leading of its own. Scoped to the
   index so the related list at the foot of an article keeps its own rhythm. */
.news-index-hero + .news-list {
  margin-top: 24px;
}
/* The marked word in the index headline is the brand itself, so it is set in
   the wordmark face from the nav logo (Unbounded 700) instead of the headline
   face. Unbounded runs wide and tall for its size and carries its own tracking,
   so at a flat 1em beside Space Grotesk it reads as a size jump rather than a
   logo - it is stepped down to match the headline's cap height. */
#news-title .title-accent {
  font-family: 'Unbounded', 'Space Grotesk', Inter, sans-serif;
  font-weight: 700;
  font-size: 0.88em;
  letter-spacing: -0.015em;
}
/* Unbounded's rounder, shallower descenders leave the rule looking tight at the
   shared offset, so this one line gets a touch more air. */
#news-title .title-accent--underline {
  text-underline-offset: 0.21em;
}
.news-article-lede {
  max-width: var(--dek-measure);
  margin: 0;
  color: var(--muted);
  font-size: clamp(16.5px, 1.8vw, 19px);
  line-height: 1.65;
  text-wrap: balance;
}
.news-hero-stat {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 22px 0 0;
  color: var(--muted-soft);
  font-size: 12.5px;
  font-weight: 600;
}
.news-hero-stat span {
  display: inline-flex;
  align-items: center;
  gap: 16px;
}
.news-hero-stat span + span::before {
  content: '';
  width: 3px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: currentColor;
  opacity: 0.55;
}

/* Tag chips: the change type stays readable at a glance. The chip sits inline
   with the count and the date, so it is sized to that line rather than to
   itself - colour alone carries the type, no leading dot. */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.tag-new {
  color: #d2ccff;
  background: rgba(150, 139, 255, 0.16);
  border-color: rgba(150, 139, 255, 0.4);
}
.tag-improved {
  color: #9ff0d4;
  background: rgba(52, 211, 153, 0.14);
  border-color: rgba(52, 211, 153, 0.36);
}
.tag-fixed {
  color: #f8d98f;
  background: rgba(248, 214, 109, 0.14);
  border-color: rgba(248, 214, 109, 0.36);
}

/* ---------- Topic cover plates ----------
   Nothing on this site is stock photography, so a cover is drawn: the topic's
   own emblem on a glass chip over a brand-tinted plate, the topic's name under
   it, and the wordmark small in the corner. The emblem is what makes one topic
   recognisable at a glance - a wall of identical wordmarks was a wall of
   nothing - and build-news paints it as inline SVG, so there is no image to
   load and nothing to go stale. One recipe with a single hue knob, so a new
   tone is one number, and the emblem's accent follows that knob rather than
   naming a colour of its own. Everything inside is sized in cqi, so the same
   markup reads correctly as a card thumbnail, an article lead, and a
   related-list tile. */
.news-cover {
  --h: 268;
  container-type: inline-size;
  display: block;
  overflow: hidden;
  border: 1px solid var(--border);
  /* The same radius and lift as a blog cover: the two walls of cards sit on
     one measure and one grid, so they are one shape as well. */
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  transition:
    border-color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}
.news-cover[data-tone='violet'] {
  --h: 266;
}
.news-cover[data-tone='indigo'] {
  --h: 250;
}
.news-cover[data-tone='plum'] {
  --h: 286;
}
.news-cover[data-tone='ink'] {
  --h: 238;
}
.news-cover[data-tone='orchid'] {
  --h: 298;
}
.news-cover[data-tone='steel'] {
  --h: 228;
}
.news-cover-plate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3.8cqi;
  aspect-ratio: 16 / 9;
  padding: 6% 8%;
  text-align: center;
  /* The first layer is the light the emblem sits in, so it follows the chip
     rather than the plate: a layout that moves the chip moves this with it. */
  background:
    radial-gradient(38% 38% at var(--glow-x, 50%) var(--glow-y, 40%), hsl(var(--h) 92% 68% / 0.3), transparent 70%),
    radial-gradient(78% 100% at 14% -18%, hsl(var(--h) 70% 56% / 0.32), transparent 62%),
    radial-gradient(86% 108% at 104% 116%, hsl(calc(var(--h) - 14) 76% 40% / 0.34), transparent 58%),
    linear-gradient(128deg, hsl(var(--h) 48% 22%), hsl(calc(var(--h) - 6) 52% 13%) 54%, hsl(calc(var(--h) - 12) 46% 7%));
}
/* Faint drafting grid, faded out behind the words. */
.news-cover-plate::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.055) 1px, transparent 1px);
  background-size: 11cqi 11cqi;
  -webkit-mask-image: radial-gradient(74% 78% at 50% 34%, #000, transparent 76%);
  mask-image: radial-gradient(74% 78% at 50% 34%, #000, transparent 76%);
}
.news-cover-plate::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(122% 100% at 50% 30%, transparent 44%, rgba(6, 3, 20, 0.52));
}
.news-cover-line,
.news-cover-mark {
  position: relative;
  z-index: 1;
  max-width: 100%;
}
/* The emblem sits on a chip rather than loose on the plate: the glass edge
   gives the mark a body of its own, so it reads as an object on the tile
   instead of a sticker printed onto the gradient. */
.news-cover-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: clamp(38px, 17cqi, 72px);
  aspect-ratio: 1;
  border-radius: 30%;
  background:
    linear-gradient(152deg, hsl(var(--h) 96% 78% / 0.26), hsl(var(--h) 90% 60% / 0.05) 58%, rgba(255, 255, 255, 0.05));
  border: 1px solid rgba(255, 255, 255, 0.17);
  box-shadow:
    0 14px 34px -16px rgba(6, 3, 20, 0.95),
    inset 0 1px 0 rgba(255, 255, 255, 0.26);
  /* The chip keeps a compositing layer of its own at rest. Hovering a card used
     to promote the whole card in one go, and entering that state re-snapped the
     emblem's hairlines to whole device pixels - read as the icon sliding a
     couple of pixels sideways while the words under it stayed put. With the
     layer already up, the card's lift moves the chip's finished raster instead
     of redrawing the mark. */
  will-change: transform;
  transition:
    transform var(--dur-mid) var(--ease-out),
    border-color var(--dur-mid) var(--ease-out),
    box-shadow var(--dur-mid) var(--ease-out);
}
/* The lit state of the glass, carried on a layer of its own so hover crossfades
   into it: a gradient cannot be transitioned to another gradient, and a filter
   would put the mark back through the offscreen surface this is avoiding. */
.news-cover-mark::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background:
    linear-gradient(152deg, hsl(var(--h) 96% 82% / 0.3), hsl(var(--h) 92% 64% / 0.09) 58%, rgba(255, 255, 255, 0.07));
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-out);
}
.news-cover-art {
  position: relative;
  z-index: 1;
  display: block;
  width: 68%;
  height: 68%;
  overflow: visible;
}
/* The emblem's own inks. The accent rides the plate's hue knob, so a mark
   tints with its tone without naming a colour in the markup. */
.news-cover .nc-line {
  stroke: rgba(255, 255, 255, 0.86);
}
.news-cover .nc-dim {
  stroke: rgba(255, 255, 255, 0.4);
}
.news-cover .nc-fill {
  fill: rgba(255, 255, 255, 0.9);
}
.news-cover .nc-dimfill {
  fill: rgba(255, 255, 255, 0.32);
}
.news-cover .nc-key {
  stroke: hsl(var(--h) 96% 80%);
}
.news-cover .nc-keyfill {
  fill: hsl(var(--h) 96% 80%);
}
/* The one fill that has to hide what is behind it: a shape drawn over another
   shape in the same mark, in the plate's own dark rather than in black. */
.news-cover .nc-plate {
  fill: hsl(calc(var(--h) - 8) 44% 10% / 0.88);
}
.news-cover-line {
  display: block;
  color: #fff;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: 15px;
  font-size: clamp(11px, 5.2cqi, 22px);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.016em;
  text-shadow: 0 1px 16px rgba(10, 2, 38, 0.5);
  text-wrap: balance;
}
/* The article lead sits under the headline, so it is a letterbox rather than
   a full card: it runs the full column the headline and the prose run, shallow
   enough that the copy under it stays in view. At that shape the plate turns on
   its side - emblem first, then the topic - because a stack centred in a 16/5
   band leaves the mark nowhere to breathe. Emblem and topic set as one block in
   the middle of the band rather than pinned to the left edge, because a band
   this wide left with the pair on the left reads as a half-empty plate. */
.news-cover-lead {
  max-width: none;
  margin: 0;
  border-radius: var(--radius-lg);
}
.news-cover-lead .news-cover-plate {
  --glow-x: 50%;
  --glow-y: 50%;
  flex-direction: row;
  justify-content: center;
  gap: 4cqi;
  aspect-ratio: 16 / 5;
  padding: 4% 6%;
  text-align: left;
}
.news-cover-lead .news-cover-mark {
  width: clamp(56px, 14cqi, 124px);
}
.news-cover-lead .news-cover-line {
  max-width: 62%;
  font-size: clamp(15px, 3.4cqi, 30px);
}

/* ---------- News index list ----------
   One card per topic: its name, its cover, then the line that says what kind of
   change it was and when. The article behind it carries the explanation. */
.news-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(42px, 4.6vw, 60px) 20px;
  margin: 40px 0 0;
  padding: 0;
  list-style: none;
}
/* The same column count at the same widths as the blog index (.blog-list): both
   walls run on the 1060px measure, so a topic cover and a post cover are the
   same picture size at every viewport rather than only at the widest one.
   Three across only once the grid is wide enough to carry them - below ~1120px
   a third column costs the name more size than a second column costs the wall. */
@media (max-width: 1120px) {
  .news-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 760px) {
  .news-list {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* The cover, the name and the meta line each take a row of the list's own grid,
   so a two-line name in one card never pushes that card's meta line below its
   neighbours': every cover in a row of cards starts on the same line, so does
   every name, and so does every rule under them. Two levels of subgrid, because
   the row is a list item and the card inside it is the link. */
.news-entry {
  min-width: 0;
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  row-gap: 12px;
}
.news-entry-link {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  row-gap: 12px;
  color: var(--text);
  border-radius: var(--radius);
  transition: transform var(--dur-base) var(--ease-out);
}
.news-entry-latest .news-cover {
  border-color: rgba(150, 139, 255, 0.34);
}
.news-entry-link:hover {
  text-decoration: none;
}
/* The lift belongs to the picture, not to the caption under it. Moving the
   whole link carried the name and the meta line off the baselines they share
   with the cards beside them, so a pointer crossing the wall nudged three rows
   of type at a time. The plate rises on its own now; the words stay put. */
.news-entry-link:hover .news-cover {
  border-color: var(--line-accent);
  transform: translateY(-2px);
}
/* The emblem lifts off its plate with the card and the glass catches more of
   the plate's own hue, where the blog cover's art grows into its frame: the
   same gesture, told with the piece each cover actually has. The lift used to
   carry a scale and half a pixel with it, which redrew a 4px-stroke mark at
   1.035 on a fractional edge - the sideways jump. Now every part of the gesture
   is either a whole-pixel move on the chip's own layer or a paint, so none of
   the emblem's ink is resampled. */
.news-entry-link:hover .news-cover-mark {
  transform: translateY(-3px);
  border-color: rgba(255, 255, 255, 0.3);
  box-shadow:
    0 20px 40px -18px rgba(6, 3, 20, 0.98),
    0 0 22px -6px hsl(var(--h) 96% 72% / 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.34);
}
.news-entry-link:hover .news-cover-mark::after {
  opacity: 1;
}
.news-entry-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
}
.news-entry-title {
  margin: 0;
  /* Names run to different lengths, so they hang from the head of their row and
     sit right under the plate they name, letting the shorter ones leave their
     air above the rule instead of under the cover. */
  align-self: start;
  color: var(--text);
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: 19.5px;
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.026em;
  /* Cards are narrow, so anything that evens the lines out leaves them all half
     empty. Greedy wrapping runs each line to the full column. */
  text-wrap: wrap;
}
/* One meta line closes the card and reads badge, then how much changed, then
   when, then the way in, all trailing one another behind middle dots: the tag is
   the headline of the row, so it leads and the rest follows in reading order. It
   holds one line for as long as it can - a strip this short reads as a single
   caption - so it tightens first and only lets its last part fall below once no
   amount of tightening fits it. It measures itself against the card rather than
   against the window, so every card compresses at the same point, and it hangs
   from the head of its row so the rule above it lands on one line across a whole
   row of cards.

   The rule is what makes it read as small print rather than as another line of
   the card: a hairline the full width of the card with an accent length lit at
   its left, painted as two background layers rather than drawn on a pseudo, since
   the dots between the parts of the row are already ::before on its children and
   a pseudo here would take the first slot and give the tag a dot of its own. The
   lit length runs the width of the card on hover, which is the one part of the
   card that moves besides the lift itself. */
.news-entry-meta {
  display: flex;
  /* Wrapping is the safety net under the two tightening steps below, and it has
     to be set here rather than in a container query: a container never answers
     a query about its own properties, and this element is the container. */
  flex-wrap: wrap;
  row-gap: 5px;
  align-items: center;
  align-self: start;
  container: news-card / inline-size;
  padding-top: 11px;
  background-image:
    linear-gradient(90deg, var(--line-accent), rgba(150, 139, 255, 0)),
    linear-gradient(90deg, var(--border), var(--border));
  background-repeat: no-repeat;
  background-position: 0 0, 0 0;
  background-size: 38% 1px, 100% 1px;
  color: var(--muted-soft);
  font-size: 12.5px;
  font-weight: 600;
  transition: background-size var(--dur-mid) var(--ease-out);
}
.news-entry-link:hover .news-entry-meta {
  background-size: 100% 1px, 100% 1px;
}
.news-entry-meta > * {
  white-space: nowrap;
}
/* The whole spacing of the row hangs off the dots rather than off a flex gap.
   A container only ever answers queries for what is inside it, never for its own
   properties, so a gap set here could not be tightened as the card narrows,
   where a margin on the dot can. */
.news-entry-meta > * + *::before {
  content: '\00b7';
  margin: 0 8px;
  color: var(--muted-soft);
  opacity: 0.5;
}
.news-entry-go {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 7px;
  color: var(--accent-bright);
  font-weight: 700;
}
/* The row earns its width back in two steps as the card narrows, and takes both
   steps in every card at once rather than letting one card break while the card
   beside it stays whole. First, anywhere under the 340px card the three-up grid
   hands out on a desktop, it closes the gaps either side of the dots and drops
   half a point of type: neither costs the line anything legible, and that alone
   carries the longest row the page can produce - a long tag, a two-digit count
   and a long month - across every phone. */
@container news-card (max-width: 339px) {
  .news-entry-meta > * + *::before {
    margin: 0 5px;
  }
  .news-entry-count,
  .news-entry-meta time,
  .news-entry-go {
    font-size: 12px;
  }
  .news-entry-go {
    gap: 5px;
  }
  .news-entry-meta .tag {
    padding: 1px 6px;
    font-size: 9.5px;
  }
}
/* Then, on the narrowest cards the layout ever reaches - one column on a small
   phone - the same row keeps its shape and tightens once more. The way in stays
   where a reader meets it on a desktop: last in the row, behind its own dot,
   trailing the date rather than pushed off to the right edge on a line of its
   own. The longest row the page can produce - a long tag, a two-digit count and
   a long month - still fits a card on a 320px phone at these margins, and the
   wrap on the row stays under it as the safety net. */
@container news-card (max-width: 300px) {
  .news-entry-meta > * + *::before {
    margin: 0 3px;
  }
  .news-entry-count,
  .news-entry-meta time,
  .news-entry-go {
    font-size: 11.5px;
  }
  .news-entry-go {
    gap: 4px;
  }
  .news-entry-meta .tag {
    padding: 1px 5px;
    font-size: 9px;
  }
}
.news-entry-go span,
.article-cta-link span {
  transition: transform var(--dur-base) var(--ease-out);
}
.news-entry-link:hover .news-entry-go span,
.article-cta-link:hover span {
  transform: translateX(2px);
}
.article-cta-link:focus-visible,
.article-breadcrumb a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.article-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  color: var(--muted-soft);
  font-size: 12px;
  font-weight: 600;
}
/* The update date is written twice, long and short. The wide strip has room for
   the spelled-out month and shows it; the phone strip swaps to the abbreviation,
   which is the width it needs to keep the whole caption on one line. */
.article-date-short {
  display: none;
}
.article-category {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  padding: 4px 10px;
  color: var(--accent-bright);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1.4;
  text-transform: uppercase;
}

/* ---------- Individual article ---------- */
/* Same measure as a blog post, so both article types sit on the same margins. */
.page.article-page {
  --article-head-top: 38px;
  max-width: 1060px;
  padding-top: var(--article-head-top);
}
.article-breadcrumb {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 48px;
  color: var(--muted-soft);
  font-size: 12.5px;
  font-weight: 600;
}
.article-breadcrumb a {
  padding: 2px 4px;
  color: var(--muted);
  border-radius: var(--radius-sm);
}
.article-hero {
  max-width: none;
  margin-bottom: 38px;
}
.article-meta > span:not(.article-category) + span:not(.article-category)::before {
  content: '·';
  margin-right: 14px;
  color: var(--muted-soft);
}
/* Desktop: the meta line has room to ride up onto the breadcrumb row and range
   right, so the headline follows the crumb trail directly instead of being held
   off by a near-empty line. Source order is reversed on the way out, which puts
   the category pill hard against the right edge, the date inside it, and the
   read time innermost. */
@media (min-width: 900px) {
  .article-page .article-breadcrumb {
    /* Hold the pill's height so the crumbs centre against it, not against the
       taller row it now shares. */
    min-height: 26px;
  }
  .article-page .article-hero .article-meta {
    position: absolute;
    top: var(--article-head-top);
    right: 0;
    flex-direction: row-reverse;
    flex-wrap: nowrap;
    white-space: nowrap;
  }
  /* The dot has to change sides with the row: it marks the gap that now falls
     after the read time rather than before it. */
  .article-page .article-hero .article-meta > span:not(.article-category) + span:not(.article-category)::before {
    content: none;
  }
  .article-page .article-hero .article-meta > span:not(.article-category) + span:not(.article-category)::after {
    content: '·';
    margin-left: 14px;
    color: var(--muted-soft);
  }
}
/* Ranged left against the same hard column edge as the body copy, so it sets
   like a printed headline: every line runs to the edge of the column and only
   the last one is short. `balance` evened the two halves out by pulling the
   first line in, which left a dead gutter down the right of the hero while the
   prose underneath ran full width; `pretty` keeps the widow control and gives
   that space back. Matches .blog-post-hero h1. */
.article-hero h1 {
  max-width: none;
  margin: 20px 0 16px;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: var(--headline);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.035em;
  text-wrap: pretty;
}
.article-takeaways {
  margin: 42px 0 58px;
  padding: 26px 30px;
  background: linear-gradient(135deg, var(--accent-soft), transparent 70%), var(--surface);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
/* Same treatment as .blog-takeaways-label - see the note there. */
.article-takeaways-label {
  margin: 0 0 15px;
  padding-bottom: 13px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: 17.5px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.022em;
}
.article-takeaways ul {
  display: grid;
  gap: 9px;
  margin: 0;
  padding-left: 22px;
}
.article-takeaways li {
  padding-left: 4px;
  color: rgba(245, 241, 232, 0.88);
  font-size: 15px;
  line-height: 1.65;
}
.article-takeaways li::marker {
  color: var(--accent);
}
/* The prose runs the whole column, like a blog post - no narrow rail with a
   dead gutter down the right of the page. */
.article-body {
  max-width: none;
}
.article-body > section {
  margin-top: 50px;
}
.article-body > section:first-child {
  margin-top: 0;
}
.article-body h2,
.article-cta h2,
.related-articles h2 {
  margin: 0 0 14px;
  color: var(--text);
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-size: clamp(27px, 3.4vw, 36px);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.032em;
  text-wrap: balance;
}
.article-body p {
  margin: 0 0 18px;
  color: rgba(245, 241, 232, 0.74);
  font-size: 16.5px;
  line-height: 1.82;
}

/* Numbered how-to: the practical half of every article. */
.howto-steps {
  display: grid;
  gap: 10px;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: howto;
}
.howto-steps li {
  position: relative;
  padding: 18px 22px 18px 64px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  counter-increment: howto;
}
.howto-steps li::before {
  content: counter(howto);
  position: absolute;
  top: 50%;
  left: 22px;
  transform: translateY(-50%);
  display: grid;
  width: 26px;
  height: 26px;
  place-items: center;
  color: var(--accent-bright);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  font-weight: 800;
}
.howto-steps h3 {
  margin: 2px 0 6px;
  color: var(--text);
  font-size: 16px;
  line-height: 1.35;
}
.howto-steps p {
  margin: 0;
  font-size: 15px;
  line-height: 1.72;
}
/* Change log: a timeline, not a table. One column against a hairline rail -
   the date and the type chip lead the entry as a single caption line, and the
   change itself runs under them at full measure. The rail's node marks where
   each entry starts and the newest one is lit, so the eye lands on the most
   recent change first. The old version spent a 124px column on a date, left
   the chip stranded beneath it, and ruled every row off with a border; the
   rail carries that same structure in one pixel. */
.article-history {
  padding-top: 46px;
  border-top: 1px solid var(--border);
}
.article-history ol {
  display: grid;
  gap: 30px;
  margin: 26px 0 0;
  padding: 0 0 0 30px;
  list-style: none;
}
.article-history li {
  position: relative;
}
/* The node sits on the caption line's centre: the chip makes that line 19px
   tall, so an 11px node starts 4px down and reads as the bullet of the date
   rather than as a mark of its own. */
.article-history li::before {
  content: '';
  position: absolute;
  top: 4px;
  left: -30px;
  width: 11px;
  height: 11px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
}
/* The newest change is the one the page is about, so its node is lit. */
.article-history li:first-child::before {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
/* Each item draws the rail down to the NEXT node rather than the list drawing
   one long line: the segment then ends exactly where the last node is, with no
   tail hanging off the bottom of the list. */
.article-history li::after {
  content: '';
  position: absolute;
  top: 17px;
  bottom: -33px;
  left: -25px;
  width: 1px;
  background: var(--border);
}
.article-history li:last-child::after {
  content: none;
}
.article-history-when {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin-bottom: 8px;
  line-height: 17px;
}
.article-history time {
  color: var(--muted-soft);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.article-history li:first-child time {
  color: var(--muted);
}
.article-history h3 {
  margin: 0 0 7px;
  color: var(--text);
  font-size: 17px;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -0.012em;
}
.article-history li p {
  margin: 0;
  max-width: 64ch;
  color: rgba(245, 241, 232, 0.7);
  font-size: 15px;
  line-height: 1.72;
}
.article-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin-top: 76px;
  padding: 22px 26px;
  background: radial-gradient(90% 150% at 100% 0%, var(--accent-soft), transparent 60%), var(--surface);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
/* The text block is the measuring frame for its own headline, so the headline
   sizes itself against the width actually left beside the button rather than
   against the whole card or the viewport. `flex: 1` is what gives it that
   width: inline-size containment zeroes its intrinsic contribution, so it takes
   whatever the row has spare. */
.article-cta > div {
  flex: 1 1 auto;
  min-width: 0;
  container-type: inline-size;
}
.article-cta h2 {
  /* Always one line. This headline needs about 19.5x its font size to sit on
     one, so 5cqi is the largest size that still fits its box - capped at the
     26px the card was designed around. */
  margin-bottom: 7px;
  font-size: min(26px, 5cqi);
  white-space: nowrap;
}
.article-cta p:not(.eyebrow) {
  max-width: 620px;
  margin: 0;
  color: var(--muted);
  font-size: 14px;
}
/* The same pill as the header's "Open app": same gradient, radius, glow and
   glassy top highlight, and the same lift on hover. Only the label differs. */
.article-cta-link {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 5px;
  padding: 6px 13px;
  color: #fff;
  background: linear-gradient(135deg, #b4a6ff 0%, #7c6cf6 100%);
  border-radius: 9px;
  box-shadow:
    0 6px 16px -8px rgba(124, 108, 246, 0.8),
    inset 0 1px 0 rgba(255, 255, 255, 0.32);
  font-size: 13px;
  font-weight: 700;
  transition: transform 160ms ease, box-shadow 220ms ease, filter 200ms ease;
}
.article-cta-link span {
  font-size: 12px;
  line-height: 1;
}
.article-cta-link:hover {
  color: #fff;
  text-decoration: none;
  transform: translateY(-1px);
  filter: brightness(1.07);
  box-shadow:
    0 14px 30px -10px rgba(124, 108, 246, 0.95),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
.related-articles {
  margin-top: 72px;
}
/* Three more topics, drawn the way the index draws them: same cover, same
   badge, count and date, same name and same way in. Only the spacing differs,
   because this wall hangs under a heading of its own rather than under the
   page title. Every rule stays scoped to a class - a bare descendant selector
   here used to reach into the cover and repaint its wordmark. */
.news-list-related {
  margin-top: 22px;
}

@media (prefers-reduced-motion: reduce) {
  .news-entry-link:hover .news-cover,
  .news-entry-link:hover .news-cover-mark {
    transform: none;
  }
  .news-entry-link,
  .news-cover,
  .news-cover-mark,
  .news-cover-mark::after,
  .news-entry-meta,
  .news-entry-go span,
  .article-cta-link,
  .article-cta-link span {
    transition-duration: 0.01ms;
  }
}

/* The column counts live with .news-list itself now, beside the blog's, so the
   two walls cannot drift apart again. */
@media (max-width: 880px) {
  .article-cta {
    align-items: flex-start;
    flex-direction: column;
    gap: 16px;
  }
  /* Column axis: flex-grow would stretch the block vertically, and a
     shrink-to-fit inline-size container would collapse to nothing. */
  .article-cta > div {
    flex: 0 0 auto;
    width: 100%;
  }
}

/* ---------- Credit costs ---------- */
/* One unified pricing sheet: every priced action is the same row, with the
   number always in the same right-hand column so the whole page scans in one
   vertical read. */
.page.credit-costs {
  max-width: 900px;
  /* The hero owns the whole gap above the headline so it matches the 30px gap
     under it; the page's own top padding would make it more than twice that. */
  padding-top: 0;
}
.cc-hero {
  max-width: 720px;
  margin: 0 auto;
  padding: 30px 0 14px;
  text-align: center;
}
.cc-hero h1 {
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-weight: 600;
  font-size: clamp(30px, 4.4vw, 44px);
  letter-spacing: -0.035em;
  text-wrap: balance;
}
.cc-hero .lede {
  max-width: 540px;
  margin: 0 auto;
  font-size: clamp(16px, 1.9vw, 19px);
  text-wrap: balance;
}
.cc-hero-note {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 16px;
  padding: 8px 15px;
  color: rgba(245, 241, 232, 0.78);
  background: rgba(150, 139, 255, 0.08);
  border: 1px solid rgba(150, 139, 255, 0.18);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
}
.cc-hero-note-icon {
  display: inline-grid;
  flex: 0 0 auto;
  width: 19px;
  height: 19px;
  place-items: center;
  color: #0c0e12;
  background: #a89eff;
  border-radius: 50%;
}
.cc-hero-note-icon svg {
  width: 12px;
  height: 12px;
}
.cc-toolbar {
  position: sticky;
  /* Parks a little below the sticky header (~61px) so the chips settle at a
     comfortable height instead of hugging the bar. */
  top: 90px;
  z-index: 5;
  display: flex;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  margin: 6px auto 26px;
  padding: 4px;
  background: rgba(18, 20, 25, 0.92);
  border: 1px solid var(--border);
  border-radius: 12px;
  backdrop-filter: blur(16px);
  box-shadow: 0 16px 40px -30px rgba(0, 0, 0, 0.95);
}
.cc-search {
  position: relative;
  display: flex;
  min-width: 0;
  flex: 1 1 220px;
  align-items: center;
}
.cc-search-ico {
  position: absolute;
  left: 12px;
  width: 16px;
  height: 16px;
  color: var(--muted-soft);
  pointer-events: none;
}
.cc-search input {
  width: 100%;
  padding: 9px 12px 9px 34px;
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: 10px;
  font: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color 150ms ease, background 150ms ease;
}
.cc-search input::placeholder {
  color: var(--muted-soft);
}
.cc-search input:focus {
  border-color: rgba(150, 139, 255, 0.5);
  background: rgba(150, 139, 255, 0.06);
}
.cc-search input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}
.cc-jump {
  display: flex;
  flex: 0 0 auto;
  justify-content: center;
  gap: 2px;
  max-width: 100%;
  overflow-x: auto;
}
.cc-jump a {
  padding: 6px 13px;
  color: var(--muted);
  border-radius: 8px;
  font-size: 12.5px;
  font-weight: 650;
  white-space: nowrap;
  transition: color 150ms ease, background 150ms ease;
}
.cc-jump a:hover {
  color: #fff;
  background: rgba(245, 241, 232, 0.07);
  text-decoration: none;
}
.cc-jump a.active {
  color: #fff;
  background: var(--accent-soft);
}
.cc-hidden {
  display: none !important;
}
.cc-empty {
  margin: 2px 0 28px;
  padding: 26px;
  color: var(--muted);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: 16px;
  font-size: 14px;
  text-align: center;
}
.cc-clear {
  margin-left: 4px;
  padding: 6px 12px;
  color: #c7c0ff;
  background: var(--accent-soft);
  border: 1px solid rgba(150, 139, 255, 0.24);
  border-radius: 8px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: color 150ms ease, background 150ms ease;
}
.cc-clear:hover {
  color: #fff;
  background: rgba(150, 139, 255, 0.28);
}

/* ---- Section (group) ---- */
.cc-group {
  /* Header + the parked chip bar + a breath, so a jumped-to section lands
     under the chips rather than behind them. */
  scroll-margin-top: 166px;
  margin: 0 0 28px;
}
.cc-head {
  display: flex;
  align-items: center;
  gap: 13px;
  margin-bottom: 11px;
}
.cc-icon {
  display: grid;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  place-items: center;
  color: #c4bcff;
  background: var(--accent-soft);
  border: 1px solid rgba(150, 139, 255, 0.24);
  border-radius: 11px;
}
.cc-icon svg {
  width: 20px;
  height: 20px;
}
.cc-icon.free {
  color: #77e6b2;
  background: rgba(72, 211, 148, 0.11);
  border-color: rgba(72, 211, 148, 0.22);
}
.cc-group h2 {
  margin: 0;
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-weight: 600;
  font-size: clamp(19px, 2.4vw, 24px);
  line-height: 1.2;
  letter-spacing: -0.02em;
}

/* ---- Table ---- */
.cc-table {
  width: 100%;
  border-collapse: collapse;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 20px 46px -40px rgba(0, 0, 0, 0.9);
}
.cc-table thead th {
  padding: 11px 22px;
  color: var(--muted-soft);
  background: rgba(255, 255, 255, 0.02);
  border-bottom: 1px solid var(--border);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-align: left;
}
.cc-table thead th.cc-col-cost {
  text-align: right;
}
.cc-table tbody td {
  padding: 14px 22px;
  border-top: 1px solid var(--border);
  vertical-align: middle;
  transition: background 140ms ease;
}
.cc-table tbody tr:first-child td {
  border-top: 0;
}
.cc-table tbody tr:hover td {
  background: rgba(255, 255, 255, 0.022);
}
.cc-feature {
  min-width: 0;
}
.cc-feature strong {
  display: block;
  color: var(--text);
  font-size: 15px;
  font-weight: 650;
  line-height: 1.3;
}
.cc-feat-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.cc-feat-head strong {
  display: inline;
}
.cc-sub {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}
.cc-tag {
  display: inline-flex;
  padding: 3px 8px;
  color: #c7c0ff;
  background: var(--accent-soft);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.cc-cost {
  width: 1%;
  text-align: right;
  white-space: nowrap;
}
.cc-cost b {
  color: #fff;
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.cc-cost span {
  display: block;
  margin-top: 5px;
  color: var(--muted-soft);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
}
.cc-unit-word {
  font-style: inherit;
}
.cc-cost--app b {
  display: inline-block;
  padding: 4px 11px;
  color: #c7c0ff;
  background: var(--accent-soft);
  border: 1px solid rgba(150, 139, 255, 0.22);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;
}
.cc-cost--app span {
  margin-top: 6px;
}

/* ---- Free actions ---- */
/* Two across, not four: the page column is capped at 900px, so a four card
   strip left each name and description in a ~190px gutter and both wrapped
   mid phrase. A 2x2 block gives every card a full line of its own. */
.cc-free-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  overflow: hidden;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 16px;
}
/* The tick closes the card instead of leading it: the text column comes first
   and the circle is parked on the right edge, centred against the card's full
   height. */
.cc-free-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  background: rgba(12, 14, 18, 0.96);
}
.cc-check {
  display: grid;
  /* Ordered past the text column rather than moved in the markup, so the tick
     still reads first for a screen reader. */
  order: 2;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  place-items: center;
  color: #8bedc0;
  background: rgba(72, 211, 148, 0.15);
  border: 1px solid rgba(72, 211, 148, 0.22);
  border-radius: 50%;
}
.cc-check svg {
  width: 12px;
  height: 12px;
}
.cc-free-item > div {
  flex: 1 1 auto;
  min-width: 0;
}
.cc-free-item strong {
  display: block;
  color: var(--text);
  font-size: 14px;
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -0.01em;
}
/* Block, with its own leading: as an inline it inherited the 1.65 page strut
   and a wrapped description fell far away from its own first line.
   Matched through the text column, not as any descendant span - the tick is a
   span too, and the loose selector outranked .cc-check, turning the circle
   into a block (so the glyph lost its centring) and nudging it down 4px. */
.cc-free-item > div > span {
  display: block;
  margin-top: 4px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.45;
  text-wrap: pretty;
}

@media (max-width: 620px) {
  .cc-hero {
    padding: 30px 0 16px;
  }
  .cc-hero h1 {
    font-size: clamp(31px, 10vw, 42px);
  }
  .cc-hero-note {
    align-items: flex-start;
    border-radius: 13px;
    text-align: left;
  }
  .cc-hero-note-icon {
    flex: 0 0 auto;
    margin-top: 1px;
  }
  /* Category chips: five equal cells that fill the bar exactly, so nothing is
     cut off or side-scrolls on a phone. */
  .cc-toolbar {
    /* Floats a little below the 55px-tall phone header so the chips read as
       their own bar instead of being welded to it. */
    top: 84px;
    width: 100%;
    margin-bottom: 22px;
    padding: 3px;
    border-radius: 13px;
  }
  /* The gap the chips now sit below would let rows slide past in the clear, so
     fill it with a fixed strip of the header's own glass that fades out at its
     bottom edge. It sits behind the chip bar (z-index 4 vs 5) inside .page's
     stacking context, so the two overlap without a seam. */
  .page.credit-costs::before {
    content: '';
    position: fixed;
    top: 55px;
    left: 0;
    right: 0;
    z-index: 4;
    height: 31px;
    background: linear-gradient(rgba(7, 8, 10, 0.88), rgba(7, 8, 10, 0));
    backdrop-filter: blur(10px);
    pointer-events: none;
  }
  .cc-jump {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 2px;
    width: 100%;
    overflow: visible;
  }
  .cc-jump a {
    padding: 8px 2px;
    font-size: 11.5px;
    letter-spacing: -0.005em;
    text-align: center;
  }
  .cc-group {
    scroll-margin-top: 152px;
    margin-bottom: 24px;
  }
  .cc-head {
    gap: 10px;
    margin-bottom: 9px;
  }
  .cc-icon {
    width: 32px;
    height: 32px;
    border-radius: 9px;
  }
  .cc-icon svg {
    width: 17px;
    height: 17px;
  }
  /* Rows become cards: name and price share the top line, the one-line
     description sits underneath and spans the whole card. The desktop table
     squeezed the name into one word per line here, because the price column
     never wraps. The feature cell unwraps (display: contents) so its title and
     description can be placed directly in the row's grid. */
  .cc-table {
    display: block;
    border-radius: 14px;
  }
  .cc-table thead {
    display: none;
  }
  .cc-table tbody {
    display: block;
  }
  .cc-table tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    column-gap: 12px;
    padding: 12px 14px;
    border-top: 1px solid var(--border);
  }
  .cc-table tbody tr:first-child {
    border-top: 0;
  }
  .cc-table tbody td {
    padding: 0;
    border-top: 0;
  }
  .cc-feature {
    display: contents;
  }
  .cc-feature > strong,
  .cc-feat-head {
    grid-column: 1;
    grid-row: 1;
  }
  .cc-feature strong {
    font-size: 14.5px;
  }
  .cc-sub {
    grid-column: 1;
    grid-row: 2;
    margin-top: 3px;
    font-size: 12.5px;
    line-height: 1.45;
  }
  /* The price stays on one line: value then unit, and it is centred against
     the whole card (title + description) instead of sitting on the top line. */
  .cc-cost {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
    display: flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 5px;
    width: auto;
    text-align: right;
  }
  .cc-cost b {
    font-size: 18px;
  }
  .cc-cost span {
    display: inline;
    margin: 0;
    font-size: 10px;
    letter-spacing: 0.02em;
  }
  /* Narrow rows only fit the value and its unit - the page is already all
     about credits, so the repeated word is dropped. */
  .cc-unit-word {
    display: none;
  }
  /* "Shown in app" / "Measured" rows keep the badge alone - the unit under it
     would push the name into a second line, and the description already says
     what is counted. */
  .cc-cost--app span {
    display: none;
  }
  .cc-cost--app b {
    padding: 3px 9px;
    font-size: 11px;
  }
  .cc-free-grid {
    grid-template-columns: 1fr;
  }
  .cc-free-item {
    padding: 12px 14px;
  }
}

/* ---------- Footer ---------- */
.site-footer {
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.3);
}
.footer-inner {
  /* Match the home footer's wide content column (capped, centred, home gutter)
     rather than the narrow page column. */
  width: min(1760px, calc(100% - var(--bar-gutter) - var(--bar-gutter)));
  margin: 0 auto;
  padding: 56px 0 28px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}
.footer-top {
  display: flex;
  justify-content: space-between;
  gap: 40px 64px;
  flex-wrap: wrap;
}
.footer-brand-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  max-width: 380px;
}
.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  text-decoration: none;
}
.footer-brand img {
  width: 34px;
  height: 34px;
  border-radius: 9px;
}
.footer-brand span {
  font-family: 'Unbounded', sans-serif;
  color: #fff;
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.015em;
}
.footer-desc {
  margin: 0;
  color: var(--muted-soft);
  font-size: 13.5px;
  line-height: 1.6;
}
.footer-cols {
  display: flex;
  gap: 48px;
  flex-wrap: wrap;
}
.footer-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  min-width: 116px;
}
.footer-col h4 {
  margin: 0 0 2px;
  color: var(--muted-soft);
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.footer-col a {
  color: rgba(245, 241, 232, 0.78);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: color 150ms ease;
}
.footer-col a:hover {
  color: #fff;
}
.footer-socials {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 2px;
}
.footer-social {
  display: inline-grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  color: var(--muted);
  border: 1px solid var(--border);
  background: rgba(245, 241, 232, 0.05);
  transition: color 160ms ease, background 180ms ease, border-color 160ms ease, transform 160ms ease;
}
.footer-social svg {
  width: 18px;
  height: 18px;
}
.footer-social:hover {
  transform: translateY(-2px);
  color: #fff;
}
.footer-social.x:hover {
  border-color: transparent;
  background: #000;
  color: #fff;
}
.footer-social.ig:hover {
  border-color: transparent;
  background: linear-gradient(45deg, #f09433, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888);
}
.footer-social.fb:hover {
  border-color: transparent;
  background: #1877f2;
}
.footer-social.tt:hover {
  border-color: transparent;
  background: #000;
  color: #fff;
  box-shadow: 0 0 0 1px rgba(37, 244, 238, 0.5), -3px 0 0 -1px rgba(254, 44, 85, 0.6);
}
.footer-social.yt:hover {
  border-color: transparent;
  background: #ff0000;
}
.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}
.footer-copy {
  color: var(--muted-soft);
  font-size: 12.5px;
  letter-spacing: 0.02em;
}
.footer-note {
  color: var(--muted-soft);
  font-size: 12.5px;
}

/* Small tablets: the bar now carries seven links (Help/Costs/News/Blog/Privacy/
   Terms/Data). Tighten the tracking and the type a touch so the row still clears
   the brand and the CTA all the way down to the phone breakpoint below. */
@media (max-width: 780px) {
  .site-nav {
    gap: 14px;
  }
  .site-nav a {
    font-size: 12.75px;
  }
}

/* Declutter the header on phones: brand + "Open app" only. The cramped
   Help/News/Terms/Privacy row was unreadable on small screens, and the full
   navigation already lives in the footer. */
@media (max-width: 620px) {
  /* Hide the whole link group so its grid track collapses and the bar is just
     brand | CTA. */
  .site-nav {
    display: none;
  }
  .site-header .bar {
    grid-template-columns: auto 1fr;
  }
  .site-header .cta {
    font-size: 12px;
    padding: 5px 11px;
  }
}

@media (max-width: 560px) {
  /* Phones: keep a real side gutter so the brand and CTA don't jam into the
     screen edges, and shrink the wordmark + icon so the header isn't oversized. */
  .site-header .bar {
    padding: 10px 16px;
    min-height: 54px;
    gap: 12px;
  }
  .brand-icon {
    width: 24px;
    height: 24px;
  }
  .brand span {
    font-size: 17px;
  }
  /* Comfortable reading gutters + a tighter top so content starts sooner. */
  .page {
    padding: 32px 0 72px;
  }
  h1 {
    font-size: clamp(27px, 8.5vw, 34px);
  }
  .lede {
    font-size: 16px;
  }
  .legal h2 {
    margin-top: 36px;
  }
  .toc ol {
    columns: 1;
  }
  /* Help + search */
  .search-wrap input {
    padding: 13px 15px;
  }
  /* ----- Help page: make it short + scannable on phones ----- */
  /* Tighten the hero so content starts sooner. */
  .help-hero {
    padding: 4px 0 2px;
  }
  .help-hero h1 {
    margin-bottom: 8px;
  }
  .help-hero .lede {
    font-size: 14.5px;
    line-height: 1.5;
  }
  .search-wrap {
    margin: 16px auto 4px;
  }
  /* Topic browser: a compact 2-up tile grid (icon + title) instead of ten tall
     full-width cards, so "browse by topic" fits on roughly two thumb-scrolls. The
     one-line description is dropped here - the emoji + title carry the meaning. */
  .cat-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    margin: 20px 0 34px;
  }
  .cat-card {
    padding: 13px 12px;
  }
  .cat-card .ic {
    font-size: 21px;
    line-height: 1;
    margin-bottom: 7px;
  }
  .cat-card .t {
    font-size: 14px;
    line-height: 1.2;
    margin: 0;
  }
  .cat-card .d {
    display: none;
  }
  /* Tighter FAQ rhythm so the long answer list reads as clean, distinct sections
     rather than one endless wall. */
  .faq-section {
    margin-top: 28px;
  }
  .faq-section > h2 {
    font-size: 19px;
    margin-bottom: 8px;
    padding-bottom: 8px;
  }
  details.faq {
    margin: 8px 0;
  }
  details.faq summary {
    padding: 13px 15px;
    font-size: 14.5px;
    gap: 10px;
  }
  details.faq .body {
    padding: 0 15px 15px;
    font-size: 14.5px;
  }
  /* News articles: preserve a generous reading rhythm without oversized cards. */
  .news-article-hero {
    padding: 6px 0 24px;
  }
  .news-article-hero h1,
  .article-hero h1,
  .blog-hero h1,
  .blog-post-hero h1 {
    font-size: clamp(24px, 6.6vw, 30px);
  }
  .news-article-lede {
    font-size: 16px;
    line-height: 1.6;
  }
  /* Badge, date, and read time are one caption, not three facts stacked down the
     page, so the strip holds a single line at every phone width. It can only do
     that by sizing itself to the room it has: the strip becomes its own
     container, and its type, gap, and separator all measure against that width,
     so the row tightens to fit instead of overflowing or breaking. Everything
     holds full size until the column drops under ~350px and only shrinks below
     it. The date turns short here for the same reason - a spelled-out month
     spends width this row cannot spare. */
  .article-meta {
    container-type: inline-size;
    flex-wrap: nowrap;
    align-items: center;
    column-gap: min(12px, 1.8cqw);
    white-space: nowrap;
  }
  .article-meta > span {
    font-size: min(12px, 3.3cqw);
  }
  .article-date-long {
    display: none;
  }
  .article-date-short {
    display: inline;
  }
  .article-meta > .article-category {
    padding: 0.3em 0.68em;
    font-size: min(11px, 2.75cqw);
    letter-spacing: 0.05em;
  }
  .article-meta > span:not(.article-category) + span:not(.article-category)::before {
    margin-right: min(14px, 1.8cqw);
  }
  /* The stat strip is three short facts, not a sentence: it holds one line and
     tightens its separators rather than wrapping the date onto its own row. */
  .news-index-hero {
    padding-bottom: 0;
  }
  .news-hero-stat,
  .news-index-hero .news-hero-stat {
    flex-wrap: nowrap;
    gap: 0 10px;
    margin-top: 16px;
    white-space: nowrap;
  }
  .news-hero-stat span {
    gap: 10px;
  }
  /* Index list: the row stacks so a long name never squeezes. The gap grows
     past the desktop sliver: stacked full-width cards need clear air between
     one topic's meta line and the next topic's cover. */
  .news-list {
    grid-template-columns: minmax(0, 1fr);
    gap: 36px;
    margin-top: 16px;
  }
  .news-entry,
  .news-entry-link {
    row-gap: 11px;
  }
  .news-entry-title {
    font-size: 18px;
  }
  .news-cover-lead .news-cover-plate,
  .blog-cover-lead {
    aspect-ratio: 5 / 2;
  }
  .howto-steps li {
    padding: 16px 18px 16px 56px;
  }
  .howto-steps li::before {
    left: 18px;
  }
  .page.article-page {
    --article-head-top: 26px;
  }
  .article-breadcrumb {
    margin-bottom: 32px;
    overflow: hidden;
    white-space: nowrap;
  }
  .article-breadcrumb span:last-child {
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .article-hero {
    margin-bottom: 28px;
  }
  .article-takeaways {
    margin: 30px 0 42px;
    padding: 22px;
  }
  .article-takeaways li,
  .article-body p {
    font-size: 15.5px;
  }
  .article-body > section {
    margin-top: 40px;
  }
  /* Phone: the same rail, pulled in - the entry column is what needs the
     width here, so the indent drops and the node and segment follow it. */
  .article-history {
    padding-top: 38px;
  }
  .article-history ol {
    gap: 26px;
    padding-left: 24px;
  }
  .article-history li::before {
    left: -24px;
  }
  .article-history li::after {
    bottom: -29px;
    left: -19px;
  }
  .article-history h3 {
    font-size: 16px;
  }
  /* Phone: the card carries the headline and the way in, nothing else. The
     supporting sentence repeats what the piece has just said and cost four
     wrapped lines above the button, so it is dropped rather than squeezed. */
  .article-cta {
    gap: 18px;
    margin-top: 54px;
    padding: 22px 20px;
  }
  .article-cta p:not(.eyebrow) {
    display: none;
  }
  .article-cta h2 {
    margin-bottom: 0;
  }
  .article-cta > div {
    width: 100%;
  }
  /* Full-width tap target, sized for a thumb rather than for a row it no
     longer shares. The cap keeps it a button on a wide handset instead of a
     stretched bar the width of the page. */
  .article-cta-link {
    justify-content: center;
    width: 100%;
    max-width: 360px;
    padding: 9px 16px;
    font-size: 13.5px;
  }
  .related-articles {
    margin-top: 54px;
  }
  /* ===== Footer: phone layout mirrors the homepage footer =====
     Left-aligned: the logo and the social row share the top line, the
     description is dropped, and the three link columns line up side by side. */
  .footer-inner {
    width: calc(100% - 44px);
    padding: 30px 0 28px;
    align-items: stretch;
    text-align: left;
    gap: 28px;
  }
  .footer-top {
    flex-direction: column;
    align-items: stretch;
    gap: 24px;
  }
  /* Brand row: logo left, social icons right; description spans the row below. */
  .footer-brand-col {
    max-width: none;
    width: 100%;
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    column-gap: 12px;
    row-gap: 16px;
  }
  .footer-brand {
    grid-column: 1;
    grid-row: 1;
  }
  /* Drop the footer description on phones - the brand row + link columns carry
     the footer and this line only adds clutter at the bottom. */
  .footer-desc {
    display: none;
  }
  .footer-socials {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    margin-top: 0;
    /* The wordmark plus five 32px tiles filled a 375px brand row with no slack,
       so anything narrower pushed the last icons onto a second line. Lay them
       out as five columns that shrink below 32px instead of wrapping. */
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 32px));
    align-items: center;
    gap: 8px;
    justify-content: flex-end;
  }
  /* Keep the monochrome X-style tiles: subtle translucent base, white glyph. */
  .footer-social {
    width: 100%;
    height: auto;
    min-width: 0;
    aspect-ratio: 1 / 1;
    color: #fff;
    border-color: transparent;
    box-shadow: none;
  }
  /* Percentages so the glyphs keep their proportions when the tiles shrink. */
  .footer-social svg {
    width: 47%;
    height: 47%;
  }
  .footer-social.yt svg {
    width: 53%;
    height: 53%;
    overflow: visible;
  }
  /* Three link columns side by side. */
  .footer-cols {
    width: 100%;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 26px 16px;
  }
  .footer-col {
    min-width: 0;
  }
  .footer-col a {
    font-size: 13px;
  }
  .footer-bottom {
    justify-content: center;
    text-align: center;
  }
  .footer-copy {
    width: 100%;
  }
  /* Drop the "From first idea to final publish" tagline on phones. */
  .footer-note {
    display: none;
  }
}
