/* postmoderno marketing site
   direction: black ground, ultramarine glow, hairline-edged floating panels.
   type: IBM Plex Mono (display/labels/times) + IBM Plex Sans (body) —
   the product lives in terminals and agents; mono is its native habitat. */

@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/plex-mono-400.woff2") format("woff2");
  font-weight: 400; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/plex-mono-500.woff2") format("woff2");
  font-weight: 500; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("../fonts/plex-mono-600.woff2") format("woff2");
  font-weight: 600; font-display: swap;
}
@font-face {
  font-family: "Plex Sans";
  src: url("../fonts/plex-sans-var.woff2") format("woff2");
  font-weight: 100 700; font-display: swap;
}

:root {
  --bg: #030509;
  --ink: #e7ecf5;
  --dim: #8b96a8;
  --faint: #7a8497;
  --hairline: rgba(140, 170, 255, 0.14);
  --blue: #1c5cff;          /* unslop-ignore: chosen accent, ultramarine */
  --blue-hi: #7fa8ff;
  --blue-dim: rgba(28, 92, 255, 0.35);
  --ok: #4cd7a4;
  --mono: "Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --sans: "Plex Sans", -apple-system, "Segoe UI", sans-serif;

  /* Type scale. Six steps, and every rule in this file picks one of them — the
     panel alone had seventeen sizes, arrived at one .74rem at a time, and its
     whole interactive layer had drifted under 12px. Pixels, not rem: these are
     interface sizes and must not drift with a page's own font-size, which the
     panel and the story both raise.

     The exception is the display heading a single page is built around — the
     hero, the login h1, the story's act headings. Those are compositions with
     one viewport each and keep their own clamp; everything that repeats picks
     a step. */
  --t-display: clamp(2.4rem, 5.4vw, 4.2rem);
  --t-h2: clamp(1.7rem, 2.9vw, 2.4rem);
  --t-body: 17px;   /* the thing being read: a post, a paragraph, a field */
  --t-ui: 14px;     /* anything you click, and the data in a row */
  --t-meta: 13px;   /* what qualifies the thing above it */
  --t-micro: 11px;  /* labels, badges, counters — and the transcript at its
                       narrowest, which is the one place it carries prose */
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Every surface here is dark. Without this the form controls the panel is
   built from — checkboxes, the workspace select, the scrollbar — are
   painted light by the platform. */
html {
  color-scheme: dark;
  /* At the root, so every surface renders at one weight. macOS paints text
     heavier than drawn; on a black ground that reads as mud. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Landing only: the router scrolls to top on navigation, and smooth
   behaviour would animate that instead of placing it. */
html:has(body.home) { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-ui);
  line-height: 1.55;
  overflow-x: hidden;
}

/* The story and the panel both read at a larger size than the utility pages —
   they are places you spend time, not forms you pass through. */
body.home, body.panel-page { font-size: var(--t-body); line-height: 1.6; }

::selection { background: var(--blue); color: #fff; }

a { color: inherit; text-decoration: none; }
code {
  font-family: var(--mono); font-size: 0.85em;
  color: var(--blue-hi);
  background: rgba(28, 92, 255, 0.08);
  border: 1px solid var(--hairline);
  border-radius: 4px; padding: 0.1em 0.35em;
}
em { font-style: italic; }

/* Cross-document transitions. Both documents in a navigation must opt in, so
   this pairs with the same rule in the app stylesheet — it is what makes
   landing → blog a crossfade rather than a white flash. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ---- scene layers ---- */

#scene {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: 0; pointer-events: none;
}
/* Persistent chrome, so it survives navigation — but only the story wants it.
   Shown by body class rather than by being absent, or arriving on a page that
   never had one would leave the story without it after a swap. */
.underglow { display: none; }
body.home .underglow,
body.panel-page .underglow {
  display: block;
  position: fixed; left: 50%; bottom: -42vh;
  width: 130vw; height: 80vh;
  transform: translateX(-50%);
  background: radial-gradient(ellipse closest-side,
    rgba(28, 92, 255, 0.28), rgba(20, 60, 200, 0.10) 45%, transparent 72%);
  z-index: 0; pointer-events: none;
}

/* Lifts the page off the canvas. The header is left out on purpose: it is
   already fixed and already above both, and naming it here would give it
   position: relative — more specific than the rule that fixes it — which drops
   it back into the flow and scrolls it away with the story. */
.home main, .home footer {
  position: relative; z-index: 1;
  transition: transform 0.9s cubic-bezier(0.76, 0, 0.24, 1), opacity 0.7s ease;
  will-change: transform;
}

/* ---- header ---- */

@property --menu-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

header.top {
  position: fixed; top: 16px; left: 50%; z-index: 10;
  width: min(1240px, calc(100% - 32px));
  transform: translateX(-50%);
  display: flex; justify-content: space-between; align-items: center;
  padding: 12px 13px 12px 24px;
  background: rgba(7, 10, 18, 0.62);
  -webkit-backdrop-filter: blur(18px) saturate(135%);
  backdrop-filter: blur(18px) saturate(135%);
  border: 1px solid rgba(140, 170, 255, 0.16);
  border-radius: 20px;
  box-shadow: 0 12px 42px rgba(0, 0, 0, 0.28), inset 0 1px rgba(255, 255, 255, 0.035);
  transition: background 0.3s, border-color 0.3s, box-shadow 0.3s,
    transform 0.9s cubic-bezier(0.76, 0, 0.24, 1), opacity 0.7s ease;
}
header.top::before,
header.top::after {
  content: "";
  position: absolute; inset: -1px;
  z-index: 0; pointer-events: none;
  padding: 1.5px;
  border-radius: inherit;
  background: conic-gradient(
    from var(--menu-angle),
    transparent 0 41%,
    rgba(74, 126, 255, 0.06) 44%,
    rgba(102, 151, 255, 0.95) 49%,
    #d2e0ff 50%,
    rgba(65, 119, 255, 0.82) 51%,
    rgba(28, 92, 255, 0.04) 56%,
    transparent 59% 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  filter: drop-shadow(0 0 5px rgba(60, 116, 255, 0.9));
  animation: menu-orbit 5.5s linear infinite;
}
header.top::after {
  opacity: 0.78;
  --menu-angle: 180deg;
  animation-name: menu-orbit-reverse;
  animation-duration: 5.5s;
}
.wordmark, .top-nav { position: relative; z-index: 1; }
@keyframes menu-orbit {
  to { --menu-angle: 360deg; }
}
@keyframes menu-orbit-reverse {
  from { --menu-angle: 180deg; }
  to { --menu-angle: 540deg; }
}
header.top.scrolled {
  background: rgba(5, 8, 15, 0.86);
  border-color: rgba(140, 170, 255, 0.24);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.38), 0 0 24px rgba(28, 92, 255, 0.06);
}
.wordmark {
  font-family: var(--mono); font-weight: 600; font-size: var(--t-body);
  letter-spacing: -0.02em;
}
.caret {
  display: inline-block; width: 8px; height: 16px;
  margin-left: 3px; vertical-align: -2px;
  background: var(--blue);
  animation: blink 1.2s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.top-nav { display: flex; gap: 7px; align-items: center; }
.nav-links { display: flex; gap: 4px; align-items: center; margin-right: 7px; }
.top-nav a {
  font-family: var(--mono); font-size: var(--t-ui); color: var(--dim);
  /* 8px, not 12: the pill is 20px with 12px of padding, and a nested corner
     has to be the outer radius minus that padding or it looks pinched. */
  padding: 11px 17px; border-radius: 8px;
  transition: color 0.2s, background 0.2s, box-shadow 0.2s;
}
.top-nav a:hover { color: var(--ink); background: rgba(255, 255, 255, 0.045); }
.top-nav .top-cta {
  color: #fff; background: var(--blue); padding-left: 16px; padding-right: 14px;
  box-shadow: 0 0 20px rgba(28, 92, 255, 0.22);
}
.top-nav .top-cta:hover { background: #316bff; box-shadow: 0 0 28px rgba(28, 92, 255, 0.42); }

/* ---- shared ---- */

.index {
  font-family: var(--mono); font-size: var(--t-micro); font-weight: 500;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--blue-hi); margin-bottom: 20px;
}
.home :is(h1, h2) {
  font-family: var(--mono); font-weight: 600;
  letter-spacing: -0.035em; line-height: 1.1;
  text-wrap: balance;
}
.home h2 { font-size: clamp(1.7rem, 3.4vw, 2.6rem); margin-bottom: 20px; }

.btn {
  display: inline-block;
  font-family: var(--mono); font-size: var(--t-ui); font-weight: 500;
  padding: 13px 26px; border-radius: 6px;
  border: 1px solid var(--hairline);
  transition: box-shadow 0.25s, border-color 0.25s, background 0.25s, scale 0.15s ease-out;
}
.btn-primary {
  background: var(--blue); border-color: transparent; color: #fff;
  box-shadow: 0 0 22px rgba(28, 92, 255, 0.28);
}
.btn-primary:hover { box-shadow: 0 0 38px rgba(28, 92, 255, 0.55); }
.btn-ghost { color: var(--dim); }
.btn-ghost:hover { border-color: var(--blue-dim); color: var(--ink); }
.cta-row { display: flex; gap: 14px; flex-wrap: wrap; }

.dim { color: var(--dim); }

/* ---- hero ---- */

.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  text-align: center; padding: 120px 24px 80px;
}
.kicker {
  font-family: var(--mono); font-size: var(--t-micro); letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--dim); margin-bottom: 28px;
}
.hero h1 {
  font-size: clamp(2.4rem, 6.4vw, 5rem);
  margin-bottom: 26px;
  text-shadow: 0 0 60px rgba(28, 92, 255, 0.35);
}
.hero .sub {
  max-width: 560px; color: var(--dim); margin-bottom: 40px;
  font-size: var(--t-body);
}
.scroll-hint {
  position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
}
.scroll-hint span {
  display: block; width: 1px; height: 56px;
  background: linear-gradient(to bottom, transparent, var(--blue-hi));
  animation: drip 2.2s ease-in-out infinite;
}
@keyframes drip {
  0% { transform: scaleY(0); transform-origin: top; }
  45% { transform: scaleY(1); transform-origin: top; }
  55% { transform: scaleY(1); transform-origin: bottom; }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

/* ---- acts ---- */

.act {
  min-height: 130vh;
  display: flex; align-items: center;
  padding: 0 max(6vw, 32px);
}
.act-right { justify-content: flex-end; }
.act-copy {
  max-width: 500px;
  padding: 48px 0;
  background: radial-gradient(closest-side, rgba(3, 5, 9, 0.85), transparent);
}
.act-copy > p:not(.index):not(.stamp) { color: var(--dim); }

.convo {
  position: relative;
  margin-top: 30px; padding: 22px 24px;
  font-family: var(--mono); font-size: var(--t-meta); line-height: 2;
  border: 1px solid var(--hairline); border-radius: 10px;
  background: rgba(6, 10, 22, 0.75);
  box-shadow: 0 0 40px rgba(28, 92, 255, 0.10);
}
.convo::before {
  content: "";
  position: absolute; top: 17px; right: 18px;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--blue-hi);
  box-shadow: 0 0 8px rgba(89, 143, 255, 0.8);
  opacity: 0.35;
}
.convo p {
  display: grid; grid-template-columns: 58px 1fr; gap: 0 8px;
  align-items: baseline;
}
.convo .who {
  color: var(--faint);
  font-size: var(--t-micro); letter-spacing: 0.08em;
}
.convo .tool {
  color: var(--blue-hi); background: rgba(28, 92, 255, 0.10);
  border: 1px solid var(--hairline); border-radius: 4px;
  padding: 1px 7px; font-size: var(--t-micro); margin-right: 6px;
}
.act-copy.in .convo::before { animation: agent-status 7s ease-in-out 0.8s infinite; }
.act-copy.in .convo p:nth-child(2) .tool:first-child {
  animation: tool-execute 7s ease-in-out 0.8s infinite;
}
.act-copy.in .convo p:nth-child(2) .tool:nth-child(2) {
  animation: tool-execute-later 7s ease-in-out 0.8s infinite;
}
.act-copy.in .convo .ok { animation: confirm-post 7s ease-in-out 0.8s infinite; }
.act-copy.in .convo p:nth-child(4) .msg { animation: destinations-ready 7s ease-in-out 0.8s infinite; }
@keyframes agent-status {
  0%, 8%, 72%, 100% { opacity: 0.3; box-shadow: 0 0 7px rgba(89, 143, 255, 0.55); }
  16%, 56% { opacity: 1; box-shadow: 0 0 13px rgba(89, 143, 255, 1); }
}
@keyframes tool-execute {
  0%, 10%, 25%, 100% { color: var(--blue-hi); box-shadow: none; }
  15%, 20% { color: #fff; border-color: rgba(127, 168, 255, 0.75); box-shadow: 0 0 14px rgba(28, 92, 255, 0.5); }
}
@keyframes tool-execute-later {
  0%, 24%, 39%, 100% { color: var(--blue-hi); box-shadow: none; }
  29%, 34% { color: #fff; border-color: rgba(127, 168, 255, 0.75); box-shadow: 0 0 14px rgba(28, 92, 255, 0.5); }
}
@keyframes confirm-post {
  0%, 38%, 55%, 100% { text-shadow: none; }
  44%, 50% { color: #80f2c7; text-shadow: 0 0 12px rgba(76, 215, 164, 0.75); }
}
@keyframes destinations-ready {
  0%, 53%, 70%, 100% { color: inherit; text-shadow: none; }
  59%, 65% { color: var(--blue-hi); text-shadow: 0 0 10px rgba(28, 92, 255, 0.6); }
}
.ok { color: var(--ok); font-weight: 500; }

.stamp {
  margin-top: 26px;
  font-family: var(--mono); font-size: var(--t-ui); color: var(--ink);
  border-left: 2px solid var(--blue);
  padding: 6px 0 6px 18px;
  font-variant-numeric: tabular-nums;
}
.stamp .arrow { color: var(--blue-hi); padding: 0 6px; }

.log {
  margin-top: 30px;
  border: 1px solid var(--hairline); border-radius: 10px;
  background: rgba(6, 10, 22, 0.75);
  box-shadow: 0 0 40px rgba(28, 92, 255, 0.10);
  overflow: hidden;
}
.log figcaption {
  font-family: var(--mono); font-size: var(--t-micro); letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--faint);
  padding: 14px 20px 0;
}
.log pre { padding: 10px 20px 18px; overflow-x: auto; }
.log code {
  background: none; border: none; padding: 0;
  font-family: var(--mono); font-size: var(--t-meta); line-height: 2.1;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.log .t { color: var(--faint); }
.log b { font-weight: 600; }
.log-row {
  position: relative;
  display: block;
  margin-inline: -20px;
  padding-inline: 20px;
  border-left: 2px solid transparent;
  opacity: 0.62;
}
.act-copy.in .log-row {
  animation: log-delivery 14s cubic-bezier(0.45, 0, 0.35, 1) 1.2s infinite;
}
.act-copy.in .log-row:nth-child(2) { animation-delay: 4s; }
.act-copy.in .log-row:nth-child(3) { animation-delay: 6.8s; }
.act-copy.in .log-row .ok {
  animation: log-live 14s ease-in-out 1.2s infinite;
}
.act-copy.in .log-row:nth-child(2) .ok { animation-delay: 4s; }
.act-copy.in .log-row:nth-child(3) .ok { animation-delay: 6.8s; }
@keyframes log-delivery {
  0%, 4% {
    opacity: 0.62;
    background: linear-gradient(90deg, rgba(28, 92, 255, 0), transparent 72%);
    border-left-color: transparent;
  }
  18%, 23% {
    opacity: 1;
    background: linear-gradient(90deg, rgba(28, 92, 255, 0.055), transparent 72%);
    border-left-color: rgba(127, 168, 255, 0.34);
  }
  43%, 100% {
    opacity: 0.62;
    background: linear-gradient(90deg, rgba(28, 92, 255, 0), transparent 72%);
    border-left-color: rgba(76, 215, 164, 0.08);
  }
}
@keyframes log-live {
  0%, 38%, 100% { color: var(--ok); text-shadow: none; }
  16%, 25% { color: #72dfb5; text-shadow: 0 0 8px rgba(76, 215, 164, 0.3); }
}

/* ---- platforms strip ---- */

.strip { padding: 18vh max(6vw, 32px); text-align: center; }
.platforms {
  list-style: none;
  display: flex; flex-wrap: wrap; gap: 14px; justify-content: center;
}
.platforms li {
  font-family: var(--mono); font-size: var(--t-ui);
  border: 1px solid var(--hairline); border-radius: 999px;
  padding: 12px 26px;
  display: flex; gap: 12px; align-items: baseline;
  transition: border-color 0.25s, box-shadow 0.25s;
}
.platforms li:hover {
  border-color: var(--blue-dim);
  box-shadow: 0 0 24px rgba(28, 92, 255, 0.18);
}
.platforms .tag {
  font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ok);
}
.platforms .soon { color: var(--dim); }
.platforms .soon .tag { color: var(--faint); }

/* ---- control ---- */

.control { padding: 14vh max(6vw, 32px); max-width: 1060px; margin: 0 auto; }
.grid {
  margin-top: 48px;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
}
.grid > div { background: var(--bg); padding: 30px 32px; }
.grid dt {
  font-family: var(--mono); font-size: var(--t-meta); font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--blue-hi); margin-bottom: 12px;
}
.grid dd { color: var(--dim); font-size: var(--t-body); }

/* ---- connect ---- */

.connect { padding: 14vh max(6vw, 32px) 16vh; max-width: 760px; margin: 0 auto; }
.steps {
  margin: 30px 0 34px; padding-left: 0;
  list-style: none; counter-reset: step;
  color: var(--dim);
}
.steps li {
  counter-increment: step;
  padding: 10px 0 10px 44px; position: relative;
}
.steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute; left: 0; top: 12px;
  font-family: var(--mono); font-size: var(--t-micro); color: var(--blue-hi);
}
.snippet {
  position: relative; margin-bottom: 18px;
  border: 1px solid var(--hairline); border-radius: 10px;
  background: rgba(6, 10, 22, 0.75);
  box-shadow: 0 0 40px rgba(28, 92, 255, 0.08);
  overflow: hidden;
}
.snippet figcaption {
  font-family: var(--mono); font-size: var(--t-micro); letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--faint);
  padding: 12px 20px 0;
}
.snippet pre { padding: 12px 20px 18px; overflow-x: auto; }
.snippet code {
  background: none; border: none; padding: 0;
  font-size: var(--t-meta); line-height: 1.75; color: var(--ink);
}
.copy {
  position: absolute; top: 10px; right: 10px;
  font-family: var(--mono); font-size: var(--t-meta);
  color: var(--dim); background: none;
  border: 1px solid var(--hairline); border-radius: 5px;
  padding: 5px 12px; cursor: pointer;
  transition: color 0.2s, border-color 0.2s, scale 0.15s ease-out;
}
.copy:hover { color: var(--ink); border-color: var(--blue-dim); }
.connect .cta-row { margin-top: 40px; }

/* ---- footer ---- */

.home .foot-links { display: flex; gap: 18px; }
.home footer {
  display: flex; gap: 28px; flex-wrap: wrap;
  justify-content: space-between; align-items: center;
  padding: 34px max(6vw, 32px);
  border-top: 1px solid var(--hairline);
  font-family: var(--mono); font-size: var(--t-meta);
}

/* ---- in-scene authentication transition ---- */

body.auth-open { overflow: hidden; }
body.auth-open main,
body.auth-open footer {
  transform: translateX(-100vw);
  opacity: 0;
}
body.auth-open header.top {
  transform: translateX(calc(-50% - 100vw));
  opacity: 0;
}
/* Only the slide lives here: what's inside is the same .auth-shell /login
   lays out, and .auth-shell's margin:auto centres it in this flex box. */
.story-auth {
  position: fixed; inset: 0; z-index: 5;
  display: flex;
  padding: clamp(1.5rem, 4vw, 3.5rem);
  overflow-y: auto;
  transform: translateX(100vw);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.9s cubic-bezier(0.76, 0, 0.24, 1), opacity 0.7s ease,
    visibility 0s linear 0.9s;
  will-change: transform;
}
body.auth-open .story-auth {
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* ---- reveal on scroll ----------------------------------------------------
   Gated on html.js, which boot.js sets and then lifts again if the modules
   never turn up — so neither scripting being off nor a module failing to load
   can leave a page hidden. */

/* An animation, not a transition. A transition only runs if the browser
   painted the from-state first, and this class is added by a module during
   page load — whether a paint happened by then is a race the story won only
   because its preloaded scene delayed the module, and the panel lost every
   time, so its heading appeared instead of rising. Keyframes run from their
   first frame whenever they are attached, which makes the reveal the same on
   every page and on every connection. */
html.js .reveal { opacity: 0; }
html.js .reveal.in { animation: reveal-rise 0.7s ease forwards; }

html.js .act-copy { opacity: 0; }
html.js .act-copy.in { animation: reveal-rise-far 0.8s ease forwards; }

@keyframes reveal-rise {
  from { opacity: 0; transform: translateY(22px); }
  to { opacity: 1; transform: none; }
}
@keyframes reveal-rise-far {
  from { opacity: 0; transform: translateY(30px); }
  to { opacity: 1; transform: none; }
}

/* ---- responsive ---- */

@media (max-width: 720px) {
  header.top { top: 10px; width: calc(100% - 20px); padding: 9px 10px 9px 16px; border-radius: 16px; }
  .top-nav a, .top-meta select { border-radius: 7px; } /* 16 − 9px padding */
  .wordmark { font-size: var(--t-ui); }
  .nav-links { display: none; }
  .top-nav .top-cta { padding: 9px 14px; font-size: var(--t-meta); }
  .act { min-height: 110vh; padding: 0 24px; }
  .act-right { justify-content: flex-start; }
  .grid { grid-template-columns: 1fr; }
  .convo { font-size: var(--t-micro); padding: 16px; }
  .convo p { grid-template-columns: 46px 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.js .reveal, html.js .act-copy { opacity: 1; transform: none; animation: none; }
  .caret, .scroll-hint span, header.top::before, header.top::after,
  .act-copy.in .convo::before, .act-copy.in .convo .tool,
  .act-copy.in .convo .ok, .act-copy.in .convo p:nth-child(4) .msg,
  .act-copy.in .log-row, .act-copy.in .log-row .ok { animation: none; }
  .log-row { opacity: 1; }
  main, footer, header.top, .story-auth { transition-duration: .01ms; }
}

/* ---- form and text primitives ----------------------------------------------
   Written for the public site directly. These used to be amber operations-panel
   rules that every blue page overrode one at a time, which is what made small
   changes here a specificity fight. */

.hidden { display: none !important; } /* must beat .stack's display:flex */
.quiet { color: var(--dim); }
.small { font-size: var(--t-meta); }
.mono { font-family: var(--mono); font-size: var(--t-ui); }
.err { color: #e78c84; }

.stack { display: flex; flex-direction: column; gap: 1.35rem; }
label {
  display: flex; flex-direction: column; gap: .6rem;
  color: #9ca7ba;
  font: 500 var(--t-meta) var(--mono);
  letter-spacing: .05em;
}
.hint { color: var(--dim); font-size: var(--t-meta); letter-spacing: 0; }

input:not([type="checkbox"]) {
  width: 100%;
  padding: 1.05rem 1.5rem;
  border: 1px solid rgba(140, 170, 255, .15);
  border-radius: 999px;
  background: rgba(2, 5, 12, .72);
  color: var(--ink);
  /* A field is read at the size of what it will hold, not at the size of the
     furniture around it. */
  font: var(--t-body) var(--mono);
  transition: border-color .25s, box-shadow .25s, background .25s;
}
input::placeholder { color: #4f596a; }
/* The ring on .field is the focus cue now, so this only deepens the well it
   sits in — no second outline competing with the light. */
input:not([type="checkbox"]):focus {
  outline: none;
  border-color: rgba(95, 143, 255, .35);
  background: rgba(4, 9, 22, .92);
}

/* Reset rather than style: every button here carries a class that says what
   kind it is. Without this an unclassed one arrives in the platform's grey,
   which on a black page is the loudest thing on it. */
/* Text fields carry the same travelling edge as the header pill — the same
   registered angle, the same conic ring masked to the border — but only while
   the field has focus. A light running the border is worth watching once; on
   eight fields sitting at rest it is just noise. An input cannot own a
   pseudo-element, so the ring lives on a wrapper. */
.field { position: relative; display: block; width: 100%; border-radius: 999px; }
.field::before, .field::after {
  content: "";
  position: absolute; inset: -1px;
  z-index: 1; pointer-events: none;
  padding: 1.5px;
  border-radius: inherit;
  background: conic-gradient(
    from var(--menu-angle),
    transparent 0 41%,
    rgba(74, 126, 255, 0.06) 44%,
    rgba(102, 151, 255, 0.95) 49%,
    #d2e0ff 50%,
    rgba(65, 119, 255, 0.82) 51%,
    rgba(28, 92, 255, 0.04) 56%,
    transparent 59% 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  filter: drop-shadow(0 0 5px rgba(60, 116, 255, 0.9));
  opacity: 0;
  transition: opacity .35s ease;
}
.field:focus-within::before, .field:focus-within::after {
  opacity: 1;
  animation: menu-orbit 5.5s linear infinite;
}
.field:focus-within::after {
  opacity: .78;
  --menu-angle: 180deg;
  animation-name: menu-orbit-reverse;
}

button {
  border: 0; border-radius: 0;
  background: none; color: inherit;
  font: 500 var(--t-ui) var(--mono); cursor: pointer;
}
[aria-busy="true"] { opacity: .55; cursor: progress; }

.tabs { display: flex; gap: 1.4rem; margin: 0 0 1.5rem; border-bottom: 1px solid rgba(140, 170, 255, .12); }
.tab {
  padding: .45rem 0 .7rem;
  border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  background: transparent; color: var(--dim);
  letter-spacing: .04em;
}
.tab:hover { color: var(--ink); }
.tab.active { color: #8fb1ff; border-bottom-color: var(--blue); }

.row-between { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }

.notice {
  margin: 1rem 0 0; padding: .65rem .75rem;
  border: 1px solid rgba(95, 143, 255, .2); border-radius: 8px;
  background: rgba(28, 92, 255, .06);
  color: #9cb9ff;
  font: var(--t-meta)/1.5 var(--mono);
}
.notice.err { border-color: rgba(196, 85, 77, .28); background: rgba(196, 85, 77, .06); color: #e78c84; }

.legal { max-width: 46rem; }

/* ---- authentication, reading and blog pages -------------------------------- */

body.auth-page {
  min-height: 100vh;
  min-height: 100svh;
  overflow-x: hidden;
  background:
    radial-gradient(ellipse at 68% 52%, rgba(28, 92, 255, 0.12), transparent 34%),
    radial-gradient(ellipse at 50% 115%, rgba(28, 92, 255, 0.17), transparent 48%),
    var(--bg);
}
/* The shared scene canvas — same element and same module as the marketing
   page, opened straight into the auth pose. */
#scene {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: 0; pointer-events: none;
}
.auth-page main {
  position: relative; z-index: 1;
  max-width: none; min-height: 100vh; min-height: 100svh;
  display: flex; flex-direction: column;
  padding: clamp(1.5rem, 4vw, 3.5rem);
}
.auth-shell {
  width: min(1020px, 100%);
  margin: auto;
  display: grid; grid-template-columns: minmax(0, .92fr) minmax(360px, 1fr);
  gap: clamp(3rem, 8vw, 7.5rem);
  align-items: center;
}
/* Reset, verification and invite acceptance have no story column to balance,
   so they centre a single card under the wordmark. */
.auth-shell.solo {
  width: min(30rem, 100%);
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}
.solo .auth-brand { margin-bottom: clamp(1.4rem, 4vh, 2.4rem); }

.auth-intro { max-width: 31rem; }
.auth-brand {
  display: inline-flex; align-items: center;
  margin-bottom: clamp(4rem, 9vh, 7rem);
  color: var(--ink); font: 500 var(--t-body) var(--mono);
  letter-spacing: -.02em;
}
.auth-brand:hover { text-decoration: none; }
.auth-brand span {
  width: 7px; height: 15px; margin-left: 4px;
  background: #1c5cff;
  box-shadow: 0 0 10px rgba(28, 92, 255, .7);
  animation: auth-caret 1.4s steps(1) infinite;
}
@keyframes auth-caret { 50% { opacity: .18; } }
.auth-kicker,
.auth-card-label,
.auth-status,
.auth-trust {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: .16em;
}
.auth-kicker {
  margin: 0 0 1.1rem;
  color: #7fa8ff; font-size: var(--t-micro);
}
/* h1 on /login, h2 inside the landing panel — same heading, one level down
   where the hero already holds the h1. See authshell-heading in the templates. */
.auth-intro :is(h1, h2) {
  max-width: 32rem; margin: 0;
  color: var(--ink);
  font: 600 clamp(2rem, 4vw, 3.5rem)/1.06 var(--mono);
  letter-spacing: -.055em;
  text-wrap: balance;
  text-shadow: 0 0 45px rgba(28, 92, 255, .17);
}
.auth-intro :is(h1, h2)::before { content: none; }
.auth-copy {
  max-width: 29rem; margin: 1.5rem 0 1.8rem;
  color: var(--dim); font-size: var(--t-body); line-height: 1.65;
}
.auth-back {
  color: #7f8ba0; font: 500 var(--t-meta) var(--mono);
  transition: color .2s;
}
.auth-back:hover { color: var(--ink); text-decoration: none; }

.auth-card {
  position: relative; isolation: isolate;
  margin: 0; padding: clamp(1.5rem, 3vw, 2.15rem);
  border: 1px solid rgba(127, 168, 255, .2);
  border-radius: 18px;
  background: rgba(6, 10, 22, .78);
  -webkit-backdrop-filter: blur(22px) saturate(125%);
  backdrop-filter: blur(22px) saturate(125%);
  box-shadow:
    0 28px 80px rgba(0, 0, 0, .46),
    0 0 55px rgba(28, 92, 255, .1),
    inset 0 1px rgba(255, 255, 255, .035);
}
.auth-card::before {
  content: "";
  position: absolute; inset: -1px; z-index: -1;
  padding: 1px; border-radius: inherit;
  background: linear-gradient(135deg, rgba(127, 168, 255, .72), transparent 28%, transparent 68%, rgba(28, 92, 255, .5));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
/* The card breathes with the scene behind it rather than on its own timer.
   --scene-glow is published by the world once per frame; the blur here is
   static, so only opacity and transform change and the compositor handles
   both. Animating box-shadow instead — the previous approach — repainted the
   card on every frame while the WebGL loop was already running. The fallback
   keeps the glow present when the scene never starts. */
.auth-card::after {
  content: "";
  position: absolute; inset: 12% -18% -18%; z-index: -2;
  border-radius: 45%; pointer-events: none;
  background: radial-gradient(ellipse, rgba(28, 92, 255, .28), transparent 68%);
  filter: blur(22px);
  opacity: calc(.45 + var(--scene-glow, .5) * .6);
  transform: scale(calc(1 + var(--scene-glow, .5) * .05));
  will-change: opacity, transform;
}
.auth-card-head {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  margin-bottom: 1.45rem;
}
.auth-card-label { margin: 0; color: #aeb9ca; font-size: var(--t-micro); }
/* Beats the amber, lowercased h2 the operations panel above sets. */
.auth-card h2 {
  margin: 0 0 .5rem;
  color: var(--ink);
  font: 600 var(--t-body)/1.2 var(--mono);
  letter-spacing: -.035em;
  text-transform: none;
}
.auth-card h2.err { color: #e78c84; }
.auth-card .auth-copy { margin: 0 0 1.5rem; font-size: var(--t-ui); }
.auth-status {
  display: inline-flex; align-items: center; gap: .45rem;
  color: #748096; font-size: var(--t-micro); letter-spacing: .1em;
}
.auth-status i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--ok); box-shadow: 0 0 8px rgba(76, 215, 164, .65);
}
.auth-card .tabs {
  gap: 1.4rem; margin: 0 0 1.5rem;
  border-bottom-color: rgba(140, 170, 255, .12);
}
.auth-card .tab {
  padding: .45rem 0 .7rem;
  border-radius: 0;
  font: 500 var(--t-ui) var(--mono);
  letter-spacing: .04em;
}
.auth-card .tab:hover { filter: none; color: var(--ink); }
.auth-card .tab.active { color: #8fb1ff; border-bottom-color: #1c5cff; }
/* The login-methods rows reuse .row; only the confirm field is new, and it is
   deliberately quiet — it sits under a list nobody edits most visits. */
.login-confirm {
  display: flex; flex-direction: column; gap: .4rem;
  margin: 1rem 0 0; max-width: 22rem;
  color: #9ca7ba;
  font: 500 var(--t-meta) var(--mono); letter-spacing: .05em;
}

/* Social login. Outlined rather than filled: these sit above the primary
   submit, and a stack of solid buttons would outshout the control that
   actually finishes the form. */
.social-logins { display: grid; gap: .55rem; margin: 0 0 1.15rem; }
.social-logins form { margin: 0; }
.social-btn {
  width: 100%; padding: .8rem 1.1rem;
  border: 1px solid rgba(127, 168, 255, .26); border-radius: 999px;
  background: rgba(127, 168, 255, .05); color: var(--ink);
  letter-spacing: .02em;
  transition: border-color .2s, background .2s;
}
.social-btn:hover { border-color: rgba(127, 168, 255, .5); background: rgba(127, 168, 255, .1); }
.social-divider {
  display: flex; align-items: center; gap: .8rem;
  margin: 0 0 1.25rem;
  color: var(--dim);
  font: var(--t-meta) var(--mono); letter-spacing: .05em;
}
.social-divider::before, .social-divider::after {
  content: ""; flex: 1; height: 1px;
  background: rgba(140, 170, 255, .14);
}

.auth-card .stack { gap: 1rem; margin-top: 0; }
.auth-card label {
  gap: .42rem;
  color: #9ca7ba;
  font: 500 var(--t-meta) var(--mono);
  letter-spacing: .05em;
}
.auth-card .row-between { margin: .25rem 0 0; }
.auth-submit {
  position: relative; isolation: isolate;
  display: inline-flex; justify-content: center; align-items: center; gap: .85rem;
  /* The arrow side is 2px tighter than the text side; equal padding reads as
     the arrow being pushed out past the label. */
  min-width: 8.5rem; padding: .95rem 1.63rem .95rem 1.75rem;
  border: 1px solid #2d67ff; border-radius: 999px;
  background: #1c5cff; color: #fff;
  font: 500 var(--t-ui) var(--mono);
  box-shadow: 0 0 22px rgba(28, 92, 255, .22);
  transition: background .25s, box-shadow .25s, transform .25s, scale .15s ease-out;
}
/* The primary control picks up the same pulse. Negative z-index puts the
   bloom above the button's fill but below its label, so the text stays crisp
   while the halo spills past the edges. */
.auth-submit::after {
  content: "";
  position: absolute; inset: -55% -14%; z-index: -1;
  pointer-events: none;
  background: radial-gradient(ellipse, rgba(120, 162, 255, .5), transparent 68%);
  filter: blur(12px);
  opacity: calc(.18 + var(--scene-glow, .5) * .26);
  will-change: opacity;
}
.auth-submit span { transition: transform .25s; }
.auth-submit:hover {
  filter: none; background: #316bff;
  box-shadow: 0 0 32px rgba(28, 92, 255, .42);
  transform: translateY(-1px);
}
.auth-submit:hover span { transform: translateX(3px); }
/* In a column of fields the control sizes to its label; stretching it to the
   field width makes the form read as two stacked inputs. */
.stack > .auth-submit, a.auth-submit { align-self: flex-start; }
a.auth-submit:hover { text-decoration: none; color: #fff; }
.auth-card #forgot-link { font: 500 var(--t-meta) var(--mono); }
.auth-card #forgot-link:hover { color: #a9beef; text-decoration: none; }
.auth-form-note { margin: 0 0 .3rem; color: var(--dim); font-size: var(--t-ui); }
.auth-card .notice {
  margin: 1rem 0 0; padding: .65rem .75rem;
  border: 1px solid rgba(95, 143, 255, .2); border-radius: 8px;
  background: rgba(28, 92, 255, .06);
  color: #9cb9ff;
  font: var(--t-meta)/1.5 var(--mono);
}
.auth-card .notice.err { border-color: rgba(196, 85, 77, .28); background: rgba(196, 85, 77, .06); color: #e78c84; }
.auth-trust {
  margin: 1.5rem 0 0; padding-top: 1rem;
  border-top: 1px solid rgba(140, 170, 255, .1);
  color: #566174; font-size: var(--t-micro); line-height: 1.6; text-align: center;
  /* Three claims that no longer fit on one line at a readable size. Balanced,
     so it breaks into two even lines instead of orphaning "revocable access"
     under a full one. */
  letter-spacing: .1em; text-wrap: balance;
}
.auth-page footer {
  align-self: center; position: relative; z-index: 2;
  margin: clamp(2rem, 6vh, 4rem) 0 0;
  font-family: var(--mono);
}
.auth-page footer a { color: #657084; }

/* Legal pages: the same cool-blue ground as login, but a reading column
   instead of a centred card — and deliberately no canvas, because a moving
   scene behind long prose is an obstacle rather than an atmosphere. */
/* The login pages paint their glow straight onto the body, which is fine when
   nothing scrolls. These documents do scroll — and so does the control panel,
   which shares this ground — so the glow moves to its own fixed layer;
   background-attachment: fixed would repaint the whole viewport on every
   scroll frame. */
.read-page, .panel-page { background: var(--bg); }
.read-page::before, .panel-page::before {
  content: "";
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse at 68% 52%, rgba(28, 92, 255, 0.12), transparent 34%),
    radial-gradient(ellipse at 50% 115%, rgba(28, 92, 255, 0.17), transparent 48%);
}
.read-page main {
  display: block; min-height: 0;
  width: min(46rem, 100%); margin-inline: auto;
  padding: clamp(2.5rem, 7vh, 5rem) clamp(1.25rem, 5vw, 2rem) 3rem;
}
.read-page .auth-brand { margin-bottom: clamp(2rem, 6vh, 3.2rem); }
/* The menu is fixed over the page, so the column starts below it — where the
   wordmark it replaces would have ended. */
.read-page header.top + main { padding-top: calc(86px + clamp(2rem, 6vh, 3.2rem)); }
.read-page .legal { max-width: none; }
.read-page h1 {
  margin: 0 0 .5rem;
  color: var(--ink);
  font: 600 var(--t-h2)/1.1 var(--mono);
  letter-spacing: -.045em;
}
.read-page h1::before { content: none; }
.read-page h2 {
  margin: 2.4rem 0 .55rem;
  color: #8fb1ff;
  font: 600 var(--t-body) var(--mono);
  letter-spacing: -.01em; text-transform: none;
}
.read-page p { margin: 0 0 .9rem; color: #b0bacb; font-size: var(--t-body); line-height: 1.7; }
.read-page .quiet { color: var(--dim); }
.read-page .mono { font-family: var(--mono); color: #cdd6e4; }
/* main, not the page: the menu above it keeps its own colours. :where keeps
   the weight this rule had before, so the footer's grey still wins. */
.read-page :where(main) a { color: #8fb1ff; }
.read-page .legal-nav { margin-top: 2rem; padding-top: 1.2rem; border-top: 1px solid rgba(140, 170, 255, .12); }
.read-page footer { text-align: center; }
/* Cross-document transitions: a full navigation crossfades instead of
   flashing white. Browsers without support navigate exactly as before. The
   same rule lives in the marketing stylesheet — both documents in a
   navigation have to opt in for it to happen. */
@view-transition { navigation: auto; }

/* The post title is a shared element: the browser moves the same box from its
   row on the index to the article heading. Slower than the surrounding
   crossfade, because it is the thing worth following. */
::view-transition-group(*) { animation-duration: .3s; }
::view-transition-group(post-title) {
  animation-duration: .45s;
  animation-timing-function: cubic-bezier(.4, 0, .2, 1);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* After every swap the router lands focus on the new page's h1 so a screen
   reader follows the navigation. The move is the router's, not the reader's,
   so no page draws a ring for it — one rule at the router's level rather
   than a copy per page. */
#page h1:focus-visible { outline: none; }

.read-head { margin-bottom: 2.6rem; }
.read-head .quiet { margin: .6rem 0 0; }

/* Blog. The index is a list of links, not cards: a date, a title, a line of
   summary. Post bodies come from markdown, so every element it can emit needs
   a rule here. */
/* Scoped to .read-page so it outranks the generic list rule below. */
.read-page .post-list { list-style: none; margin: 0; padding: 0; }
.post-list li { margin: 0; border-top: 1px solid rgba(140, 170, 255, .12); }
.post-list a {
  display: flex; flex-direction: column; gap: .3rem;
  padding: 1.35rem 0;
  color: inherit;
}
.post-list a:hover { text-decoration: none; }
.post-meta {
  color: #6f7b8f;
  font: 500 var(--t-micro) var(--mono);
  text-transform: uppercase; letter-spacing: .13em;
}
.post-title {
  color: var(--ink);
  font: 600 var(--t-body)/1.25 var(--mono);
  letter-spacing: -.03em;
  transition: color .2s;
}
.post-list a:hover .post-title { color: #8fb1ff; }
.post-summary { color: #98a3b5; font-size: var(--t-ui); line-height: 1.6; }

.post .read-head .post-meta { margin: 0 0 .9rem; }
.post .read-head .post-meta a { color: #6f7b8f; }
.post-lede { margin-top: .8rem; color: #98a3b5; font-size: var(--t-body); }
.read-page h3 {
  margin: 1.9rem 0 .5rem;
  color: #aeb9ca;
  font: 600 var(--t-ui) var(--mono);
  letter-spacing: .02em;
}
.read-page :is(ul, ol) { margin: 0 0 1rem; padding-left: 1.4rem; color: #b0bacb; font-size: var(--t-body); line-height: 1.7; }
.read-page li { margin-bottom: .35rem; }
.read-page strong { color: var(--ink); font-weight: 600; }
.read-page code {
  padding: .1em .38em; border-radius: 4px;
  border: 1px solid rgba(140, 170, 255, .14);
  background: rgba(28, 92, 255, .07);
  color: #b9cbf2;
  font: .84em var(--mono);
}
.read-page pre {
  margin: 0 0 1.2rem; padding: .95rem 1.1rem;
  border: 1px solid rgba(140, 170, 255, .14); border-radius: 10px;
  background: rgba(2, 5, 12, .6);
  overflow-x: auto;
}
.read-page pre code { padding: 0; border: 0; background: none; font-size: var(--t-meta); line-height: 1.6; }
.read-page blockquote {
  margin: 0 0 1.2rem; padding: .2rem 0 .2rem 1.1rem;
  border-left: 2px solid rgba(95, 143, 255, .45);
  color: #9aa5b8; font-size: var(--t-body);
}
.read-page blockquote p:last-child { margin-bottom: 0; }
/* A pure-white hairline, not the blue one the rest of the page uses: an
   outline that picks up a tint reads as dirt along the image edge. Inset, so
   it costs the image no layout. */
.read-page img {
  max-width: 100%; height: auto; border-radius: 10px; margin: .4rem 0 1.2rem;
  outline: 1px solid rgba(255, 255, 255, .1); outline-offset: -1px;
}
.read-page hr { margin: 2.2rem 0; border: 0; border-top: 1px solid rgba(140, 170, 255, .12); }
.read-page table { width: 100%; margin: 0 0 1.2rem; border-collapse: collapse; font-size: var(--t-ui); }
.read-page :is(th, td) { padding: .5rem .6rem; border-bottom: 1px solid rgba(140, 170, 255, .12); text-align: left; }
.read-page th { color: #aeb9ca; font: 600 var(--t-micro) var(--mono); text-transform: uppercase; letter-spacing: .1em; }
.read-page td { color: #b0bacb; }

/* Pricing. Wider than prose: the cards are compared side by side. The
   interval radios pick which offer each card shows with :has(), so a browser
   without it shows both rather than neither. */
.pricing-page main { width: min(68rem, 100%); }
.cadence { position: relative; display: inline-flex; gap: 2px; margin: 0 0 1.4rem; padding: 3px; border: 1px solid var(--hairline); border-radius: 8px; }
.cadence input { position: absolute; opacity: 0; pointer-events: none; }
.cadence label {
  display: block; padding: 7px 16px; border-radius: 6px; cursor: pointer;
  color: var(--dim); font: 500 var(--t-ui) var(--mono);
  transition: background .2s, color .2s;
}
.cadence input:checked + label { background: var(--blue); color: #fff; }
.cadence input:focus-visible + label { outline: 2px solid var(--blue-hi); outline-offset: 2px; }
.has-cadence:has(#cadence-year:checked) .price-offer:not(.cad-year),
.has-cadence:not(:has(#cadence-year:checked)) .price-offer:not(.cad-month) { display: none; }

.price-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; }
.price-card {
  display: flex; flex-direction: column;
  padding: 1.5rem; border: 1px solid var(--hairline); border-radius: 12px;
  background: rgba(10, 16, 30, .72);
}
.read-page .price-card h2 { margin: 0 0 1rem; color: var(--ink); font-size: var(--t-body); }
.read-page .price-amount { margin: 0; color: var(--dim); font-size: var(--t-ui); }
.price-amount b { color: var(--ink); font: 600 2.1rem/1.1 var(--mono); letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
.read-page .price-note { margin: .35rem 0 1.2rem; color: var(--dim); font-size: var(--t-meta); line-height: 1.5; }
.price-card .btn { display: block; text-align: center; }
.read-page .price-card .btn-primary { color: #fff; }
.read-page .price-card a.btn:hover { text-decoration: none; }
.read-page .price-limits { list-style: none; margin: 1.4rem 0 0; padding: 0; font-size: var(--t-ui); line-height: 1.5; }
.read-page .price-limits li { margin: 0; padding: .45rem 0; border-top: 1px solid rgba(140, 170, 255, .1); }
.price-legal { margin-top: 2rem; max-width: 46rem; }
.read-page .price-legal p { color: var(--dim); font-size: var(--t-meta); line-height: 1.6; }

@media (max-width: 820px) {
  .auth-page main { padding: 1.25rem; }
  .auth-shell { grid-template-columns: 1fr; gap: 2.25rem; max-width: 32rem; }
  .auth-intro { text-align: center; }
  .auth-brand { margin-bottom: 2.8rem; }
  .auth-copy { margin-inline: auto; }
  .auth-intro :is(h1, h2) { font-size: clamp(1.85rem, 8vw, 2.7rem); }
  .auth-back { display: none; }
  /* In the sliding panel the back link is the close control — keep it. */
  .story-auth .auth-back { display: inline-block; margin-top: 1rem; }
}
@media (max-width: 460px) {
  .auth-page main { padding: .8rem; }
  .auth-intro { padding: 1rem .5rem 0; }
  .auth-brand { margin-bottom: 2rem; }
  .auth-copy { font-size: var(--t-ui); }
  .auth-card { padding: 1.25rem; border-radius: 15px; }
  .auth-status { display: none; }
  .auth-card .row-between { align-items: flex-start; flex-direction: column-reverse; }
  .auth-submit { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  /* The card and button glows need no rule here: the world publishes
     --scene-glow exactly once when motion is reduced, so they hold still. */
  .auth-brand span { animation: none; }
  .auth-submit, .auth-submit span { transition: none; }
}

/* ---- control panel ----------------------------------------------------------
   The authenticated surface, on the same ground and the same shell as the rest
   of the site. Structure carried over from the story: a floating pill of
   chrome, numbered sections, hairline rows. Data stays mono because tokens,
   handles and times ARE the data — that has always been true of this page and
   is the one thing worth keeping from the panel it replaces. */

.panel-page {
  min-height: 100vh;
  min-height: 100svh;
  overflow-x: hidden;
}
/* The header stays out of this for the same reason it does on the story: it is
   fixed, and a position here would beat the rule that fixes it. */
.panel-page main {
  position: relative; z-index: 1;
  display: block;
  padding: clamp(6.5rem, 13vh, 8.5rem) clamp(1rem, 4vw, 2rem) 4rem;
}
.panel-shell,
.panel-page main > footer { width: min(1100px, 100%); margin-inline: auto; }
.panel-section:last-of-type { margin-bottom: clamp(2rem, 6vh, 4rem); }
.panel-page main > footer {
  margin-top: 2.5rem; padding-top: 2rem;
  border-top: 1px solid rgba(140, 170, 255, .1);
  font-family: var(--mono);
}
.panel-page main > footer a { color: var(--faint); }
.panel-page main > footer a:hover { color: var(--ink); }
/* Clears the fixed header when a section link jumps to one. */
.panel-page section[id] { scroll-margin-top: 6.5rem; }

.top-meta { display: flex; align-items: center; gap: 7px; }
.top-meta select {
  padding: 8px 10px; border-radius: 8px;
  border: 1px solid rgba(140, 170, 255, .16);
  background: rgba(2, 5, 12, .72); color: var(--ink);
  font: 500 var(--t-meta) var(--mono);
}
.top-meta select:focus-visible { outline: none; border-color: rgba(95, 143, 255, .72); }
.top-org {
  padding: 0 6px; color: var(--faint);
  font: 500 var(--t-meta) var(--mono);
}
.nav-links a.active { color: #8fb1ff; background: rgba(28, 92, 255, .10); }

/* ---- page head ---- */

.panel-head {
  min-height: 58vh;
  display: flex; flex-direction: column; justify-content: center;
  margin-bottom: clamp(3rem, 8vh, 6rem);
}
.panel-head .index { margin-bottom: 18px; }
.panel-page h1 {
  max-width: 22ch; margin: 0;
  font: 600 var(--t-display)/1.06 var(--mono);
  letter-spacing: -.05em; text-wrap: balance;
  text-shadow: 0 0 60px rgba(28, 92, 255, .35);
}
.panel-id {
  margin-top: 1.1rem;
  color: var(--faint); font: 500 var(--t-meta) var(--mono); letter-spacing: .04em;
}
.panel-id .mono { color: #b0bacb; font-size: inherit; }
.panel-id .sep { color: rgba(140, 170, 255, .3); padding: 0 .35rem; }

/* ---- sections ---- */

/* No card. The story never puts its copy in a box either — it sinks a pool of
   the background behind it and lets the scene carry the rest of the frame.
   Same device here, widened: the rows need the whole column, so the pool is an
   ellipse across it rather than the story's closest-side circle. */
/* Two columns, the way the story sets its copy against the scene: the number
   and the heading hold one side and stay there while the data scrolls past
   them. Below the breakpoint they stack, and the label leads its section. */
.panel-section {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
  gap: clamp(1.5rem, 4.5vw, 4.5rem);
  margin-bottom: clamp(3rem, 8vh, 6rem);
  padding: clamp(2rem, 5vh, 3.25rem) clamp(1rem, 2.5vw, 2rem);
}
.panel-section-head {
  position: sticky; top: 7rem;
  align-self: start;
}
.panel-section::before {
  content: "";
  position: absolute; inset: -6% -8%;
  z-index: -1; pointer-events: none;
  background: radial-gradient(60% 55% at 50% 50%,
    rgba(3, 5, 9, .96), rgba(3, 5, 9, .82) 58%, transparent 88%);
}
.panel-section .index { margin-bottom: 14px; }
.panel-page h2 {
  margin: 0;
  color: var(--ink);
  font: 600 var(--t-h2)/1.12 var(--mono); letter-spacing: -.045em;
  text-wrap: balance;
}
.panel-page h3 {
  margin: 3rem 0 .9rem;
  color: #aeb9ca;
  font: 600 var(--t-meta) var(--mono);
  text-transform: uppercase; letter-spacing: .16em;
}
/* The body column reads a step up from the chrome around it: this is the
   column someone is here to use, and at interface sizes its prose and its
   disclosures were the smallest text on a page about their own accounts. The
   section's number and heading hold the left column and keep their sizes —
   raising those too would make the page a poster rather than a panel. */
.panel-body > p.quiet { color: var(--dim); font-size: var(--t-body); }
.panel-body > p.quiet.small, .panel-body .row .small { font-size: var(--t-ui); }
.panel-body label { font-size: var(--t-ui); }
.panel-body > .rows:first-child { margin-top: -.7rem; } /* the first row's own padding is the gap */
/* Short prose everywhere on this page: stop the last line landing on one
   orphaned word. Not balance — these are not headings. */
.panel-body p, .hint, .post-summary, .read-page p, .read-page li { text-wrap: pretty; }
/* The closing aside under a section's controls. */
details.expander + p.quiet, .rows + p.quiet { margin-top: 1.2rem; }

/* ---- rows ---- */

.rows { display: flex; flex-direction: column; margin-bottom: .4rem; }
.row {
  display: flex; align-items: center; gap: .9rem;
  margin: 0 -.9rem; padding: 1rem .9rem;
  border-top: 1px solid rgba(140, 170, 255, .1);
  border-radius: 8px;
  /* A row hover happens constantly; it gets the cheapest transition that
     still reads as a response, and nothing longer. */
  transition: background-color .12s ease-out;
  /* Times and dates sit in columns that re-render under the reader. */
  font-variant-numeric: tabular-nums;
}
.row:first-child { border-top: 0; }
.row:hover { background: rgba(28, 92, 255, .055); }
.row .grow { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .platform { width: 6.2rem; flex-shrink: 0; color: var(--faint); }
/* Handles, token names and scheduled times are the row — they read at the size
   of the column they are in, not at the size of the chrome. The two fixed
   columns above are sized for these: 6.2rem holds "mastodon" and 11rem holds a
   full "Wed 13 Aug 09:00". */
.panel-body .row .mono { font-size: var(--t-body); }

/* A connection is four short things and stays on one line. The other two kinds
   carry a post or a list of scopes, which lost every fight for the width they
   were in — so they become a small grid instead: fixed columns of meta on the
   first line, and the thing the row is actually about on the second, with the
   whole column to itself. */
.row.post, .row.token {
  display: grid;
  align-items: center;
  column-gap: .9rem; row-gap: .3rem;
}
/* Fixed widths, so status and time read down the column rather than shifting
   with the length of each label. */
.row.post {
  /* 7rem, not 6.4: at 11px with .13em of tracking the longest status label
     ("publishing") measures ~101px, which left under a pixel of headroom — and
     the track is fixed, so the pill would have spilled rather than grown. */
  grid-template-columns: 7rem 11rem minmax(0, 1fr);
  grid-template-areas: "chip when targets" "body body body";
}
.row.post .chip { grid-area: chip; text-align: center; }
.row.post .when { grid-area: when; color: #b0bacb; }
.row.post .targets { grid-area: targets; }
.row.post .grow { grid-area: body; }

.row.token {
  /* The middle track is fixed for the same reason the post row's are: each row
     is its own grid, so an `auto` sized to "used never" and one sized to a full
     timestamp put the column in a different place on every line. 11rem holds
     "used 2026-08-12 15:04". */
  grid-template-columns: minmax(0, 1fr) 11rem auto;
  /* Three lines now: what it is called, what it may do, and which handles it
     may act as. The account scope earns its own line because it is the answer
     to "what can this thing actually reach", and reading it should not depend
     on hovering anything. */
  grid-template-areas: "name used action" "perms perms action" "scope scope action";
}
.row.token .grow { grid-area: name; display: flex; align-items: center; gap: .5rem; }
.row.token .used { grid-area: used; }
.row.token .perms { grid-area: perms; }
.row.token .scope { grid-area: scope; min-width: 0; }
.row.token .ghost { grid-area: action; }
/* Destinations are a short list in a wide track, so they clip rather than push
   the row around. The scopes wrap instead: they have a line to themselves, and
   a title tooltip is not a disclosure a phone can open. */
.row .targets { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .perms { min-width: 0; }

.chip {
  flex-shrink: 0;
  padding: .22rem .6rem; border-radius: 999px;
  border: 1px solid rgba(140, 170, 255, .18);
  color: var(--faint);
  font: 500 var(--t-micro) var(--mono);
  text-transform: uppercase; letter-spacing: .13em; white-space: nowrap;
}
/* Two states carry meaning here — working, and wanting attention. Anything
   mid-flight stays neutral rather than earning a third hue. */
.chip.active, .chip.published { color: var(--ok); border-color: rgba(76, 215, 164, .4); }
.chip.scheduled { color: #8fb1ff; border-color: var(--blue-dim); }
/* A connector was granted through a consent screen, not pasted in by hand.
   Revoking one cuts a live agent off, so the row says which kind it is before
   anyone reaches for the button. */
.chip.connector { color: #8fb1ff; border-color: var(--blue-dim); letter-spacing: .1em; }
.chip.needs_reauth, .chip.failed, .chip.partial { color: #e78c84; border-color: rgba(196, 85, 77, .42); }
/* Billing reuses the same two: a trial is working, and a payment that failed
   or an account closing wants attention. */
.chip.trialing { color: var(--ok); border-color: rgba(76, 215, 164, .4); }
.chip.past_due, .chip.closing { color: #e78c84; border-color: rgba(196, 85, 77, .42); }

/* ---- billing ---- */

/* The portal links are navigations dressed as the row buttons beside them. */
a.ghost { display: inline-block; }
.portal-links { display: flex; flex-wrap: wrap; gap: .6rem; }
.plan-preview, .billing-wanted {
  margin: 1.2rem 0 .4rem; padding: 1rem 1.1rem;
  border: 1px solid var(--blue-dim); border-radius: 9px;
  background: rgba(28, 92, 255, .05);
}
.plan-preview p, .billing-wanted p { margin: 0 0 .6rem; }
.billing-wanted p:last-child { margin-bottom: 0; }
.plan-preview-head { color: var(--ink); }
.plan-preview-due { font-variant-numeric: tabular-nums; }
.plan-preview-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; margin-top: 1rem; }

/* ---- banners ---- */

.banner {
  margin-bottom: 1.5rem; padding: .7rem .95rem;
  border: 1px solid rgba(196, 85, 77, .32); border-radius: 9px;
  background: rgba(196, 85, 77, .07);
  color: #e78c84; font: var(--t-meta)/1.6 var(--mono);
}
.banner.ok {
  margin: 0 0 .8rem;
  border-color: rgba(76, 215, 164, .32); background: rgba(76, 215, 164, .07);
  color: #7fe4bd;
}
.banner p { margin: 0; }
.verify-code {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin-top: .7rem;
}
.verify-code .field { flex: 0 0 11rem; }
.verify-code input { text-align: center; letter-spacing: .22em; }
/* The banner is its own colour, so a bare button reads as text beside the
   ghost one next to it. */
.verify-code button[type="submit"] {
  padding: .5rem 1.1rem; border-radius: 999px;
  border: 1px solid rgba(196, 85, 77, .45); background: rgba(196, 85, 77, .16);
  color: #f0a49c; font: inherit; cursor: pointer;
}
.verify-code button[type="submit"]:hover { background: rgba(196, 85, 77, .26); }

.panel-shell > .notice { margin: 0 0 1.5rem; }

/* ---- disclosures and forms ---- */

details.expander {
  margin-top: 1.9rem; padding-top: 1.9rem;
  border-top: 1px solid rgba(140, 170, 255, .1);
}
details.expander + details.expander { margin-top: .2rem; }
details.expander summary {
  display: flex; align-items: center; gap: .5rem;
  /* The summary paints nothing of its own, so this padding is invisible and
     the negative margin puts the layout back where it was — a 40px band for
     the pointer on a row that still reads as one line. */
  padding-block: 11px; margin-block: -11px;
  color: #8fb1ff; font: 500 var(--t-body) var(--mono);
  cursor: pointer; list-style: none;
  transition: color .2s;
}
details.expander summary::-webkit-details-marker { display: none; }
.expander-mark {
  display: grid; place-items: center;
  width: 1.15rem; height: 1.15rem;
  border: 1px solid var(--blue-dim); border-radius: 6px;
  font-size: var(--t-meta); line-height: 1;
}
/* Both glyphs stay in the box and cross-fade, so the swap has an exit as well
   as an enter — toggling the content of one pseudo-element has neither. */
.expander-mark::before, .expander-mark::after {
  grid-area: 1 / 1;
  transition: opacity .3s cubic-bezier(.2, 0, 0, 1),
              scale .3s cubic-bezier(.2, 0, 0, 1),
              filter .3s cubic-bezier(.2, 0, 0, 1);
}
.expander-mark::before { content: "+"; }
.expander-mark::after { content: "−"; opacity: 0; scale: .25; filter: blur(4px); }
details.expander[open] .expander-mark::before { opacity: 0; scale: .25; filter: blur(4px); }
details.expander[open] .expander-mark::after { opacity: 1; scale: 1; filter: blur(0); }
details.expander[open] summary { margin-bottom: 1.6rem; color: var(--ink); }
details.expander summary:hover { color: var(--ink); }
details.expander .stack { max-width: 30rem; }

fieldset {
  display: flex; flex-direction: column; gap: .5rem;
  padding: .8rem 1rem;
  border: 1px solid rgba(140, 170, 255, .14); border-radius: 9px;
}
legend {
  padding: 0 .4rem;
  color: var(--faint); font: 500 var(--t-meta) var(--mono);
  text-transform: uppercase; letter-spacing: .14em;
}
/* An aside inside a legend is a sentence, not a label — the caps the legend
   wears would have it shouting the exception louder than the rule. (Tracking
   needs no reset here: .hint sets its own.) */
legend .hint { text-transform: none; }
label.check {
  flex-direction: row; align-items: center; gap: .55rem;
  color: #b0bacb; font: var(--t-body) var(--sans); letter-spacing: 0;
  text-transform: none; cursor: pointer;
}
/* The platform draws a 13px box whatever the label does. Sized to the text it
   answers, so the box and its sentence read as one control. */
label.check input[type="checkbox"] {
  width: 15px; height: 15px; flex-shrink: 0; margin: 0; padding: 0;
  accent-color: var(--blue);
}
label.check .hint { margin-left: .2rem; }

/* A step under the row it sits in. Every row here ends in one of these, and at
   the row's own size three "disconnect"s read louder than the three accounts
   they belong to. */
.ghost {
  flex-shrink: 0;
  font-size: var(--t-meta);
  padding: .4rem .95rem; border-radius: 999px;
  border: 1px solid rgba(140, 170, 255, .16);
  color: var(--dim);
  transition: color .2s, border-color .2s, background .2s, scale .15s ease-out;
}
.ghost:hover { color: var(--ink); border-color: var(--blue-dim); background: rgba(28, 92, 255, .06); }
.ghost.danger:hover { color: #e78c84; border-color: rgba(196, 85, 77, .5); background: rgba(196, 85, 77, .07); }

/* ---- press feedback and hit areas ------------------------------------------
   The controls here are deliberately small — this is a dense operations
   surface — so the box that takes the pointer is grown past the box that is
   painted, centred on it, and nothing moves. Bands extend vertically only:
   these controls sit in rows beside each other, and two hit areas must never
   overlap. */

.ghost, .tab { position: relative; }
.ghost::after, .tab::after, .copy::after {
  content: "";
  position: absolute; left: 0; right: 0; top: 50%;
  height: 40px; transform: translateY(-50%);
}

/* scale rather than transform: the primary control already lifts on hover
   with translateY, and the two only compose if they stay separate
   properties. 0.96 — anything under 0.95 reads as exaggerated. */
.auth-submit:active, .ghost:active, .copy:active, .btn:active { scale: .96; }

/* ---- agent snippets ---- */

.panel-body .snippet { margin: .6rem 0 1.1rem; }
.panel-body .snippet code { line-height: 1.7; }
/* Clearance for the copy button, but only as much as it needs: at 5rem the
   longest line — the whole `claude mcp add` command — no longer fit and the
   block grew a scrollbar. The button sits over the caption row, not over the
   code, so this is margin for the eye rather than for the pointer. */
.panel-body .snippet pre { padding-right: 2.5rem; }
#token-result .snippet code { color: #b9cbf2; user-select: all; }

@media (max-width: 880px) {
  .panel-section { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
  .panel-section-head { position: static; }
}
@media (max-width: 720px) {
  .panel-page main { padding-top: 5.5rem; }
  .panel-page section[id] { scroll-margin-top: 5rem; }
  .top-meta select { max-width: 8rem; }
  .row { flex-wrap: wrap; gap: .45rem .8rem; }
  .row .platform { width: auto; }
  .row .grow { flex-basis: 100%; order: -1; white-space: normal; }
  /* A billing fact reads label first, like a form: "plan Pro", not a value
     with its name under it. */
  .row.fact .grow { flex-basis: auto; order: 0; }
  /* One column of meta instead of three, and the post reads as a paragraph.
     Row data drops back to the interface size here: "Wed 13 Aug 09:00" is
     163px at 17px, and a 320px screen has ~134px to give it. The chip track
     goes elastic to buy the rest. */
  .panel-body .row .mono { font-size: var(--t-ui); }
  .row.post {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "chip when" "targets targets" "body body";
  }
  .row.token {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "name action" "used action" "perms perms";
  }
}
@media (max-width: 460px) {
  .panel-section { padding: 1.15rem; border-radius: 12px; }
  .top-org { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .row, .ghost, details.expander summary { transition: none; }
  /* The glyphs still swap and the press still registers — only the movement
     goes. Every one of these states has a static cue behind it: the colour,
     the disclosure opening, the request that follows the click. */
  .expander-mark::before, .expander-mark::after { transition: none; }
  .auth-submit:active, .ghost:active, .copy:active, .btn:active { scale: 1; }
}

/* ── consent ─────────────────────────────────────────────────────────────
   The authorize screen. Wider than the login card because it is a decision,
   not a form: what is granted has to be readable in one pass, without
   scrolling past the buttons that act on it. */
.consent-card { max-width: 34rem; }

.consent-form { display: grid; gap: 1.35rem; margin-top: 1.5rem; }

.consent-group {
  margin: 0; padding: .9rem 1rem 1rem;
  border: 1px solid rgba(127, 168, 255, .16); border-radius: 12px;
  background: rgba(9, 14, 28, .5);
}
.consent-group legend {
  padding: 0 .45rem;
  color: #7f8ba0; font: 500 var(--t-meta) var(--mono);
  text-transform: uppercase; letter-spacing: .08em;
}

.consent-row {
  /* A label is a stacked field elsewhere (caption above input); here the
     control sits beside what it names, so the direction is set back. */
  display: flex; flex-direction: row; align-items: center; gap: .7rem;
  padding: .42rem 0;
  color: var(--ink); font-size: var(--t-body); font-weight: 400;
  letter-spacing: 0; text-transform: none; line-height: 1.45;
  cursor: pointer;
}
/* Scopes are shown, not chosen — the client asked for them and the answer is
   allow or deny, so they carry no control to click. */
.consent-row.static { cursor: default; padding-left: 1.55rem; }
.consent-row small { color: var(--dim); font: 400 var(--t-meta) var(--mono); }
/* The global field style is built for text inputs: full width, pill border,
   heavy padding. A radio that inherits it stretches the row and floats away
   from the label it belongs to, so this puts the native control back. */
.consent-row input {
  flex: none;
  width: auto; margin: 0; padding: 0;
  border: 0; border-radius: 0; background: none;
  accent-color: #1c5cff;
}
/* On a scope row the key sits under its sentence rather than trailing it, so
   a long description and a short one break the same way. */
.consent-row.static small { display: block; margin-top: .1rem; }
/* The documents a box accepts, or sits beside, have to read as links: a tick
   is only worth recording if the text it agrees to was plainly offered. */
.consent-row a, .auth-form-note a {
  color: #8fb1ff; text-decoration: underline; text-underline-offset: .18em;
}

.consent-hint {
  margin: 0 0 .6rem;
  color: var(--dim); font-size: var(--t-meta); line-height: 1.55;
}

.consent-actions { display: flex; align-items: center; gap: 1.1rem; }

/* Deny is a real outcome, not a way out of a mistake: it redirects the client
   with access_denied. Quiet, but never hidden behind a link. */
.auth-secondary {
  padding: .95rem 1.2rem;
  border: 1px solid rgba(127, 168, 255, .22); border-radius: 999px;
  background: none; color: var(--dim);
  font: 500 var(--t-ui) var(--mono);
  cursor: pointer;
  transition: color .2s, border-color .2s;
}
.auth-secondary:hover { color: var(--ink); border-color: rgba(127, 168, 255, .4); }
