/* thecommishbot design system.
 *
 * One hand-written stylesheet, no framework, no build step. Light-on-dark permanently:
 * there is no theme toggle and no light palette, so colors are plain custom properties
 * rather than a themeable indirection layer.
 *
 * Order: fonts, tokens, reset, layout, typography, header, footer, buttons, feature
 * cards, transcript, homepage sections (season banner, showcase, tabs, more grid, CTA
 * band), interior pages (steps, screenshots, pledges, FAQ, legal docs), signup (notices,
 * form, league confirmation, persona picker, checkout, status page), styleguide.
 * Components own their own block; nothing reaches into another component's classes.
 *
 * Contrast: every text/background pair below clears WCAG AA (4.5:1 for body text, 3:1
 * for large text and UI edges) with room to spare — the tightest pair is ~6.1:1.
 * tests/design_system.rs pins the tokens and re-derives the ratios, so a "slightly
 * nicer" green that breaks AA fails the build.
 */

/* --- Fonts -------------------------------------------------------------------- */

/* Oswald, self-hosted. A condensed athletic gothic — the register of a jersey number
 * or a stadium sign, which no system font reaches for. Subset to latin and instanced
 * to a 400-700 weight axis: one 20KB file covers every heading weight on the site,
 * and it is still zero external requests.
 *
 * The upstream font is SIL OFL 1.1; the license ships beside it in assets/fonts/. */
@font-face {
  font-family: "Oswald";
  src: url("/assets/fonts/oswald-subset.woff2") format("woff2");
  font-weight: 400 700;  /* a variable range, not a single cut */
  font-style: normal;
  font-display: swap;
}

/* --- Tokens ------------------------------------------------------------------- */

:root {
  /* Near-black, warmed up: this palette is built on browns rather than the blue-grays
     of a typical dev-tool dark theme. Football is a fall sport played on dirt. */
  --bg: #1e1b2e;
  --bg-raised: #2a263d;   /* cards, transcript, anything lifted off the page */
  --bg-sunken: #151321;   /* wells set into a raised surface */

  --text: #f8fafc;        /* 16.7:1 on --bg */
  --text-muted: #94a3b8;  /* 6.9:1 on --bg, 6.4:1 on --bg-raised */

  /* One accent, spent only on links, the primary CTA, and active state. Turf green,
     leaning yellow: real field turf is a warm green, and the warmth is what keeps this
     off both the emerald of every "success" toast and the pure #0f0 of a terminal. */
  --accent: #06b6d4;         /* 8.6:1 on --bg */
  --accent-ink: #020617;     /* text on an accent fill; 8.6:1 */
  --accent-bright: #22d3ee;  /* link hover — on a dark page, hover goes UP in contrast */
  --accent-dim: #0891b2;     /* pressed/hover fill; still 6.5:1 under --accent-ink */
  --accent-wash: rgba(6, 182, 212, 0.1);  /* accent-tinted surfaces */

  /* Muted secondary: penalty-flag amber, for pending and needs-attention states only.
     Never a CTA — two competing accents is how a palette stops meaning anything. Green
     means live, amber means there's a flag on the play. */
  --secondary: #f59e0b;   /* 9.3:1 on --bg-raised */
  --secondary-wash: rgba(245, 158, 11, 0.1);  /* notices: the amber twin of --accent-wash */

  --border: #334155;
  --border-strong: #475569;

  /* Spacing scale, roughly 1.5x. Anything that needs a gap picks from here. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;
  --space-9: 6rem;

  /* Barely-there rounding. The soft 10-16px corner is the house style of every SaaS
     landing page; at 3-6px an edge still reads as deliberate rather than as a pill.
     --radius-full stays, because round-vs-square is load-bearing on the avatars. */
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 24px;
  --radius-full: 999px;

  --measure: 72rem;       /* page gutter to gutter */
  --measure-prose: 65ch;  /* a comfortable line of reading */

  /* How tall the sticky header stands. CSS can't derive this — it falls out of the
     header's own padding and its tallest child — so it is measured (69px wherever the
     header is sticky) and rounded up. Rounding up is the safe direction: too big only
     leaves air above an anchored section, too small parks the header on top of it. */
  --header-height: 4.5rem;

  /* Display face for headings and UI labels; system sans for anything you actually
     read at length, since a condensed face is a poor host for body copy. The condensed
     fallbacks are what Mac and Windows ship, so a swap-in stays in the right register
     even before the woff2 lands (or if it never does). */
  --font-display: "Nunito", "Quicksand", system-ui, sans-serif;
  --font-sans: "Inter", "Nunito", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  /* One tight shadow, not a soft 24px bloom. Things sit on the page; they don't float
     above it glowing. */
  --shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 8px 24px rgba(0, 0, 0, 0.15);
  --transition: 250ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Oswald is already condensed and tall, so it wants none of the negative tracking a
   wide grotesk needs at display sizes, and it can sit tighter on its leading. */
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-wrap: balance;
}

p {
  margin: 0;
}

img, svg {
  max-width: 100%;
}

img {
  height: auto;
}

ul, ol {
  margin: 0;
  padding: 0;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  color: var(--accent-bright);
}

/* --- Accessibility ------------------------------------------------------------ */

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Present to a screen reader, absent to everyone else.
 *
 * Two users, one implementation: a heading that a section needs for its name and its
 * place in the outline but that the design has no room for, and the tab radios, which are
 * the state of the tab strip without being the thing anyone should look at. Clipped
 * rather than display:none — display:none is invisible to assistive tech too, and takes
 * the radios out of the tab order along the way. */
.sr-only,
.tab-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* Off-screen until tabbed to, then the first stop on every page. */
.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: 10;
  padding: var(--space-2) var(--space-4);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius-sm);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* Honor the OS setting: nothing here is load-bearing motion, so drop all of it. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

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

main {
  display: block;
}

.section {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-8) var(--space-5);
}

.section + .section {
  padding-top: var(--space-6);
}

.section-head {
  max-width: var(--measure-prose);
  margin-bottom: var(--space-6);
}

/* Uppercase is the whole trick: it is what separates a jersey from a paragraph. Spent
   only on the two headings that announce a band of content — see .feature-title for
   where it deliberately stops. */
.section-title {
  font-size: clamp(1.75rem, 3.4vw, 2.5rem);
  /* /* text-transform: uppercase */; */
  letter-spacing: 0.01em;
}

.section-prose {
  margin-top: var(--space-3);
  color: var(--text-muted);
  font-size: 1.05rem;
}

/* A fork inside a section: the two routes to the same league ID, the website and the
   phone. Sized well under .section-title — it divides a band, it doesn't announce one —
   and left off the uppercase for the reason .section-title gives. */
.subhead {
  margin-top: var(--space-7);
  font-size: 1.25rem;
}

.section-body > .subhead:first-child {
  margin-top: 0;
}

.subhead-prose {
  max-width: var(--measure-prose);
  margin-top: var(--space-3);
  color: var(--text-muted);
}

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

.hero {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-9) var(--space-5) var(--space-7);
  text-align: center;
}

/* The one place type is allowed to get loud. Scales continuously between a phone and
   a desktop, so there is no breakpoint where it looks stranded. Condensed buys real
   estate: this runs bigger than the old stack could at the same measure, and the
   leading tucks under 1 because uppercase has no descenders to collide. */
.hero-title {
  font-size: clamp(3rem, 8vw, 5.25rem);
  font-weight: 700;
  letter-spacing: 0.005em;
  line-height: 0.95;
  /* /* text-transform: uppercase */; */
}

/* The line that sells it. Full-strength text, not muted: it is the second-loudest thing
   in the hero, and the muted register belongs to .hero-sub underneath — two greyed
   paragraphs in a row is a hierarchy with nothing on top of it. */
.hero-tagline {
  max-width: 34ch;
  margin: var(--space-4) auto 0;
  font-size: clamp(1.1rem, 2.2vw, 1.4rem);
  line-height: 1.45;
  text-wrap: balance;
}

/* The line that explains it. Wider measure than the tagline and quieter — at 34ch this
   would stack into a second headline instead of reading as the sentence under one. */
.hero-sub {
  max-width: 54ch;
  margin: var(--space-4) auto 0;
  color: var(--text-muted);
  font-size: 1.05rem;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

/* --- Page head ---------------------------------------------------------------- */

/* The hero's smaller sibling, and what every page but the homepage opens with. Same
   register — display caps over a muted line — at about two thirds the volume. The hero
   type is the wordmark's alone; "FAQ" arriving at 5rem is a joke the page can't back up,
   and a second thing shouting that loudly makes the first one quieter. */
.page-head {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-8) var(--space-5) var(--space-5);
}

.page-title {
  font-size: clamp(2.25rem, 5vw, 3.25rem);
  font-weight: 700;
  letter-spacing: 0.005em;
  /* /* text-transform: uppercase */; */
}

.page-sub {
  max-width: var(--measure-prose);
  margin-top: var(--space-4);
  color: var(--text-muted);
  font-size: 1.05rem;
}

.page-sub + .page-sub {
  margin-top: var(--space-3);
}

/* Small caps label above a heading. Mono, because it reads as metadata.
 *
 * display:block rather than relying on the element: this is a <p> everywhere but /signup's
 * step 2, where it sits inside a <legend> and a legend takes only phrasing and heading
 * content — so there it has to be a <span> and still stack. */
.eyebrow {
  display: block;
  margin-bottom: var(--space-2);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  /* /* text-transform: uppercase */; */
}

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

/* Solid, not frosted. A blurred translucent bar is the single most recognizable tell of
   a template dark site, it costs a compositor pass on every scroll frame, and the thing
   it buys — seeing your content smeared behind the nav — is not worth having. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 5;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}

/* A sticky header covers the top of whatever an in-page link just jumped to: the browser
   scrolls the target to y=0, and the header is sitting there. "See it in action" landed
   with the section's eyebrow behind the bar.

   scroll-padding-top on the scroll container fixes every jump at once — :target links,
   the skip link, scrollIntoView() — instead of a scroll-margin per anchor that the next
   person has to know to add. Lives here rather than in Layout because the header is the
   entire reason it exists; see the mobile block below, where it goes away with the
   stickiness. */
html {
  scroll-padding-top: calc(var(--header-height) + var(--space-4));
}

.site-header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-4) var(--space-5);
}

/* The wordmark. Condensed lets it run a few points larger than the nav without taking
   any more width — it reads as a logo rather than as the first link. */
.site-brand {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  /* /* text-transform: uppercase */; */
  text-decoration: none;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  margin-left: auto;
}

/* No hamburger: the nav is short enough to wrap to a second line on a phone, which
   costs one row of height and zero JavaScript. */
.site-nav-link {
  color: var(--text-muted);
  font-size: 0.95rem;
  text-decoration: none;
  transition: color var(--transition);
}

.site-nav-link:hover {
  color: var(--text);
}

.site-nav-link[aria-current="page"] {
  color: var(--text);
  font-weight: 600;
}

/* The wrap above *is* the mobile nav — there is no hamburger to open. But wrapped it
   stands ~145px tall, and it grows as pages get added, so pinning it to the top of a
   phone would spend a fifth of the viewport on chrome. Below this width it scrolls
   away like everything else. */
@media (max-width: 40em) {
  .site-header {
    position: static;
  }

  /* Nothing is pinned over the content down here, so an anchor that reserved room for a
     header would just land with a gap above it. */
  html {
    scroll-padding-top: 0;
  }

  /* The hero's desktop breathing room reads as a blank screen on a phone. */
  .hero {
    padding-top: var(--space-7);
  }

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

  .site-footer {
    margin-top: var(--space-7);
  }

  /* Let the copyright fall in with the other footer rows rather than being shoved to
     its own right-aligned line. */
  .footer-copy {
    margin-left: 0;
  }
}

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

.site-footer {
  margin-top: var(--space-9);
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.9rem;
}

.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-6);
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-6) var(--space-5);
}

.footer-brand {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.footer-name {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  /* /* text-transform: uppercase */; */
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
}

.footer-links a {
  color: var(--text-muted);
  text-decoration: none;
}

.footer-links a:hover {
  color: var(--text);
}

.footer-copy {
  margin-left: auto;
}

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

/* Uppercase display type, tracked out. Tracking is what keeps condensed caps from
   packing into a solid block at small sizes — and it lands the label somewhere between
   a scoreboard and a ticket stub. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1.2;
  /* /* text-transform: uppercase */; */
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--transition), border-color var(--transition),
    color var(--transition);
}

.button-sm {
  padding: var(--space-2) var(--space-4);
  font-size: 0.95rem;
}

.button-primary {
  background: var(--accent);
  color: var(--accent-ink);
}

.button-primary:hover {
  background: var(--accent-dim);
  color: var(--accent-ink);
  transform: scale(1.05);
}

.button-secondary {
  border-color: var(--border-strong);
  background: transparent;
  color: var(--text);
}

.button-secondary:hover {
  border-color: var(--text-muted);
  background: var(--bg-raised);
  color: var(--text);
  transform: scale(1.05);
}

/* --- Feature cards ------------------------------------------------------------ */

.feature-grid {
  display: grid;
  gap: var(--space-4);
  /* Four across on a desktop, two on a tablet, one on a phone — without a single
     media query, because the cards decide based on their own room. */
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.feature-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
  transition: border-color var(--transition);
}

.feature-card:hover {
  border-color: var(--border-strong);
}

.feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-full);
  background: var(--accent-wash);
  color: var(--accent);
}

/* Display face, but sentence case: this is where the shouting stops. Uppercase on every
   heading down to the card level is how a sporty page turns into a shouty one. The bump
   in size only offsets how much narrower condensed runs. */
.feature-title {
  font-size: 1.25rem;
  font-weight: 600;
}

.feature-desc {
  color: var(--text-muted);
  font-size: 0.95rem;
}

/* --- Chat transcript ---------------------------------------------------------- */

/* The signature element: the bot, in a league chat, as it actually appears. */

.transcript {
  margin: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.transcript-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
  background: var(--bg-sunken);
}

/* A presence dot, in place of the three macOS window lights that used to sit here.
   Those said "terminal" about what is meant to be a phone chat app, and they are the
   stock chrome of every code-sample card on the web. Purely decorative — it is a chat
   app's online pip, and it carries no information the channel name doesn't. */
.transcript-live {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-full);
  background: var(--accent);
}

.transcript-channel {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  /* Long league names truncate rather than wrap the title bar to two lines. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.transcript-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-4);
  list-style: none;
}

.msg {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* Initials chip. Square-ish for the bot, round for people — the same tell Sleeper
   uses, and one that survives a grayscale screenshot. */
.msg-avatar {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.msg-bot .msg-avatar {
  border-color: transparent;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-ink);
}

.msg-main {
  min-width: 0;  /* let long words wrap instead of stretching the flex row */
}


.msg-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
  font-family: var(--font-mono);
  font-size: 0.72rem;
}

.msg-author {
  color: var(--text);
  font-weight: 600;
}

.msg-time {
  color: var(--text-muted);
}

/* Says "bot" in words. The accent bubble alone would carry that by color only. */
.msg-badge {
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  /* /* text-transform: uppercase */; */
}

.msg-bubble {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* Bubbles hug their text and stop at a readable measure. Uncapped, a long bot post
     runs the full width of the card and stops looking like chat. */
  max-width: 42rem;
  padding: var(--space-3) var(--space-4);
  /* Square off the corner nearest the avatar so the bubble points at its sender. A hard
     0 rather than a small step off the spacing scale: --radius is now 4px, so anything
     but square is the same corner as the other three and the tell disappears. */
  border-radius: 0 var(--radius) var(--radius) var(--radius);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  font-size: 0.95rem;
  overflow-wrap: break-word;
}

.msg-bot .msg-bubble {
  border-color: color-mix(in srgb, var(--accent) 28%, transparent);
  background: var(--accent-wash);
}

/* A Sleeper event, not a person talking: no avatar, no bubble, centered. */
.msg-system {
  justify-content: center;
}

.msg-event {
  padding: var(--space-1) var(--space-3);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-full);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-align: center;
}

/* --- Season banner ------------------------------------------------------------ */

/* rocket.rs runs a release banner in this slot; this is the same slot with the same job —
   the one line about the site that changes from month to month. Full-bleed so it reads as
   a strip across the page rather than as one more card in the stack, and accent-washed
   because the news it carries is always "we're open". */
.season-banner {
  border-block: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  background: var(--accent-wash);
}

.season-banner-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2) var(--space-3);
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-3) var(--space-5);
  font-size: 0.95rem;
  text-align: center;
}

/* --- Showcase ----------------------------------------------------------------- */

/* A transcript with its caption beside it. The narrow caption column is what gives the
   bubbles their measure: across the full 72rem, a bubble capped at 42rem leaves a third
   of the row empty and stops looking like a phone. */
.showcase {
  display: grid;
  gap: var(--space-6);
  align-items: start;
}

@media (min-width: 62em) {
  .showcase {
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
    gap: var(--space-7);
  }

  /* Alternates which side the transcript lands on, so three showcases in a row read as a
     rhythm rather than a list. Visual order only — the caption stays first in the source
     every time, so reading order never depends on which way a showcase happens to face.
     The tracks mirror along with the order: moving the caption to column two without
     this hands the narrow column to the transcript, which is the one thing the layout
     exists to prevent. */
  .showcase-flip {
    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
  }

  .showcase-flip .showcase-text {
    order: 2;
  }
}

/* --- Week in the life tabs ---------------------------------------------------- */

/* CSS-only tabs, driven by a radio group. The stacked layout below is the default rather
   than a fallback: it is what a phone gets, and what a browser without :has() gets, which
   means the tab rules can't strand anyone behind a `display: none` they have no way to
   undo. src/home.rs owns the beats; the per-id rules further down are the half CSS can't
   loop over, and tests/design_system.rs is what keeps the two lists in step. */

.tabs {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.tab-strip {
  display: none;
  flex-wrap: wrap;
  gap: var(--space-2);
  border-bottom: 1px solid var(--border);
}

.tab-label {
  padding: var(--space-3) var(--space-4);
  margin-bottom: -1px;  /* the active underline sits on the strip's rule, not above it */
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  /* /* text-transform: uppercase */; */
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition);
}

.tab-label:hover {
  color: var(--text);
}

/* .tab-input is hidden up in Accessibility, alongside .sr-only — same technique, and one
   copy of it. The label is the visible control; the radio underneath is the state. */

.tab-label:has(:checked) {
  border-bottom-color: var(--accent);
  color: var(--text);
}

/* The ring goes on the label: the input it would otherwise draw around is one clipped
   pixel, which is a focus indicator nobody can see. */
.tab-label:has(:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}

.tab-panels {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.tab-panel {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
}

.tab-panel-title {
  font-size: 1.25rem;
}

.tab-panel-body {
  max-width: var(--measure-prose);
  margin-top: var(--space-2);
  color: var(--text-muted);
}

@supports selector(:has(*)) {
  @media (min-width: 48em) {
    .tab-strip {
      display: flex;
    }

    /* Every panel occupies the same single cell, which leaves the section permanently as
       tall as its longest beat — so changing tabs never resizes it and never shoves the
       grid below up the page. A min-height floor would do the same job only until
       somebody writes a fourth line of copy. */
    .tab-panels {
      display: grid;
    }

    .tab-panel {
      grid-area: 1 / 1;
      /* visibility, not display: a `display: none` panel contributes no height, and the
         stack would collapse to whichever one is showing — exactly the jump this is
         here to stop. Hidden either way, to the screen reader and the tab key both. */
      visibility: hidden;
    }

    /* One selector per beat in src/home.rs — CSS has no way to generate these from the
       list, so a test asserts every beat still has its pair here. */
    .tabs:has(#tab-recap:checked) #panel-recap,
    .tabs:has(#tab-preview:checked) #panel-preview,
    .tabs:has(#tab-nags:checked) #panel-nags,
    .tabs:has(#tab-mentions:checked) #panel-mentions {
      visibility: visible;
    }
  }
}

/* --- More grid ---------------------------------------------------------------- */

/* Nine short lines, deliberately lighter than the four feature cards above them: a rule
   and a title, no fill, no icon. If everything on a page is a card, nothing on it is a
   headline. */
.more-grid {
  display: grid;
  gap: var(--space-5) var(--space-4);
  /* Lands on exactly 3 / 2 / 1 across at the three breakpoints. min() rather than a bare
     20rem: a 20rem floor is wider than a 360px phone once the page gutter is taken out,
     and grid tracks overflow rather than shrink below their minimum. */
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

.more-item {
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.more-title {
  font-size: 1.05rem;
}

.more-desc {
  margin-top: var(--space-2);
  color: var(--text-muted);
  font-size: 0.95rem;
}

/* --- CTA band ----------------------------------------------------------------- */

/* The closer. Full-bleed and raised so it reads as the end of the page rather than as one
   more section. */
.cta-band {
  border-block: 1px solid var(--border);
  background: var(--bg-raised);
}

.cta-band-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-8) var(--space-5);
  text-align: center;
}

/* The band closes the page with a rule of its own, so the footer's usual run-up would
   leave six rems of nothing between two borders. Only when the band is genuinely last —
   the styleguide shows one mid-page, and that one changes nothing. */
main:has(> .cta-band:last-child) + .site-footer {
  margin-top: 0;
}

/* --- Steps -------------------------------------------------------------------- */

/* A numbered how-to. Capped at the prose measure like everything else you actually read:
   the step column is a paragraph, not a layout. */
.steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: var(--measure-prose);
  list-style: none;
}

.step {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

/* The number, as a chip — same small filled square as the feature card's icon tile, which
   is the only other thing on the site shaped like this. Decorative: the <ol> is what
   actually says these are steps and which one this is. */
.step-index {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-full);
  background: var(--accent-wash);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
}

.step-title {
  font-size: 1.15rem;
}

.step-body {
  margin-top: var(--space-2);
  color: var(--text-muted);
}

/* --- Screenshots -------------------------------------------------------------- */

.shot {
  max-width: var(--measure-prose);
  margin: var(--space-5) 0 0;
}

.shot-frame {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
}

/* The hole where a screenshot goes. Dashed, and held open at the real image's aspect
   ratio (set inline, from the Shot's own dimensions) so that dropping the file in later
   moves nothing on the page. Amber tag: pending is exactly what amber is for. */
.shot-pending {
  display: grid;
  place-items: center;
  padding: var(--space-5);
  border-style: dashed;
  border-color: var(--border-strong);
}

.shot-brief {
  max-width: 40ch;
  color: var(--text-muted);
  font-size: 0.9rem;
  text-align: center;
}

.shot-brief-tag {
  display: block;
  margin-bottom: var(--space-2);
  color: var(--secondary);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  /* /* text-transform: uppercase */; */
}

.shot-brief-path {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  overflow-wrap: break-word;
}

.shot-caption {
  margin-top: var(--space-3);
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* A phone screenshot on its own. Portrait and nearly 2.2x as tall as it is wide, so the
   prose measure the wide shots use would run it off the bottom of the screen — it gets a
   phone-sized frame instead, which is also the size it was taken at. */
.shot-phone .shot {
  max-width: 19rem;
}

/* Phone screenshots, side by side wherever there's room for two. */
.shot-row {
  display: grid;
  gap: var(--space-4);
  max-width: var(--measure-prose);
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
}

.shot-row .shot {
  margin-top: var(--space-5);
}

/* --- League ID and the handle ------------------------------------------------- */

/* The URL a league ID is read out of. Scrolls rather than wraps: a wrapped URL breaks the
   long number across two lines, which is the exact string the reader is trying to see as
   one thing. */
.url-sample {
  max-width: var(--measure-prose);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  white-space: nowrap;
  overflow-x: auto;
}

.url-dim {
  color: var(--text-muted);
}

/* <mark>, off the browser's highlighter yellow and onto the accent. The site's one
   honest use of "look at this bit": everything around it is the part they can ignore. */
.url-id {
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--accent-wash);
  color: var(--accent);
}

.handle-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  color: var(--text-muted);
}

/* The one string a visitor has to retype into somebody else's app. Mono so the
   characters are unambiguous, and one click selects the whole thing rather than a word
   of it. */
.handle {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 1rem;
  user-select: all;
}

/* --- Pledges ------------------------------------------------------------------ */

/* What it will and won't do, side by side. Two cards rather than one list with two
   colors: the promise being made is that the second column is exhaustive, and a column is
   the only shape that says so. */
.pledge-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

.pledge {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
}

.pledge-title {
  font-size: 1.25rem;
}

.pledge-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-4);
  list-style: none;
}

.pledge-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  color: var(--text-muted);
  font-size: 0.95rem;
}

/* The icon keeps its box: in a flex row it would otherwise be squeezed as the text wraps,
   so the longest line — the one that most needs the mark — gets the smallest one. The
   nudge down optically centers it on the first line of text rather than its ascenders. */
.pledge-item .icon {
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.25rem;
}

.pledge-will .icon {
  color: var(--accent);
}

/* Not red: "won't" is the reassuring column, not the error state, and the site has one
   accent on purpose. The headings above are what tell the two lists apart — the marks
   are a second signal, never the only one. */
.pledge-wont .icon {
  color: var(--text-muted);
}

/* --- FAQ ---------------------------------------------------------------------- */

.faq-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: var(--measure-prose);
}

.faq-q {
  font-size: 1.25rem;
}

.faq-a {
  margin-top: var(--space-3);
  color: var(--text-muted);
}

.faq-a p + p {
  margin-top: var(--space-3);
}

/* --- Legal docs --------------------------------------------------------------- */

.doc {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: var(--measure-prose);
}

/* The only signal a reader has that these aren't the terms they read last season, so it
   sits above the lede rather than in the fine print at the bottom. */
.doc-updated {
  margin-top: var(--space-4);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  /* /* text-transform: uppercase */; */
}

.doc-heading {
  font-size: 1.35rem;
}

.doc-section p {
  margin-top: var(--space-3);
  color: var(--text-muted);
}

/* --- Notices ------------------------------------------------------------------ */

/* A banner interrupting a page: the canceled-checkout note, the no-JS explanations. Always
   amber — every notice on this site is "there's a flag on the play", never an error, and
   the site has no red to reach for anyway. Full-strength text: a banner someone has to
   squint at is a banner that failed. */
.notice {
  max-width: var(--measure-prose);
  margin-top: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 1px solid color-mix(in srgb, var(--secondary) 28%, transparent);
  border-radius: var(--radius);
  background: var(--secondary-wash);
  color: var(--text);
  font-size: 0.95rem;
}

/* When the notice is what a page opens with rather than something inside it. */
.notice-lead {
  margin-top: 0;
  margin-bottom: var(--space-6);
}

.notice p + p {
  margin-top: var(--space-3);
}

/* --- Signup form -------------------------------------------------------------- */

/* Narrower than the prose measure's siblings elsewhere: this is a form someone fills in
   top to bottom, and a column they have to track across is a column they lose their place
   in. The persona grid is the only thing that wants the width, and it gets it. */
.signup-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  max-width: 46rem;
}

/* Step 2 is a real <fieldset> — five radios that add up to one choice is precisely what a
   fieldset is — and a fieldset arrives with a border, a margin, and a min-width it will
   not give up without being asked. The steps either side are plain sections; same class,
   because they are the same thing on the page. */
.form-step {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

/* The <legend>. Deliberately not flexed: legend is the one element whose rendering the
   spec still carves out, and display:flex on it remains a coin flip across engines. Its
   two children stack on their own. */
.form-step-head {
  padding: 0;
}

.form-step-title {
  font-size: 1.5rem;
}

.form-step-hint {
  max-width: var(--measure-prose);
  margin-top: var(--space-2);
  color: var(--text-muted);
  font-size: 0.95rem;
}

.field-label {
  display: block;
  margin: var(--space-5) 0 var(--space-2);
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  /* /* text-transform: uppercase */; */
}

/* Mono, and roomier than a text field needs to be: this is an 18-digit number being copied
   off another screen, and every ambiguous 0/O or 1/l in it is a signup that fails at the
   API for no reason anyone can see. No placeholder either — a sample ID sitting in a
   numeric field reads as a value that's already there. */
.field-input {
  width: 100%;
  max-width: 26rem;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 1rem;
  letter-spacing: 0.02em;
}

.field-input:hover {
  border-color: var(--text-muted);
}

/* The "where do I find this?" disclosure. A <details>, so it costs no JavaScript and
   nothing on the page depends on it being open. */
.form-help {
  max-width: var(--measure-prose);
  margin-top: var(--space-4);
}

.form-help summary {
  color: var(--accent);
  cursor: pointer;
  font-size: 0.9rem;
}

.form-help-body > * {
  margin-top: var(--space-3);
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* --- League confirmation ------------------------------------------------------ */

/* The answer to "is this the right league?", built by signup.js from the API's lookup.
   Accent-edged: it is the one moment on the form where something has gone right, and the
   only green on the page until the button. */
.league-confirm {
  max-width: 26rem;
}

/* Empty until the lookup lands, and an empty box with a border is a box that looks broken
   while you type. */
.league-confirm:empty {
  display: none;
}

.confirm {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: var(--radius);
  background: var(--accent-wash);
}

/* Sleeper's, off Sleeper's CDN, and plenty of leagues have none — so it holds its box
   whether or not an image ever arrives in it. */
.confirm-avatar {
  flex: none;
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}

.confirm-main {
  min-width: 0;
}

.confirm-name {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  overflow-wrap: break-word;
}

.confirm-meta {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.78rem;
}

/* The two answers that aren't a league: checking, and a friendly no. Amber for the no —
   a mistyped league ID is a flag on the play, not a failure. */
.confirm-note {
  margin-top: var(--space-4);
  color: var(--text-muted);
  font-size: 0.9rem;
}

.confirm-note-bad {
  color: var(--secondary);
}

/* --- Persona picker ----------------------------------------------------------- */

.persona-grid {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
}

.persona-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  /* start, not the default stretch: a stretched grid track centers the radio against the
     whole card, which on a three-line card parks it beside the sample line instead of the
     name it belongs to. */
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
  cursor: pointer;
  transition: border-color var(--transition), background-color var(--transition);
}

.persona-card:hover {
  border-color: var(--border-strong);
}

/* The native radio, kept and tinted rather than replaced. Its entire job is to show which
   of five things is chosen, and no custom span beats the dot the platform already draws:
   the real control survives forced-colors mode, a zoomed viewport, and a stylesheet that
   failed to load, all of which are exactly when someone needs to know what they picked.
   The nudge lands it on the first line of the name rather than the top of the card. */
.persona-input {
  margin: 0.3rem 0 0;
  accent-color: var(--accent);
}

/* Decorative reinforcement — the radio underneath is the real state, so a browser without
   :has() loses a highlight and nothing else. */
.persona-card:has(:checked) {
  border-color: var(--accent);
  background: var(--accent-wash);
}

/* The ring belongs on the card: the radio is 13px in the corner of a 300px target, and a
   focus indicator you have to hunt for is not one. */
.persona-card:has(:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.persona-name {
  display: block;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
}

.persona-blurb {
  display: block;
  margin-top: var(--space-1);
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* The sample is the actual promise — /faq tells people this line is what their league is
   getting all season — so it is set as a quotation and not as more blurb. Full-strength
   text under a muted description: it is the thing on the card worth reading twice. */
.persona-sample {
  display: block;
  margin-top: var(--space-3);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border-strong);
  color: var(--text);
  font-size: 0.9rem;
  font-style: italic;
}

.persona-card:has(:checked) .persona-sample {
  border-left-color: var(--accent);
}

/* --- Checkout ----------------------------------------------------------------- */

/* Cloudflare renders the widget into this box. Its published size is 300x65, reserved here
   so the submit button doesn't jump down the page when the script lands — third-party
   markup is the only thing on this site that could shift a layout, and this is the only
   page it's on. */
.turnstile {
  min-height: 65px;
  margin-top: var(--space-5);
}

.form-submit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-5);
}

/* Beside the button, not under a heading somewhere up the page: the plan's rule is no
   surprises at checkout, and this is the last thing read before the click that spends it. */
.price-note {
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* --- Status page -------------------------------------------------------------- */

.status,
.status-league {
  max-width: var(--measure-prose);
}

.status-league {
  font-family: var(--font-mono);
  font-size: 0.9rem;
}

.status-panel {
  padding: var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
}

/* The phase, in a word and in a color. Amber pending, green live, and the two ended states
   get neither: "we owe you a refund" is news, not an error, and there is no red here to
   make it one. */
.status-pip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  /* /* text-transform: uppercase */; */
}

.status-pip::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-full);
  background: currentColor;
}

.status-pip-pending {
  color: var(--secondary);
}

.status-pip-live {
  color: var(--accent);
}

.status-pip-ended {
  color: var(--text-muted);
}

/* The only motion on the site, and it earns it: on a page where nothing else changes for
   minutes, this dot is the whole answer to "is this thing still working, or did it take my
   money and die?". Only the pending pip moves — a pulsing "live" would be decoration.

   Under prefers-reduced-motion the global rule collapses the duration and the dot simply
   sits there at full opacity, which says the same thing more quietly. */
.status-pip-pending::before {
  animation: pip-pulse 2s ease-in-out infinite;
}

@keyframes pip-pulse {
  50% { opacity: 0.3; }
}

.status-heading {
  font-size: clamp(1.5rem, 3vw, 2rem);
}

.status-body {
  margin-top: var(--space-3);
  color: var(--text-muted);
}

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

/* The bot's own words for why it turned a league away, out of status.json. Set as a quote
   in mono: it is the only string on the page we didn't write, and it is what gets pasted
   into the support email. */
.status-detail {
  margin-top: var(--space-5);
  padding-left: var(--space-4);
  border-left: 2px solid var(--border-strong);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  /* The one string on the page we didn't write. It can be anything the bot put in
     status.json — including a league id or a URL with nothing to break on — and mono has
     no narrow glyphs to save it on a phone. */
  overflow-wrap: break-word;
}

.status-slow,
.status-foot {
  margin-top: var(--space-5);
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* --- Copy link ---------------------------------------------------------------- */

/* The link that hangs off a block of copy — the "email us" under a FAQ answer. Undecorated
   until hover: it is a next step, and it sits alone on its line where nothing else could
   be mistaken for one. */
.copy-link {
  margin-top: var(--space-3);
  font-size: 0.9rem;
}

.copy-link a {
  text-decoration: none;
}

.copy-link a:hover {
  text-decoration: underline;
}

/* --- Styleguide --------------------------------------------------------------- */

/* Only /styleguide uses these. They are scaffolding for reviewing components, not
   components themselves — nothing public should reach for a .sg-* class. */

.sg-swatches {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

.sg-swatch {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.sg-swatch-chip {
  height: 4rem;
  border-bottom: 1px solid var(--border);
}

.sg-swatch-label {
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.72rem;
}

.sg-swatch-label span {
  display: block;
  color: var(--text-muted);
}

.sg-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
}

.sg-icons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.sg-icon {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  width: 7rem;
  padding: var(--space-4) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-raised);
  color: var(--accent);
}

.sg-icon-name {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
}

.sg-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.sg-type-sample {
  border-left: 2px solid var(--border);
  padding-left: var(--space-4);
}

.sg-note {
  max-width: var(--measure-prose);
  color: var(--text-muted);
  font-size: 0.9rem;
}

.sg-scale {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.sg-scale-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
}

.sg-scale-bar {
  flex: none;
  height: 0.75rem;
  /* Not --radius-sm: at --space-1 the bar is 4px wide and a 6px radius turns it into
     a dot, which reads as a bullet rather than the smallest step on the scale. */
  border-radius: 2px;
  background: var(--accent-wash);
  border: 1px solid var(--accent);
}
