/* webfetch.io design system
 *
 * Every color is a token, defined three times:
 *   :root                                        light, the base
 *   @media (prefers-color-scheme:dark) + guard    dark, when the OS asks for it
 *   :root[data-theme="dark"]                      dark, when the visitor chose it
 * The media block is guarded by :not([data-theme="light"]) so an explicit light
 * choice beats a dark OS. No color is ever defined only inside one of those
 * blocks, which is the mistake that leaves an element unstyled in one theme.
 */

:root {
  color-scheme: light;

  --bg:        #f7f7f5;
  --bg-alt:    #eae9e4;
  --panel:     #ffffff;
  --panel-2:   #f4f4f1;
  --rule:      #dedeD7;
  --rule-2:    #ebebe6;

  --ink:       #14181d;
  --ink-2:     #4b535c;
  --ink-3:     #6d757e;

  --accent:      #b64d08;
  --accent-hi:   #d25c0c;
  --accent-ink:  #ffffff;
  --accent-soft: #fdf0e6;

  --violet:      #5b3cc4;
  --violet-soft: #efeaff;

  --ok:      #1a6f37;
  --ok-soft: #e6f4ea;
  --warn:      #8a4f0d;
  --warn-soft: #fdf3e3;
  --danger:      #a81f18;
  --danger-soft: #fdeceb;

  --code-bg:   #f4f4f1;
  --code-ink:  #1b2027;
  --shadow:    0 1px 2px rgba(20,24,29,.06), 0 8px 24px rgba(20,24,29,.06);
  --shadow-sm: 0 1px 2px rgba(20,24,29,.08);

  --radius:    10px;
  --radius-sm: 6px;
  --maxw:      1120px;

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;

    --bg:        #0d1116;
    --bg-alt:    #11171e;
    --panel:     #141c24;
    --panel-2:   #1a232d;
    --rule:      #26323e;
    --rule-2:    #1d2630;

    --ink:       #e8ecef;
    --ink-2:     #9aa6b2;
    --ink-3:     #7b8894;

    --accent:      #f0862b;
    --accent-hi:   #ff9a48;
    --accent-ink:  #14181d;
    --accent-soft: #2a1c11;

    --violet:      #9d86ff;
    --violet-soft: #1e1a33;

    --ok:      #4fb26e;
    --ok-soft: #10241a;
    --warn:      #f0a24b;
    --warn-soft: #2a2011;
    --danger:      #f2685f;
    --danger-soft: #2b1514;

    --code-bg:   #0f1620;
    --code-ink:  #dbe3ea;
    --shadow:    0 1px 2px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.35);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.5);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:        #0d1116;
  --bg-alt:    #11171e;
  --panel:     #141c24;
  --panel-2:   #1a232d;
  --rule:      #26323e;
  --rule-2:    #1d2630;

  --ink:       #e8ecef;
  --ink-2:     #9aa6b2;
  --ink-3:     #7b8894;

  --accent:      #f0862b;
  --accent-hi:   #ff9a48;
  --accent-ink:  #14181d;
  --accent-soft: #2a1c11;

  --violet:      #9d86ff;
  --violet-soft: #1e1a33;

  --ok:      #4fb26e;
  --ok-soft: #10241a;
  --warn:      #f0a24b;
  --warn-soft: #2a2011;
  --danger:      #f2685f;
  --danger-soft: #2b1514;

  --code-bg:   #0f1620;
  --code-ink:  #dbe3ea;
  --shadow:    0 1px 2px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.35);
  --shadow-sm: 0 1px 2px rgba(0,0,0,.5);
}

/* ---- Reset ---------------------------------------------------------------- */

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

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

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img, svg, video { max-width: 100%; height: auto; }
hr { border: 0; border-top: 1px solid var(--rule); margin: 2rem 0; }

a { color: var(--accent); text-decoration: none; text-underline-offset: 3px; }
a:hover { text-decoration: underline; }
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

h1, h2, h3, h4 { line-height: 1.25; margin: 0 0 .6em; font-weight: 650; letter-spacing: -.01em; }
h1 { font-size: clamp(1.9rem, 1.3rem + 2.4vw, 2.9rem); }
h2 { font-size: clamp(1.4rem, 1.1rem + 1.2vw, 1.9rem); }
h3 { font-size: 1.15rem; }
p  { margin: 0 0 1rem; }
ul, ol { margin: 0 0 1rem; padding-left: 1.3rem; }
li { margin: .3rem 0; }
small { font-size: .85rem; }

code, kbd, samp, pre { font-family: var(--mono); font-size: .9em; }
code {
  background: var(--code-bg);
  color: var(--code-ink);
  padding: .12em .38em;
  border-radius: 4px;
  border: 1px solid var(--rule-2);
}
pre {
  background: var(--code-bg);
  color: var(--code-ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  overflow-x: auto;
  margin: 0 0 1rem;
  line-height: 1.55;
}
pre code { background: none; border: 0; padding: 0; }

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--accent); color: var(--accent-ink);
  padding: .6rem 1rem; z-index: 100; border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

/* ---- Layout --------------------------------------------------------------- */

.wrap { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }
main  { flex: 1 0 auto; padding: 2.5rem 0 4rem; }
/* Top margin as well as bottom. With bottom-only, a .section following a .band
 * put its h2 hard against the band's bottom border: the band's own padding sits
 * inside the band, so nothing separated the two. Adjacent sections still read as
 * one gap because sibling margins collapse to the larger of the two -- but a
 * margin never collapses through a border, which is exactly why the band case
 * needed this and the section-to-section case never showed the fault. */
.section { margin: 3rem 0; }
/* Headings and their intro text run the full container width, matching the
   header and the tables and cards beneath them. A measure-limited lede next to a
   full-width table reads as a broken layout rather than as typography. */
.lede { font-size: 1.1rem; color: var(--ink-2); }
.muted { color: var(--ink-2); }
.dim   { color: var(--ink-3); }
.center { text-align: center; }
.stack > * + * { margin-top: 1rem; }
.row { display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; }
.grid { display: grid; gap: 1.1rem; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* ---- Header --------------------------------------------------------------- */

.site-header {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(8px);
  border-bottom: 1px solid var(--rule);
}
.nav { display: flex; align-items: center; gap: 1rem; height: 60px; }
.brand {
  display: inline-flex; align-items: center; gap: .5rem;
  font-weight: 700; font-size: 1.05rem; color: var(--ink); letter-spacing: -.02em;
}
.brand:hover { text-decoration: none; }
.brand-mark {
  width: 26px; height: 26px; flex: none;
  color: var(--accent);
}
.brand .tld { color: var(--ink-3); font-weight: 600; }
.nav-links { display: flex; gap: .25rem; margin-left: auto; align-items: center; }
/* The toggle sits before the links in the DOM so it stays in the bar when the
   menu collapses. Flex order puts it back on the right at desktop width, where
   otherwise it would strand itself against the wordmark. */
.nav-links { order: 1; }
.nav > .theme-toggle { order: 2; margin-left: .6rem; }
.nav-links a {
  color: var(--ink-2); padding: .45rem .7rem; border-radius: var(--radius-sm);
  font-size: .94rem; font-weight: 500;
}
.nav-links a:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }
.nav-links a[aria-current="page"] { color: var(--ink); background: var(--panel-2); }

.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex: none;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--panel); color: var(--ink-2); cursor: pointer;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--ink-3); }
.theme-toggle svg { width: 17px; height: 17px; }
.theme-toggle .icon-dark { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .theme-toggle .icon-dark { display: block; }
  :root:where(:not([data-theme="light"])) .theme-toggle .icon-light { display: none; }
}

.nav-toggle { display: none; }

@media (max-width: 800px) {
  .nav-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px;
    border: 1px solid var(--rule); border-radius: var(--radius-sm);
    background: var(--panel); color: var(--ink-2); cursor: pointer;
  }
  .nav-links {
    display: none; position: absolute; left: 0; right: 0; top: 60px;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--panel); border-bottom: 1px solid var(--rule);
    padding: .5rem 20px 1rem; margin-left: 0;
  }
  .nav-links.open { display: flex; }
  .nav-links a { padding: .65rem .5rem; }
  .nav > .theme-toggle { order: 1; margin-left: auto; }
  .nav > .nav-toggle   { order: 2; margin-left: .5rem; }
  .nav-links           { order: 3; }
}

/* ---- Buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  font: inherit; font-weight: 600; font-size: .95rem;
  padding: .6rem 1.1rem; border-radius: var(--radius-sm);
  border: 1px solid transparent; cursor: pointer;
  background: var(--accent); color: var(--accent-ink);
  transition: background .12s ease, border-color .12s ease, transform .06s ease;
}
.btn:hover { background: var(--accent-hi); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }
.btn-secondary { background: var(--panel); color: var(--ink); border-color: var(--rule); }
.btn-secondary:hover { background: var(--panel-2); border-color: var(--ink-3); }
.btn-secondary.is-active { border-color: var(--accent); color: var(--accent); }

/* TOTP enrollment QR. Always dark-on-white, even in dark theme: plenty of
   scanners misread an inverted code. The image carries its own quiet zone. */
.totp-qr { display: inline-block; background: #fff; border-radius: 8px; line-height: 0; margin: 0 0 1rem; }
.totp-qr[hidden] { display: none; }
.totp-qr img { display: block; image-rendering: pixelated; border-radius: 8px; }
.btn-ghost { background: none; color: var(--accent); border-color: transparent; padding-inline: .4rem; }
.btn-ghost:hover { background: var(--accent-soft); }
.btn-sm { padding: .38rem .7rem; font-size: .87rem; }
.btn-lg { padding: .75rem 1.5rem; font-size: 1rem; }

/* ---- Cards and panels ------------------------------------------------------ */

.card {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.3rem;
}
.card-tight { padding: 1rem; }
.card h3 { margin-top: 0; }
.card-hover:hover { border-color: var(--ink-3); box-shadow: var(--shadow-sm); }

.panel {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel-head {
  padding: .7rem 1rem; border-bottom: 1px solid var(--rule);
  background: var(--panel-2); font-size: .88rem; font-weight: 600; color: var(--ink-2);
  display: flex; align-items: center; gap: .6rem;
}
.panel-body { padding: 1rem; }

/* ---- Breadcrumbs ------------------------------------------------------- */

.breadcrumbs { padding: .85rem 0 0; }
.breadcrumbs ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  list-style: none; margin: 0; padding: 0;
  font-size: .84rem; color: var(--ink-3);
}
.breadcrumbs li { display: flex; align-items: center; gap: .4rem; }
.breadcrumbs li:not(:first-child)::before { content: "/"; color: var(--rule); }
.breadcrumbs a { color: var(--ink-2); }
.breadcrumbs a:hover { color: var(--accent); text-decoration: underline; }
.breadcrumbs [aria-current="page"] { color: var(--ink); font-weight: 500; }

/* ---- Hero ----------------------------------------------------------------- */

.hero { padding: 3rem 0 2rem; }
.hero h1 { }
.hero .lede { font-size: 1.15rem; margin-bottom: 1.6rem; }
.eyebrow {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .8rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent); background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
  padding: .25rem .7rem; border-radius: 999px; margin-bottom: 1rem;
}

/* ---- Badges and pills ------------------------------------------------------ */

.badge {
  display: inline-block; font-size: .76rem; font-weight: 600;
  padding: .16rem .5rem; border-radius: 999px; border: 1px solid transparent;
  white-space: nowrap;
}
.badge-ok      { background: var(--ok-soft);     color: var(--ok);     border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.badge-warn    { background: var(--warn-soft);   color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.badge-danger  { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 30%, transparent); }
.badge-neutral { background: var(--panel-2);     color: var(--ink-2);  border-color: var(--rule); }
.badge-accent  { background: var(--accent-soft); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }

/* ---- Tables ---------------------------------------------------------------- */

.table-wrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: var(--radius); }
table { width: 100%; border-collapse: collapse; font-size: .92rem; }
thead th {
  text-align: left; font-weight: 600; color: var(--ink-2);
  background: var(--panel-2); padding: .6rem .8rem;
  border-bottom: 1px solid var(--rule); white-space: nowrap;
}
tbody td { padding: .55rem .8rem; border-bottom: 1px solid var(--rule-2); vertical-align: top; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--panel-2); }
td code, th code { white-space: nowrap; }

/* ---- Forms ----------------------------------------------------------------- */

label { display: block; font-size: .88rem; font-weight: 600; color: var(--ink-2); margin-bottom: .3rem; }
input[type=text], input[type=url], input[type=email], input[type=number],
input[type=password], select, textarea {
  width: 100%; font: inherit; font-size: .95rem;
  padding: .55rem .7rem;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
textarea { min-height: 8rem; resize: vertical; }
.field { margin-bottom: 1rem; }
.field-hint { font-size: .82rem; color: var(--ink-3); margin-top: .25rem; }
.checkline { display: flex; align-items: flex-start; gap: .5rem; }
.checkline input { width: auto; margin-top: .3rem; }
.checkline label { margin: 0; font-weight: 500; color: var(--ink); }

/* ---- Alerts ---------------------------------------------------------------- */

.alert {
  border: 1px solid var(--rule); border-left-width: 3px;
  border-radius: var(--radius-sm); padding: .8rem 1rem; margin: 0 0 1.2rem;
  background: var(--panel-2); font-size: .93rem;
}
.alert-ok     { background: var(--ok-soft);     border-left-color: var(--ok); }
.alert-warn   { background: var(--warn-soft);   border-left-color: var(--warn); }
.alert-danger { background: var(--danger-soft); border-left-color: var(--danger); }
.alert-info   { background: var(--violet-soft); border-left-color: var(--violet); }
.alert :last-child { margin-bottom: 0; }

/* ---- Code block with a copy button ----------------------------------------- */

.codeblock { position: relative; margin: 0 0 1rem; }
/* The Copy button gets its own strip above the code. Padding on the right only
 * pads the end of a scrolled line, so a long first line still ran under the
 * button wherever the column was narrow (phones, and the docs beside the sidebar). */
.codeblock pre { margin: 0; padding-top: 2.6rem; }
.copy-btn {
  position: absolute; top: .55rem; right: .55rem;
  font: inherit; font-size: .78rem; font-weight: 600;
  padding: .22rem .55rem; cursor: pointer;
  background: var(--panel); color: var(--ink-2);
  border: 1px solid var(--rule); border-radius: 5px;
}
.copy-btn:hover { color: var(--ink); border-color: var(--ink-3); }
.copy-btn[data-copied="1"] { color: var(--ok); border-color: var(--ok); }

/* ---- Pricing --------------------------------------------------------------- */

.price-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.price-card { display: flex; flex-direction: column; position: relative; }
.price-card.featured { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.price-flag {
  position: absolute; top: -.7rem; left: 1.2rem;
  background: var(--accent); color: var(--accent-ink);
  font-size: .72rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  padding: .16rem .55rem; border-radius: 999px;
}
.price-amount { font-size: 2rem; font-weight: 700; letter-spacing: -.02em; }
.price-amount .per { font-size: .9rem; font-weight: 500; color: var(--ink-3); }
.price-list { list-style: none; padding: 0; margin: 1rem 0; font-size: .92rem; flex: 1; }
.price-list li { display: flex; gap: .5rem; align-items: flex-start; margin: .45rem 0; }
.price-list .tick { color: var(--ok); flex: none; font-weight: 700; }
.price-list .cross { color: var(--ink-3); flex: none; font-weight: 700; }
.compare { font-size: .82rem; color: var(--ink-3); margin-top: .5rem; }

/* ---- Docs layout ----------------------------------------------------------- */

.docs { display: grid; grid-template-columns: 230px minmax(0, 1fr); gap: 2.5rem; align-items: start; }
.docs-nav { position: sticky; top: 76px; font-size: .9rem; max-height: calc(100dvh - 96px); overflow-y: auto; }
.docs-nav ul { list-style: none; padding: 0; margin: 0 0 1.2rem; }
.docs-nav li { margin: 0; }
.docs-nav a { display: block; padding: .3rem .5rem; border-radius: 5px; color: var(--ink-2); }
.docs-nav a:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }
.docs-nav .nav-title {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); font-weight: 700; margin: 1.2rem .5rem .35rem;
}
.docs-body { min-width: 0; }
.docs-body h2 { margin-top: 2.5rem; padding-top: .5rem; scroll-margin-top: 76px; }
.docs-body h3 { margin-top: 1.8rem; scroll-margin-top: 76px; }
.docs-body h2:first-child { margin-top: 0; }
@media (max-width: 900px) {
  .docs { grid-template-columns: 1fr; gap: 1.5rem; }
  .docs-nav { position: static; max-height: none; border-bottom: 1px solid var(--rule); padding-bottom: 1rem; }
}

/* ---- Live demo -------------------------------------------------------------- */

.demo { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--panel); overflow: hidden; }
.demo-bar { display: flex; gap: .5rem; padding: .9rem; border-bottom: 1px solid var(--rule); flex-wrap: wrap; }
.demo-bar input[type=url] { flex: 1 1 260px; }
.demo-opts { display: flex; gap: 1rem; padding: 0 .9rem .8rem; flex-wrap: wrap; font-size: .88rem; }
.demo-opts label { display: inline-flex; align-items: center; gap: .35rem; font-weight: 500; color: var(--ink-2); margin: 0; }
.demo-opts input { width: auto; }
.demo-out { border-top: 1px solid var(--rule); }
.demo-out pre { border: 0; border-radius: 0; margin: 0; max-height: 380px; }
.demo-meta {
  display: flex; gap: .5rem; flex-wrap: wrap; align-items: center;
  padding: .6rem .9rem; border-top: 1px solid var(--rule-2);
  background: var(--panel-2); font-size: .82rem; color: var(--ink-2);
}
.spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--rule); border-top-color: var(--accent);
  animation: spin .7s linear infinite; display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 3s; } }

/* ---- Stat tiles ------------------------------------------------------------- */

.stat { background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius); padding: .9rem 1rem; }
.stat-label { font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-3); font-weight: 600; }
.stat-value { font-size: 1.5rem; font-weight: 700; letter-spacing: -.02em; margin-top: .15rem; }
.stat-sub { font-size: .8rem; color: var(--ink-3); }

/* ---- Footer ----------------------------------------------------------------- */

.site-footer {
  flex: none; border-top: 1px solid var(--rule);
  background: var(--bg-alt); color: var(--ink-2);
  font-size: .89rem; padding: 2rem 0;
}
.footer-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.footer-grid h4 { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); margin-bottom: .5rem; }
.footer-grid ul { list-style: none; padding: 0; margin: 0; }
.footer-grid li { margin: .3rem 0; }
.footer-grid a { color: var(--ink-2); }
.footer-grid a:hover { color: var(--ink); }
.footer-bottom {
  margin-top: 1.6rem; padding-top: 1.2rem; border-top: 1px solid var(--rule);
  display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; color: var(--ink-3); font-size: .85rem;
}

/* ---- Utilities --------------------------------------------------------------- */

.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.nowrap { white-space: nowrap; }
.mono { font-family: var(--mono); }
.mt-0 { margin-top: 0; } .mb-0 { margin-bottom: 0; }
.mt-2 { margin-top: 1.5rem; } .mb-2 { margin-bottom: 1.5rem; }
.mt-3 { margin-top: 2.5rem; } .mb-3 { margin-bottom: 2.5rem; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 34ch; display: inline-block; vertical-align: bottom; }

/* ---- Utility classes replacing inline style attributes ----------------------
   The Content-Security-Policy is enforced with a nonce and no 'unsafe-inline',
   which blocks style="" attributes as well as inline <style> blocks. Everything
   that would have been an inline style is a class instead. */

.pad-block   { padding: 4rem 0; }
.center-block{ margin-left: auto; margin-right: auto; margin-bottom: 1.6rem; }
.row-center  { justify-content: center; }
.push-right  { margin-left: auto; }
.fs-sm       { font-size: .9rem; }
.fs-xs       { font-size: .82rem; }
.label-sm    { font-size: .88rem; font-weight: 600; }
.mt-1        { margin-top: 1rem; }
.mt-half     { margin-top: .5rem; }

/* ---- Long-form content, blog, and admin ------------------------------------
   Added ahead of the blog and admin modules so those can be built without
   editing this file. Everything reads from the same tokens, so both themes work
   with no further changes. */

/* Blog posts run the full content width, like every other page (operator,
   2026-10-09): a 68ch measure wrapped the text in a narrow column beside empty
   space and sent code samples into a sideways scroll. */
.prose h2 { margin-top: 2.2rem; }
.prose h3 { margin-top: 1.6rem; }
.prose img { border-radius: var(--radius); border: 1px solid var(--rule); }
.prose blockquote {
  margin: 1.2rem 0; padding: .6rem 1rem;
  border-left: 3px solid var(--accent); background: var(--panel-2);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.prose blockquote :last-child { margin-bottom: 0; }
.prose table { margin-bottom: 1rem; }
/* Running text, not a reference table: long links and identifiers wrap, and the
   one-line rules meant for parameter tables do not squeeze a post's columns off
   a phone screen. */
.prose { overflow-wrap: break-word; }
.prose td:first-child, .prose td:first-child code, .prose thead th { white-space: normal; }
.prose .table-wrap { margin-bottom: 1rem; }
.prose .table-wrap table { margin-bottom: 0; }
/* An SVG with only a viewBox would fill the whole column. */
.prose img[src$=".svg" i] { max-width: min(100%, 640px); }
.prose figure { margin: 0 0 1rem; }
.prose figcaption { font-size: .88rem; color: var(--ink-3); margin-top: .4rem; }

.post-list { list-style: none; padding: 0; margin: 0; }
.post-list > li { padding: 1.1rem 0; border-bottom: 1px solid var(--rule-2); }
.post-list > li:last-child { border-bottom: 0; }
.post-title { font-size: 1.2rem; font-weight: 650; margin: 0 0 .3rem; }
.post-title a { color: var(--ink); }
.post-title a:hover { color: var(--accent); }
.post-meta { font-size: .84rem; color: var(--ink-3); display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.post-excerpt { color: var(--ink-2); margin: .4rem 0 0; }

.tag-list { display: flex; gap: .35rem; flex-wrap: wrap; list-style: none; padding: 0; margin: 0; }

.admin-grid { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.admin-bar { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.2rem; }
.admin-bar form { display: inline-flex; gap: .4rem; align-items: center; margin: 0; }
.admin-bar select, .admin-bar input { width: auto; }

.pager { display: flex; gap: .5rem; align-items: center; justify-content: center; margin-top: 1.5rem; }
.pager .current { padding: .38rem .7rem; font-size: .87rem; color: var(--ink-3); }

.empty-state { text-align: center; padding: 3rem 1rem; color: var(--ink-3); }

/* ---- Wiggles ---------------------------------------------------------------
   The mascot. Artwork carries its own colors rather than theme tokens, because
   he is a character and should look like himself in both themes; the surfaces
   around him do the adapting. */

.wiggles-head { width: 30px; height: 30px; flex: none; display: block; }
.wiggles      { width: 100%; height: auto; display: block; }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 2.5rem;
  align-items: center;
}
.hero-art { position: relative; }
/* A soft ground so he is standing on something rather than floating. */
.hero-art::after {
  content: "";
  position: absolute;
  left: 12%; right: 12%; bottom: 2%;
  height: 16px;
  background: radial-gradient(ellipse at center, color-mix(in srgb, var(--ink) 16%, transparent), transparent 70%);
  border-radius: 50%;
  z-index: -1;
}
.hero-art .wiggles { filter: drop-shadow(0 18px 28px color-mix(in srgb, var(--ink) 18%, transparent)); }

@media (max-width: 860px) {
  /* On a phone the copy matters more than the picture, so he shrinks and moves
     above the text rather than squeezing it. */
  .hero-grid { grid-template-columns: 1fr; gap: 1.2rem; }
  .hero-art { order: -1; max-width: 190px; }
}

/* ============================================================================
 * Full-bleed bands
 *
 * Content lives inside a single .wrap opened by the layout, so a section that
 * wants edge-to-edge color has to break out of it and then re-inset its own
 * content. overflow-x:clip on main contains the 100vw overshoot that a visible
 * scrollbar would otherwise turn into horizontal page scroll; clip is used
 * rather than hidden because hidden would create a scroll container and break
 * position:sticky inside it.
 * ========================================================================== */
main { overflow-x: clip; }

/* main carries 2.5rem of top padding, which is right when the first thing on a
 * page is ordinary content sitting under the nav. When the first thing is a
 * full-bleed band it is wrong: the band should meet the header, and the padding
 * leaves a strip of page background above it that reads as an empty second nav
 * bar. Scoped with :has() so only the pages that open on a band are affected. */
main:has(> .wrap > .band:first-child) { padding-top: 0; }

.band {
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
  padding-block: 3.5rem;
  margin-block: 0;
}
.band + .band { margin-top: 0; }
.band-alt    { background: var(--bg-alt); border-block: 1px solid var(--rule-2); }
.band-panel  { background: var(--panel); border-block: 1px solid var(--rule-2); }

/* The dark band is dark in both themes on purpose: it is the page's anchor,
 * the way ScrapingBee's black CTA reads the same whatever the OS is set to.
 * Every color inside it is restated rather than inherited, so nothing that
 * lands on it can pick up a light-theme token and disappear. */
.band-dark {
  background: #10151b;
  --ink: #f2f4f6; --ink-2: #b9c1cb; --ink-3: #8c96a2;
  --panel: #19212a; --panel-2: #202a35; --rule: #2b3541; --rule-2: #222b35;
  --code-bg: #0b0f14; --code-ink: #e6eaee;
  color: #f2f4f6;
}
.band-dark h1, .band-dark h2, .band-dark h3 { color: #f2f4f6; }
.band-dark .muted, .band-dark .dim { color: #8c96a2; }

.band > .band-inner { max-width: var(--maxw); margin-inline: auto; }
.band-head { max-width: 46rem; margin-bottom: 2rem; }
.band-head h2 { margin-top: 0; }

/* ============================================================================
 * Scroll reveal
 *
 * Elements start displaced and settle as they enter the viewport. The initial
 * state is applied by JS (adding .reveal-armed) rather than sitting in the
 * stylesheet, so with JS off or blocked nothing is ever left invisible.
 * ========================================================================== */
.reveal-armed [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .55s cubic-bezier(.22,.61,.36,1), transform .55s cubic-bezier(.22,.61,.36,1);
}
.reveal-armed [data-reveal].is-visible { opacity: 1; transform: none; }
.reveal-armed [data-reveal][data-reveal-delay="1"] { transition-delay: .07s; }
.reveal-armed [data-reveal][data-reveal-delay="2"] { transition-delay: .14s; }
.reveal-armed [data-reveal][data-reveal-delay="3"] { transition-delay: .21s; }
.reveal-armed [data-reveal][data-reveal-delay="4"] { transition-delay: .28s; }

@media (prefers-reduced-motion: reduce) {
  .reveal-armed [data-reveal] { opacity: 1; transform: none; transition: none; }
}
/* Print never scrolls, so the observer never reveals anything below the fold. */
@media print {
  .reveal-armed [data-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ============================================================================
 * Icon tiles, steps, quotes, FAQ
 * ========================================================================== */
.tiles { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.tile {
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 1.25rem; display: flex; flex-direction: column; gap: .5rem;
}
.tile-ico {
  width: 38px; height: 38px; border-radius: 9px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); flex: none;
}
.band-dark .tile-ico { background: rgba(210,92,12,.18); color: #f0a06a; }
.tile-ico svg { width: 20px; height: 20px; }
.tile h3 { margin: 0; font-size: 1.02rem; }
.tile p  { margin: 0; color: var(--ink-2); font-size: .93rem; }

.steps { display: grid; gap: 1.4rem; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); counter-reset: step; }
.step { position: relative; padding-left: 3rem; counter-increment: step; }
.step::before {
  content: counter(step); position: absolute; left: 0; top: -2px;
  width: 2.1rem; height: 2.1rem; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  display: grid; place-items: center; font-weight: 700; font-size: .95rem;
}
.step h3 { margin: 0 0 .3rem; font-size: 1.02rem; }
.step p  { margin: 0; color: var(--ink-2); font-size: .93rem; }

.quote {
  border-left: 3px solid var(--accent); background: var(--panel);
  padding: 1rem 1.2rem; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.quote p      { margin: 0 0 .5rem; font-size: 1.02rem; }
.quote footer { color: var(--ink-3); font-size: .86rem; }

.faq { display: grid; gap: .6rem; }
.faq details {
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 0 1.1rem;
}
.faq summary {
  cursor: pointer; list-style: none; padding: 1rem 2rem 1rem 0; position: relative;
  font-weight: 600; color: var(--ink);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; position: absolute; right: .25rem; top: 50%; width: 9px; height: 9px;
  border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3);
  transform: translateY(-70%) rotate(45deg); transition: transform .18s ease;
}
.faq details[open] summary::after { transform: translateY(-30%) rotate(-135deg); }
.faq summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.faq .faq-body { padding: 0 0 1.05rem; color: var(--ink-2); }
.faq .faq-body :first-child { margin-top: 0; }
.faq .faq-body :last-child  { margin-bottom: 0; }
@media (prefers-reduced-motion: reduce) { .faq summary::after { transition: none; } }

/* ============================================================================
 * Assurance strip
 *
 * This was a single row of ticked labels and read as filler, because a bare
 * claim asks the reader to take it on faith. Each one now carries the sentence
 * that backs it, which is the difference between a badge and an argument.
 * ========================================================================== */
.assure {
  display: grid; gap: 0;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.assure li { list-style: none; padding: 0 1.4rem; position: relative; }
.assure li + li::before {
  content: ""; position: absolute; left: 0; top: .2rem; bottom: .2rem;
  width: 1px; background: var(--rule);
}
/* The separator is a desktop device. Once the grid wraps to one column the rule
 * lands above each item rather than beside it, which reads as a broken border. */
@media (max-width: 700px) {
  .assure { gap: 1.4rem; }
  .assure li { padding: 0; }
  .assure li + li::before { display: none; }
}
.assure-ico {
  width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); margin-bottom: .6rem;
}
.assure-ico svg { width: 18px; height: 18px; }
.assure h3 {
  margin: 0 0 .3rem; font-size: .96rem; letter-spacing: -.005em;
}
.assure p { margin: 0; font-size: .86rem; line-height: 1.5; color: var(--ink-2); }
.band-dark .assure-ico { background: rgba(210,92,12,.18); color: #f0a06a; }


/* In light themes the dark band is the page's anchor and reads on its own. In a
 * dark theme it would otherwise sit at almost exactly the page background and
 * stop being a band at all, so it lifts a step and takes visible edges. */
.band-dark { border-block: 1px solid #232c36; }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .band-dark {
    background: #171f28;
    --panel: #1e2732; --panel-2: #26313d; --rule: #323e4b; --rule-2: #2a3540;
    --code-bg: #10161d;
    border-block-color: #2c3742;
  }
}
:root[data-theme="dark"] .band-dark {
  background: #171f28;
  --panel: #1e2732; --panel-2: #26313d; --rule: #323e4b; --rule-2: #2a3540;
  --code-bg: #10161d;
  border-block-color: #2c3742;
}

/* ============================================================================
 * Live demo console
 *
 * The old layout was one thin input above a pane that stayed hidden until you
 * pressed the button, so the section read as empty space. It now holds a
 * console that is always on screen, showing a labeled example until a real
 * response replaces it, with the claims beside it rather than above.
 * ========================================================================== */
.demo-grid {
  display: grid; gap: 1.5rem; align-items: start;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}
@media (max-width: 900px) { .demo-grid { grid-template-columns: minmax(0, 1fr); } }

.console { border: 1px solid var(--rule); border-radius: var(--radius);
           background: var(--panel); overflow: hidden; box-shadow: var(--shadow); }
.console-chrome {
  display: flex; align-items: center; gap: .45rem;
  padding: .6rem .9rem; background: var(--panel-2); border-bottom: 1px solid var(--rule);
}
.console-chrome i {
  width: 10px; height: 10px; border-radius: 50%; background: var(--rule); flex: none;
}
.console-chrome i:nth-child(1) { background: #e05c4b; }
.console-chrome i:nth-child(2) { background: #e0a83c; }
.console-chrome i:nth-child(3) { background: #4aa564; }
.console-title {
  margin-left: .5rem; font-family: var(--mono); font-size: .78rem; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The pane keeps its height whether it holds the example or a real response, so
 * running a fetch never makes the page jump under the pointer. */
.demo-out pre { min-height: 15rem; }
.demo-out.is-sample pre code { color: var(--ink-3); }

.demo-side { background: var(--panel); border: 1px solid var(--rule);
             border-radius: var(--radius); padding: 1.25rem; }
.demo-side h3 { margin-top: 0; font-size: 1.02rem; }
.checklist { list-style: none; padding: 0; margin: 0 0 1.1rem; display: grid; gap: .7rem; }
.checklist li { display: grid; grid-template-columns: 1.1rem 1fr; gap: .55rem;
                font-size: .91rem; color: var(--ink-2); line-height: 1.45; }
.checklist svg { width: 15px; height: 15px; color: var(--ok); margin-top: .2rem; }
.checklist strong { color: var(--ink); font-weight: 600; }

/* ============================================================================
 * Escalation ladder, animated
 *
 * A looping depiction of what mode=auto actually does: try the cheap route, get
 * refused, climb, and stop at the first one that works. Pure CSS on a single
 * shared cycle, so the rungs cannot drift out of step with each other the way
 * four independent JS timers would, and there is nothing to start or stop.
 *
 * The whole thing is decorative: it is aria-hidden, and the same sequence is
 * written out in the prose beside it.
 * ========================================================================== */
.ladder { --cycle: 9s; display: grid; gap: .5rem; }
.rung {
  display: grid; grid-template-columns: 1.4rem 1fr auto; gap: .7rem; align-items: center;
  padding: .7rem .85rem; border-radius: var(--radius-sm);
  border: 1px solid var(--rule); background: var(--panel);
  font-family: var(--mono); font-size: .84rem;
  opacity: .45;
}
.rung .dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--ink-3);
  justify-self: center; position: relative;
}
.rung .verdict { font-size: .76rem; color: var(--ink-3); letter-spacing: .01em; }
.rung .cost { color: var(--ink-3); font-size: .78rem; }

/* Each rung runs the same three-state cycle, offset so they fire in order:
 * dim -> probing -> settled. The settled color is what differs. */
@keyframes rung-try {
  0%, 12%   { opacity: .45; border-color: var(--rule); }
  14%, 26%  { opacity: 1;   border-color: var(--accent); }
  28%, 92%  { opacity: .6;  border-color: var(--danger); }
  94%, 100% { opacity: .45; border-color: var(--rule); }
}
@keyframes rung-win {
  0%, 40%   { opacity: .45; border-color: var(--rule); }
  42%, 52%  { opacity: 1;   border-color: var(--accent); }
  54%, 92%  { opacity: 1;   border-color: var(--ok); }
  94%, 100% { opacity: .45; border-color: var(--rule); }
}
@keyframes dot-try {
  0%, 13%   { background: var(--ink-3); }
  14%, 27%  { background: var(--accent); }
  28%, 92%  { background: var(--danger); }
  94%, 100% { background: var(--ink-3); }
}
@keyframes dot-win {
  0%, 41%   { background: var(--ink-3); }
  42%, 53%  { background: var(--accent); }
  54%, 92%  { background: var(--ok); }
  94%, 100% { background: var(--ink-3); }
}
@keyframes verdict-in { 0%, 27% { opacity: 0; } 30%, 92% { opacity: 1; } 94%, 100% { opacity: 0; } }
@keyframes verdict-win { 0%, 53% { opacity: 0; } 56%, 92% { opacity: 1; } 94%, 100% { opacity: 0; } }

/* rung-2's stagger used to be animation-delay: 14%. That shifts its whole
 * timeline including the 94%-100% reset, so the reset landed at 108%-114%,
 * wrapping into the start of the next lap -- rung-1 and rung-3 had already
 * reset and rung-1 was probing again while rung-2 sat there still showing
 * "403 refused" from the lap before. Baking the offset into a dedicated
 * keyframe instead keeps its reset on the same 94%-100% mark as the other
 * two, so all three go idle together. */
@keyframes rung-try-2 {
  0%, 26%   { opacity: .45; border-color: var(--rule); }
  28%, 40%  { opacity: 1;   border-color: var(--accent); }
  42%, 92%  { opacity: .6;  border-color: var(--danger); }
  94%, 100% { opacity: .45; border-color: var(--rule); }
}
@keyframes dot-try-2 {
  0%, 27%   { background: var(--ink-3); }
  28%, 41%  { background: var(--accent); }
  42%, 92%  { background: var(--danger); }
  94%, 100% { background: var(--ink-3); }
}
@keyframes verdict-in-2 { 0%, 41% { opacity: 0; } 44%, 92% { opacity: 1; } 94%, 100% { opacity: 0; } }

.rung-1 { animation: rung-try var(--cycle) infinite; }
.rung-2 { animation: rung-try-2 var(--cycle) infinite; }
.rung-3 { animation: rung-win var(--cycle) infinite; }
.rung-1 .dot { animation: dot-try var(--cycle) infinite; }
.rung-2 .dot { animation: dot-try-2 var(--cycle) infinite; }
.rung-3 .dot { animation: dot-win var(--cycle) infinite; }
.rung-1 .verdict { animation: verdict-in var(--cycle) infinite; opacity: 0; }
.rung-2 .verdict { animation: verdict-in-2 var(--cycle) infinite; opacity: 0; }
.rung-3 .verdict { animation: verdict-win var(--cycle) infinite; opacity: 0; }
.rung-1 .verdict, .rung-2 .verdict { color: var(--danger); }
.rung-3 .verdict { color: var(--ok); }
/* The fourth rung is never reached, which is the point: max_cost stopped it. */
.rung-4 { opacity: .3; border-style: dashed; }

.ladder-total {
  margin-top: .3rem; padding: .7rem .85rem; border-radius: var(--radius-sm);
  background: var(--panel-2); border: 1px solid var(--rule);
  font-family: var(--mono); font-size: .82rem;
  animation: verdict-win var(--cycle) infinite; opacity: 0;
}
.ladder-total strong { color: var(--ok); }

@media (prefers-reduced-motion: reduce) {
  .ladder * { animation: none !important; }
  .rung, .rung .verdict, .ladder-total { opacity: 1; }
  .rung-1, .rung-2 { border-color: var(--danger); }
  .rung-3 { border-color: var(--ok); }
  .rung-1 .dot, .rung-2 .dot { background: var(--danger); }
  .rung-3 .dot { background: var(--ok); }
}

/* ============================================================================
 * Cinematic hero
 *
 * A dark full-bleed field with slow-drifting light behind the claim, rather than
 * a headline sitting on the page background. The light is three large radial
 * gradients on long, mismatched loops, so the field never visibly repeats;
 * gradients rather than blurred elements because a filter over an area this size
 * costs a repaint on every frame.
 *
 * This band is dark in both themes deliberately. It is the one place on the site
 * that should look the same to everyone.
 * ========================================================================== */
.hero-cinema {
  position: relative; isolation: isolate; overflow: hidden;
  background: #080c11;
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
  border-bottom: 1px solid #1b242f;
  color: #eef2f6;
  --ink: #eef2f6; --ink-2: #b3bdc9; --ink-3: #8593a1;
  --rule: #26313d; --rule-2: #1e2731; --panel: #121a23; --panel-2: #18222d;
  --code-bg: #0b1016; --code-ink: #dde5ed;
}
.cinema-bg { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.aurora { position: absolute; border-radius: 50%; opacity: .5; }
.a1 { width: 62vw; height: 62vw; left: -12vw; top: -26vw;
      background: radial-gradient(circle, rgba(210,92,12,.34) 0%, rgba(210,92,12,0) 62%);
      animation: drift-1 34s ease-in-out infinite alternate; }
.a2 { width: 54vw; height: 54vw; right: -14vw; top: -18vw;
      background: radial-gradient(circle, rgba(91,60,196,.30) 0%, rgba(91,60,196,0) 64%);
      animation: drift-2 41s ease-in-out infinite alternate; }
.a3 { width: 70vw; height: 40vw; left: 18vw; bottom: -24vw;
      background: radial-gradient(ellipse, rgba(26,111,55,.22) 0%, rgba(26,111,55,0) 66%);
      animation: drift-3 47s ease-in-out infinite alternate; }
@keyframes drift-1 { from { transform: translate3d(0,0,0)      scale(1);    } to { transform: translate3d(7vw,4vw,0)   scale(1.12); } }
@keyframes drift-2 { from { transform: translate3d(0,0,0)      scale(1.08); } to { transform: translate3d(-6vw,6vw,0)  scale(1);    } }
@keyframes drift-3 { from { transform: translate3d(0,0,0)      scale(1);    } to { transform: translate3d(-9vw,-3vw,0) scale(1.15); } }

/* A faint rule grid, so the light has something to fall on and the field reads
 * as a surface rather than a smear. Masked to fade out toward the edges. */
.cinema-grid {
  position: absolute; inset: 0;
  background-image: linear-gradient(rgba(255,255,255,.028) 1px, transparent 1px),
                    linear-gradient(90deg, rgba(255,255,255,.028) 1px, transparent 1px);
  background-size: 62px 62px;
  mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, #000 30%, transparent 100%);
}

.cinema-inner { position: relative; text-align: center; }
.cinema-inner .eyebrow { justify-content: center; color: #f0a06a; }
.cinema-inner h1 {
  /* Sized so each of the two lines sits on one line of its own at desktop
   * widths. A 20ch cap broke the second one across three, which turned the
   * strongest thing on the page into a stack of fragments. */
  font-size: clamp(2rem, 4.4vw, 3.35rem); line-height: 1.08; letter-spacing: -.03em;
  margin: .55rem auto 1rem; max-width: 32ch; text-wrap: balance;
}
.cinema-inner h1 .line-2 { color: #f0a06a; display: block; }
.cinema-inner .lede { max-width: 46rem; margin-inline: auto; color: #b3bdc9; }
.cinema-inner .row { justify-content: center; }

/* The spec strip. Every figure here is a fact about the service, not telemetry:
 * inventing live counters for a product with no traffic yet would be a lie the
 * first curious visitor could check. */
.spec-strip {
  margin: 1.6rem auto 0; max-width: 60rem;
  display: flex; flex-wrap: wrap; justify-content: center; gap: .35rem 0;
  font-family: var(--mono); font-size: .76rem; color: #8593a1;
}
.spec-strip span { padding: 0 .85rem; position: relative; white-space: nowrap; }
.spec-strip span + span::before {
  content: ""; position: absolute; left: 0; top: .25em; bottom: .25em; width: 1px;
  background: #2b3644;
}
.spec-strip b { color: #cbd5e0; font-weight: 500; }

@media (prefers-reduced-motion: reduce) { .aurora { animation: none; } }

/* ============================================================================
 * Hero terminal
 *
 * Three calls typed on one 21s loop. Developers read curl and JSON on sight, so
 * a shell session explains the product faster than a diagram of it does.
 *
 * Timing is a single shared cycle rather than three timers, so the blocks cannot
 * drift apart over a long session, and there is nothing to start, stop or
 * resynchronise.
 * ========================================================================== */
.term {
  --cyc: 21s;
  border: 1px solid #22303f; border-radius: 12px; overflow: hidden;
  background: #0b1118; box-shadow: 0 14px 30px rgba(0,0,0,.4), 0 40px 80px rgba(0,0,0,.32);
  position: relative;
}
.term-bar {
  display: flex; align-items: center; gap: .4rem;
  padding: .6rem .85rem; background: #131c26; border-bottom: 1px solid #22303f;
}
.term-bar i { width: 9px; height: 9px; border-radius: 50%; background: #2c3a49; }
.term-bar i:nth-child(1) { background: #e05c4b; }
.term-bar i:nth-child(2) { background: #e0a83c; }
.term-bar i:nth-child(3) { background: #4aa564; }
.term-name { margin-left: .55rem; font-family: var(--mono); font-size: .72rem; color: #6f7f8f; }
.term-live {
  margin-left: auto; display: inline-flex; align-items: center; gap: .35rem;
  font-family: var(--mono); font-size: .66rem; color: #6f7f8f; letter-spacing: .04em;
}
.term-live b { width: 6px; height: 6px; border-radius: 50%; background: #4aa564;
               animation: pulse-live 2.4s ease-in-out infinite; }
@keyframes pulse-live { 0%,100% { opacity: .35; } 50% { opacity: 1; } }

.term-screen {
  position: relative; height: 212px; padding: .9rem 1rem;
  font-family: var(--mono); font-size: .715rem; line-height: 1.72; color: #b9c6d4;
}
@media (max-width: 560px) { .term-screen { height: 236px; font-size: .655rem; } }

.call { position: absolute; inset: .9rem 1rem; opacity: 0; }
.cmd, .out, .cur { white-space: nowrap; overflow: hidden; }
.cmd { color: #dbe5ef; }
.cmd u  { text-decoration: none; color: #7fd18c; }   /* string arguments */
.cmd k  { color: #f0a06a; }                          /* flags            */
.cmd s, .out s, .cur s { text-decoration: none; color: #5d6b7a; }
.out b  { color: #f0a06a; font-weight: 400; }        /* JSON keys        */
.out em { color: #7fd18c; font-style: normal; }      /* JSON values      */
.out d  { color: #8fa0b2; }                          /* markup, prose    */
.out c  { color: #7d8d9d; }                          /* the header line  */
.caret { display: inline-block; width: 7px; height: .95em; background: #7fd18c;
         vertical-align: -2px; animation: blink 1.1s steps(2, start) infinite; }
@keyframes blink { 0%,49% { opacity: 1; } 50%,100% { opacity: 0; } }

/* One block holds for a third of the cycle. */
@keyframes call-hold { 0%,1% {opacity:0} 3%,31% {opacity:1} 33%,100% {opacity:0} }
.call { animation: call-hold var(--cyc) infinite; }
.call-2 { animation-delay: calc(var(--cyc) * .333); }
.call-3 { animation-delay: calc(var(--cyc) * .666); }

/* Command lines type; output lines arrive whole, the way a response does.
 *
 * Both animations run for the WHOLE cycle, with the typing expressed as a short
 * ramp inside it. Giving them a short duration and `infinite` instead made every
 * line retype itself twice a second, which read as a glitch rather than a shell.
 * Sharing one period is also what lets a per-line delay place a line in the
 * sequence without any of them drifting. */
@keyframes type-in {
  0%     { width: 0; }
  2.4%   { width: 100%; }
  30%    { width: 100%; }
  30.4%  { width: 0; }
  100%   { width: 0; }
}
@keyframes drop-in {
  0%     { opacity: 0; }
  0.7%   { opacity: 1; }
  30%    { opacity: 1; }
  30.7%  { opacity: 0; }
  100%   { opacity: 0; }
}
.cmd { width: 0; animation: type-in var(--cyc) steps(34, end) infinite both; }
.out, .cur { opacity: 0; animation: drop-in var(--cyc) linear infinite both; }

/* Each line waits for the one above it. The block offset plus its own step. */
.call-1 .l1 { animation-delay: calc(var(--cyc) * .030); }
.call-1 .l2 { animation-delay: calc(var(--cyc) * .058); }
.call-1 .l3 { animation-delay: calc(var(--cyc) * .086); }
.call-1 .l4 { animation-delay: calc(var(--cyc) * .120); }
.call-1 .l5 { animation-delay: calc(var(--cyc) * .134); }
.call-1 .l6 { animation-delay: calc(var(--cyc) * .148); }
.call-1 .l7 { animation-delay: calc(var(--cyc) * .162); }
.call-1 .l8 { animation-delay: calc(var(--cyc) * .176); }
.call-1 .l9 { animation-delay: calc(var(--cyc) * .196); }

.call-2 .l1 { animation-delay: calc(var(--cyc) * .363); }
.call-2 .l2 { animation-delay: calc(var(--cyc) * .400); }
.call-2 .l3 { animation-delay: calc(var(--cyc) * .414); }
.call-2 .l4 { animation-delay: calc(var(--cyc) * .428); }
.call-2 .l5 { animation-delay: calc(var(--cyc) * .448); }
.call-2 .l6 { animation-delay: calc(var(--cyc) * .468); }

.call-3 .l1 { animation-delay: calc(var(--cyc) * .696); }
.call-3 .l2 { animation-delay: calc(var(--cyc) * .724); }
.call-3 .l3 { animation-delay: calc(var(--cyc) * .752); }
.call-3 .l4 { animation-delay: calc(var(--cyc) * .786); }
.call-3 .l5 { animation-delay: calc(var(--cyc) * .800); }
.call-3 .l6 { animation-delay: calc(var(--cyc) * .814); }
.call-3 .l7 { animation-delay: calc(var(--cyc) * .828); }
.call-3 .l8 { animation-delay: calc(var(--cyc) * .848); }
.call-3 .l9 { animation-delay: calc(var(--cyc) * .868); }

@media (prefers-reduced-motion: reduce) {
  .term * { animation: none !important; }
  .call { opacity: 0; } .call-1 { opacity: 1; }
  .cmd { width: 100%; } .out, .cur { opacity: 1; }
  .caret { opacity: 1; }
}

/* ---- Wiggles, holding the screen ----------------------------------------- */
.term-holder { position: relative; }
.term-holder .wiggles {
  position: absolute; right: -26px; bottom: -34px; width: 158px; z-index: 3;
  filter: drop-shadow(0 12px 20px rgba(0,0,0,.45));
  animation: wiggle-peek 6s ease-in-out infinite;
  transform-origin: 50% 100%;
}
@keyframes wiggle-peek { 0%,100% { transform: rotate(-2.5deg); } 50% { transform: rotate(2.5deg); } }
@media (prefers-reduced-motion: reduce) { .term-holder .wiggles { animation: none; } }
@media (max-width: 860px) { .term-holder .wiggles { width: 100px; right: -6px; bottom: -22px; } }
/* Copy on the left, the shell on the right: the arrangement worldip.io uses, and
 * the right one for an audience that will read the curl before the headline. */
.hero-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
  gap: 2.4rem; align-items: center; text-align: left;
}
@media (max-width: 900px) {
  .hero-split { grid-template-columns: minmax(0, 1fr); gap: 2.8rem; }
}
.cinema-inner { text-align: left; }
.cinema-inner .eyebrow { justify-content: flex-start; }
.cinema-inner h1 { margin-inline: 0; max-width: 21ch; }
.cinema-inner .lede { margin-inline: 0; max-width: 40rem; }
.cinema-inner .row { justify-content: flex-start; }
.spec-strip { justify-content: flex-start; margin-top: 2.6rem; }


/* ============================================================================
 * Announcement bar
 *
 * Sits above the header, full width, on the accent. One line, one link, and a
 * figure derived from Tier::TIERS rather than typed, so it cannot end up
 * advertising a discount we no longer give.
 * ========================================================================== */
/* Development notice (SITE_DEV_NOTICE). Deliberately a dark bar with caution
   striping, one look in both themes, so it can't be mistaken for the orange promo
   bar directly beneath it. */
.devnotice {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .35rem .7rem; padding: .7rem 1rem .55rem;
  background: #14181d; color: #e8eaed;
  font-size: .84rem; line-height: 1.45; text-align: center;
  position: relative; z-index: 61;
}
.devnotice::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px;
  background: repeating-linear-gradient(-45deg, #f0a24b 0 10px, #14181d 10px 20px);
}
.devnotice-tag {
  display: inline-block; padding: .12rem .55rem; border-radius: 999px;
  background: #f0a24b; color: #14181d;
  font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
@media (max-width: 560px) { .devnotice { font-size: .78rem; } }

.announce {
  background: linear-gradient(90deg, #b64d08 0%, #d25c0c 55%, #b64d08 100%);
  color: #fff; text-align: center; font-size: .84rem; line-height: 1.4;
  padding: .5rem 1rem; position: relative; z-index: 60;
}
.announce a { color: #fff; text-decoration: none; font-weight: 600;
              border-bottom: 1px solid rgba(255,255,255,.45); }
.announce a:hover { border-bottom-color: #fff; }
.announce b { font-weight: 700; }
.announce .arrow { display: inline-block; transition: transform .18s ease; }
.announce a:hover .arrow { transform: translateX(3px); }
/* A slow sheen, so the bar reads as alive without asking for attention. */
.announce::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 38%, rgba(255,255,255,.16) 50%, transparent 62%);
  background-size: 260% 100%;
  animation: sheen 7s linear infinite;
}
@keyframes sheen { from { background-position: 160% 0; } to { background-position: -60% 0; } }
@media (prefers-reduced-motion: reduce) { .announce::after { animation: none; opacity: 0; } }
@media (max-width: 560px) { .announce { font-size: .78rem; } }
/* ============================================================================
 * Plan grid
 *
 * All five on screen at once, no sideways scroll. That only became possible
 * once the cards stopped repeating the four guarantees every plan shares: those
 * are stated once, under the grid, and the cards carry only what differs.
 * ========================================================================== */
.plans {
  display: grid; gap: .8rem; align-items: stretch;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  padding-top: .9rem;   /* room for the flag on the featured card */
}
@media (max-width: 1080px) { .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .plans { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px)  { .plans { grid-template-columns: minmax(0, 1fr); } }

.plan {
  display: flex; flex-direction: column; position: relative;
  padding: 1.1rem .95rem 1.05rem;
  transition: transform .2s cubic-bezier(.22,.61,.36,1), box-shadow .2s ease, border-color .2s ease;
}
.plan h3 { margin: 0; font-size: .95rem; color: var(--ink-2); font-weight: 600;
           letter-spacing: .01em; }
.plan .price-amount { font-size: 1.62rem; margin-top: .1rem; }
.plan .price-amount .per { font-size: .78rem; }

.plan-vs { margin: .3rem 0 .9rem; font-size: .76rem; color: var(--ink-3); line-height: 1.45; }
.plan-vs s { color: var(--ink-3); }
.plan-vs b { color: var(--ok); font-weight: 700; }

/* A definition list rather than ticks: three numbers compared across five
 * columns want to line up with each other, which a bulleted sentence cannot do. */
.plan-spec { margin: 0 0 1rem; flex: 1; display: grid; gap: .45rem; }
.plan-spec > div { display: flex; align-items: baseline; justify-content: space-between; gap: .4rem; }
.plan-spec dt { font-size: .74rem; color: var(--ink-3); }
.plan-spec dd { margin: 0; font-size: .88rem; font-weight: 700; text-align: right; }
.plan-spec dd span { font-weight: 400; color: var(--ink-3); font-size: .72rem; margin-left: .15rem; }
.plan .btn { width: 100%; justify-content: center; }

/* The recommended plan is raised rather than merely outlined, so the eye lands
 * on it before it starts reading numbers. */
.plan.featured {
  border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow);
  transform: translateY(-.55rem);
}
.plan:hover { border-color: var(--accent); box-shadow: var(--shadow); }
.plan.featured:hover { transform: translateY(-.75rem); }
@media (prefers-reduced-motion: reduce) { .plan, .plan.featured { transition: none; } }
@media (max-width: 460px) { .plan.featured, .plan.featured:hover { transform: none; } }

.plan-common {
  list-style: none; padding: 0; margin: 1.4rem 0 0;
  display: flex; flex-wrap: wrap; gap: .5rem 1.6rem;
  font-size: .88rem; color: var(--ink-2);
}
.plan-common li { display: flex; align-items: center; gap: .4rem; }
.plan-common .tick { color: var(--ok); font-weight: 700; }

/* Long unbreakable tokens in a table cell -- `domcontentloaded`, or the
 * own_proxy syntax `<protocol><user>:<pass>@<host>:<port>` -- set a min-content
 * width that pushes the whole table past its container. The wrap then scrolls,
 * which is correct behavior and completely wrong here: on a reference table a
 * horizontal scrollbar reads as truncated text, and every wrapped line silently
 * loses its right-hand edge. Letting those tokens break keeps the table inside
 * its column, where all of it is readable at once. */
/* `anywhere` rather than `break-word`: only the former reduces the intrinsic
 * min-content width, which is the number the table is actually sized from.
 * break-word changes how a line wraps once a width is chosen and left the
 * table at its original 902px. */
/* Breaking has to be scoped to the column that needs it. Applied to every cell,
 * `anywhere` chopped the parameter names themselves -- wait_browse/r,
 * premium_pro/xy, extract_rul/es -- which is worse than the clipping it fixed:
 * a reference table is scanned for exact identifiers.
 *
 * So identifiers stay whole and only the description column, where the long
 * examples live, is allowed to break mid-token. */
td, th { overflow-wrap: normal; }
td code, th code { white-space: normal; }
td:first-child, td:first-child code { white-space: nowrap; overflow-wrap: normal; }
td:last-child, td:last-child code { overflow-wrap: anywhere; }
/* A table of sentences (the privacy data table) has prose in its first column,
 * so it wraps like any other text. */
.table-prose td:first-child { white-space: normal; }

/* A checkbox with its sentence beside it (the key portal's AUP acceptance). */
label.check { display: flex; align-items: flex-start; gap: .5rem; font-weight: 500; color: var(--ink-2); margin: 0; }
label.check input { width: auto; margin-top: .3rem; flex: none; }

/* Print: looping animations would freeze mid-cycle (an empty terminal, a
 * half-faded ladder), so print the same static frame reduced motion gets. */
@media print {
  .ladder * { animation: none !important; }
  .rung, .rung .verdict, .ladder-total { opacity: 1; }
  .rung-1, .rung-2 { border-color: var(--danger); }
  .rung-3 { border-color: var(--ok); }
  .rung-1 .dot, .rung-2 .dot { background: var(--danger); }
  .rung-3 .dot { background: var(--ok); }
  .term * { animation: none !important; }
  .call { opacity: 0; } .call-1 { opacity: 1; }
  .cmd { width: 100%; } .out, .cur { opacity: 1; }
  .caret { opacity: 1; }
  .aurora, .term-holder .wiggles, .announce::after { animation: none !important; }
}

/* Key portal: who is signed in, and the way out. */
.portal-bar { display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  flex-wrap: wrap; margin: 0 0 1rem; font-size: .9rem; }
.portal-bar form { margin: 0; }
.portal-bar strong { color: var(--ink); overflow-wrap: anywhere; }
