/* RingScript site — one calm palette, light & dark, no dependencies. */

/* One light theme on purpose: a white page, code always on dark panels.
   (The Playground keeps its own editor styling.) */
:root {
    color-scheme: light;
    --bg: #ffffff;
    --panel: #ffffff;
    --ink: #1c2126;
    --ink-soft: #4a5560;
    --muted: #6a737d;
    /* RingServ: the family stylesheet with the dominant colour turned
       from RingScript's green to the ELECTRIC VIOLET of the logo's
       sweep -- the author's call: blue is every developer tool's colour,
       and a signature should not be widely worn. #8a2be2 measures 5.97
       against white (computed, not assumed), comfortably past the 4.5
       the father sheet holds itself to; the blue the logo opens with
       recedes to the secondary role. */
    --accent: #8a2be2;
    /* The secondary is the CRYSTAL'S TEAL, not the ring's blue. The blue
       that felt dominant on this page was never the palette -- it was the
       syntax colour of :atoms, and RingServ's declarative samples are
       MADE of :atoms where RingScript's barely used them. Teal contrasts
       with the violet instead of competing with it. 4.8 on white. */
    --accent-2: #0c7d8f;
    --accent-soft: #f4ecfd;
    --accent-ink: #ffffff;
    --error: #b42318;
    --border: #e2e8ee;
    /* A separator has no fill behind it to help, so it needs more
       weight than a card's outline: the What's new entries were
       divided by a line nobody could see. */
    --rule: #c3cede;
    /* Code sits on a soft light panel, not a black box — easier on the eye
       against a white page, and the syntax colours stay legible. */
    --code-bg: #f7f9fc;
    --code-ink: #24313f;
    --code-border: #dbe3ec;
    /* Every syntax colour clears 4.5:1 against --code-bg, measured, because
       a colour that carries meaning must also carry contrast (Zui rule 105).
       Three of them used not to: comments 3.84, tags 4.39, numbers 4.48. */
    --k: #8b3fc9;   /* keywords  5.45 */
    --s: #0a7d4b;   /* strings   4.92 */
    --n: #a83a0f;   /* numbers   6.08 */
    --c: #5c6773;   /* comments  5.47 */
    --a: #0c7d8f;   /* :atoms — the crystal teal, see --accent-2  4.8 */
    --t: #0f5bc4;   /* html tags 6.01 */
    --f: #7a4f01;   /* functions 6.76 */
    --mono: "Cascadia Code", Consolas, "SF Mono", monospace;

    /* ------------------------------------------------------ the type scale
       Three voices and one reading size (Zui rule 107, "Hierarchy Is Not
       Miniaturization"): a hero, a title, and ONE size for everything that
       is read. Below the title, structure is carried by weight, colour and
       space — never by shrinking the text. Two deviations only, both the
       ones the rule sanctions: monospace is set metrically so it LOOKS the
       same size as the prose beside it, and interface chrome that is never
       read for content (the menu, the footer, small controls) may sit at
       --floor, which is the absolute legibility floor and not a millimetre
       below it (rule 105). Nothing on this site is smaller than --floor. */
    --floor: 1rem;        /* 16px — the legibility floor, chrome only     */
    --read: 1.2rem;       /* 19.2px — the one reading size                */
    --title: 1.6rem;      /* 25.6px — the title voice                     */
    --mono-size: 1.08rem; /* 17.3px — optical parity with --read          */
}

* { box-sizing: border-box; }
/* No smooth scrolling. The reader asked to be at an anchor; animating the
   journey there is motion that reports nothing (Zui rule 112). */
body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font: var(--read)/1.75 system-ui, "Segoe UI", -apple-system, sans-serif;
    -webkit-font-smoothing: antialiased;
}
/* Horizontal only: the shorthand would win on specificity over the vertical
   padding of `section`, and every section is also a .wrap. */
.wrap { max-width: 64rem; margin: 0 auto; padding-left: 1.25rem; padding-right: 1.25rem; }
.narrow { max-width: 46rem; }

/* ---------------------------------------------------------------- nav */
/* Opaque, not translucent. A sticky bar is allowed to be a bar, but not to
   let the reading show through it as it passes underneath (rule 110). */
nav {
    position: sticky; top: 0; z-index: 20;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
}
nav .wrap { display: flex; align-items: center; gap: 1.4rem; padding-top: .7rem; padding-bottom: .7rem; }
/* FIVE stops and one call to action. Rule 27 caps a horizontal menu at
   eight; this sits well under it, because the menu is a claim about how
   the site is built and the site has five branches, not seven:

       Start here        · and the tutorial inside it
       Local-first apps  · and "Working with data" inside it
       Why Ring?         · and "The story" inside it
       Q&A
       Blog              · the posts, and "What's new" inside it

   Four pages left the bar without leaving the site. Two of them were one
   subject told twice (Why Ring? / The story), one was a demo that now has
   a parent naming the whole idea, and one is release notes, which belong
   with the reading. A menu does not grow by promotion (rule 116): where a
   page matters but sits inside a branch, the branch names it.

   The active stop is the BRANCH, not the page — so a child marks its
   parent and the highlight never claims a structure that is not there.

   There is no breadcrumb trail, deliberately: the branches are one level
   deep and each parent names its children in its own text, so a trail
   would repeat the menu one line lower. Reported back to the constitution
   as evidence, and it is why rules 108 and 115 now say "interfaces that
   have depth". */
nav a { color: var(--ink-soft); text-decoration: none; font-size: var(--floor); }
nav a:hover { color: var(--ink); }
nav a.active { color: var(--accent); font-weight: 600; }
/* The RS mark sits with the wordmark, not above it: inline-flex so the two
   share a baseline-ish centre and the rounded square never stretches. */
nav .brand {
    margin-right: auto; font-weight: 700; color: var(--ink); font-size: 1.15rem;
    display: inline-flex; align-items: center;
}
/* Spacing goes on the mark, not as a flex `gap`: "Ring" and the "Script"
   span are two flex items, so a gap would prise the wordmark apart too. */
nav .brand img { width: 26px; height: 26px; display: block; flex: 0 0 auto; margin-right: .5rem; }
nav .brand span { color: var(--accent); }
nav a.repo { display: inline-flex; align-items: center; gap: .4rem; }
nav a.repo svg { width: 1.05em; height: 1.05em; fill: currentColor; }
nav .cta {
    background: var(--accent); color: var(--accent-ink);
    padding: .35rem 1rem; border-radius: 7px; font-weight: 600;
}
nav .cta:hover { color: var(--accent-ink); opacity: .9; }

/* --------------------------------------------------------------- hero */
/* Vertical only — the header is also a .wrap, and the shorthand would tie
   on specificity and win by order, wiping out its horizontal padding.
   (Same trap as `section`; there it is the element selector that loses.) */
.hero { text-align: center; padding-top: 4rem; padding-bottom: 2.5rem; }
.hero img.logo { width: 200px; height: auto; margin-bottom: .6rem; }
.hero h1 { font-size: clamp(2rem, 5vw, 2.9rem); line-height: 1.15; margin: .3rem 0 .8rem; letter-spacing: -.02em; }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero p.sub { font-size: var(--read); color: var(--ink-soft); max-width: 40rem; margin: 0 auto 1.4rem; }
.badges { display: flex; gap: .5rem; justify-content: center; flex-wrap: wrap; margin-bottom: 1.6rem; }
.badge {
    font-size: var(--floor); color: var(--ink-soft);
    border: 1px solid var(--border); border-radius: 100px;
    padding: .3rem 1rem; background: var(--panel);
}
.actions { display: flex; gap: .7rem; justify-content: center; flex-wrap: wrap; }
.btn {
    display: inline-block; padding: .6rem 1.5rem; border-radius: 8px;
    text-decoration: none; font-weight: 600; font-size: var(--read);
    border: 1px solid var(--accent);
}
.btn.primary { background: var(--accent); color: var(--accent-ink); }
.btn.ghost { color: var(--accent); background: transparent; }
.btn:hover { opacity: .9; }

/* Marks a link that opens in a new tab. Kept at .9em (never small in
   absolute terms) and slightly lifted, so it reads as a sign, not a glyph. */
.newtab {
    font-size: 1em;          /* never smaller than the label it marks */
    display: inline-block;
    transform: translateY(-.06em);
    opacity: .8;
}
a:hover .newtab { opacity: 1; }

/* Measured, not guessed. The bar is brand + five stops + the call to
   action, and it needs 710px of row — so it fits from a 751px viewport.
   The switch sits at 849px rather than 751: system-ui is a different font
   on every platform, and a breakpoint tuned to this machine's metrics is
   a breakpoint that wraps on someone else's. 100px of slack buys that,
   and a menu is never allowed to wrap by ACCIDENT (rule 27).

   (It was 1023px when the menu carried seven stops. Cutting the menu to
   five gave ~270px back, and leaving the old value would have stacked the
   bar on tablets that can now hold it in one line.)

   Under the breakpoint it becomes a deliberate two-row layout, brand on
   its own line, which is what rule 27 permits on a small viewport. It also
   stops being pinned there: stacked rows would eat a sixth of a phone
   screen for the whole scroll. */
@media (max-width: 849px) {
    nav { position: static; }
    nav .wrap { flex-wrap: wrap; gap: .15rem 1rem; }
    nav .brand { width: 100%; margin-right: 0; }
    /* A 27px line of text is a poor thumb target. Padding takes each link
       to ~40px tall; the row gap shrinks to match, so the bar keeps its
       height. The CTA has its own padding and is already big enough. */
    nav a { padding-top: .4rem; padding-bottom: .4rem; }
}


/* -------------------------------------------------------------- cards */
/* Sections breathe: a clear gap from whatever sits above them. */
section { padding: 4.5rem 0 1rem; }
section + section { padding-top: 4.5rem; }
/* Headings set their own leading. Body text wants 1.75; a heading at that
   spacing falls apart the moment it wraps, which on a phone is most of
   them — the two halves of a title drift apart and stop reading as one. */
h1, h2, h3 { line-height: 1.25; text-wrap: balance; }
/* Short centred blocks split evenly rather than dropping one word
   onto a line of its own. */
.hero p.sub, figcaption, .badge, .lead { text-wrap: balance; }
h1 { font-size: clamp(1.9rem, 6vw, 2.3rem); letter-spacing: -.02em; }
h2 { font-size: var(--title); letter-spacing: -.01em; margin: 0 0 .6rem; }
/* The third level is NOT a fourth size. It is the reading size carrying
   more weight and the darker ink — the whole of rule 107 in one line. */
h3 { font-size: var(--read); font-weight: 700; margin: 0 0 .35rem; }
.lead { color: var(--ink-soft); font-size: var(--read); max-width: 44rem; margin: 0 0 2rem; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1rem; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 1.3rem 1.4rem; }
.card p { margin: 0; color: var(--ink-soft); font-size: var(--read); }
/* Neutral geometric glyphs, deliberately not emoji: they inherit a calm
   grey and never fight the text for attention. */
.card .icon {
    font-size: 2rem; line-height: 1;
    display: block; margin-bottom: .7rem;
    color: var(--muted); opacity: .55;
}

/* --------------------------------------------------------------- code */
pre, code { font-family: var(--mono); }
pre.code {
    background: var(--code-bg); color: var(--code-ink);
    border: 1px solid var(--code-border); border-radius: 10px;
    padding: 1.1rem 1.2rem; overflow-x: auto; margin: 0;
    font-size: var(--mono-size); line-height: 1.7;
}
pre.code .k { color: var(--k); font-weight: 600; }
pre.code .s { color: var(--s); }
pre.code .n { color: var(--n); }
pre.code .c { color: var(--c); font-style: italic; }
pre.code .a { color: var(--a); }
pre.code .t { color: var(--t); }
pre.code .f { color: var(--f); }
/* Marks something announced but not yet released. */
.soon {
    display: inline-block; vertical-align: middle;
    margin-left: .5rem; padding: .1em .7em;
    border: 1px solid var(--border); border-radius: 100px;
    background: var(--bg);
    font-size: var(--floor); font-weight: 600; color: var(--muted);
    white-space: nowrap;
}

/* Room before scrollbars (Zui rule 114). A measure that fits sentences does
   not fit a terminal transcript, a shell command or a wide table — and a
   reader can see the empty margin beside a scrollbar. So inside an article
   these blocks take the page's spare width when they need it, growing about
   the SAME axis so the reading line never moves, and scroll only once the
   page itself has run out. The cap is the page's own content column, not
   the viewport: a code block that ran the full width of a 27-inch monitor
   would trade one bad layout for another.

   The author owes the other half of this rule: lines are written to fit the
   column, so the widening stays exceptional rather than routine.

   It grows to the RIGHT, into the empty column, rather than centring on
   itself: an article is left-aligned inside a wider wrap, so a block
   centred on the article's own middle reaches past the LEFT edge of the
   page — where it is clipped rather than scrolled, because a document's
   scroll width does not count overflow to the left. (That is exactly how
   this went wrong the first time: the live example's output was cut off
   mid-word with no scrollbar to recover it.) Growing rightwards keeps the
   reading line exactly where it was, which is what the rule actually asks
   for, and it can never leave the page.

   And it is for CODE ONLY. A code block cannot reflow — a line is a line,
   and breaking it changes the program. A table can: its second column is
   prose and wraps like prose. Widening a table to avoid a scrollbar it was
   never going to have just drags the grid out past the text it belongs to
   and makes the page look broken. So a table keeps the reading column and
   wraps inside it; `.scroll` stays on it as the last resort, for the rare
   grid too wide even for that. */
article pre {
    width: max-content;
    min-width: 100%;
    max-width: min(60rem, calc(100vw - 2.5rem));
}
/* ...but never a block that lives inside a panel of its own: it belongs to
   the panel's width, not the page's. */
article .runner pre { max-width: 100%; width: auto; }

/* A numeric table has a floor: its figures do not wrap, so below a certain
   width the columns simply cannot give any more. Measured on a 375px
   phone, the widest three-column tables missed by 6px — so the cells give
   up some of their side padding there rather than hand the reader a
   scrollbar for six pixels. */
@media (max-width: 480px) {
    article .scroll table th, article .scroll table td {
        padding-left: .4rem; padding-right: .4rem;
    }
}

/* A diagram cannot reflow. Squeeze one into a phone column and its labels
   drop under the reading floor — the same defect as small text, arrived at
   sideways. So a figure holds its diagram at the width its labels need
   (41.25rem = 660px is the widest diagram's own coordinate width, so its
   16-unit labels render at 16px or better) and scrolls inside itself once
   the page has run out. Written here rather than per page so that no
   future diagram can forget it. */
article figure { overflow-x: auto; }
article figure svg { min-width: 41.25rem; }

/* ------------------------------------------------- the code editor
   Ring on this site is shown the way the Playground shows it: numbered
   lines, the same syntax colours, the same monospace. Two arrangements
   share this styling — a static snippet (a gutter beside a <pre>) and the
   live editor (a gutter beside a highlighted layer under a transparent
   textarea) — because they should be indistinguishable to a reader. */
.rh-shell {
    display: flex; align-items: stretch;
    border: 1px solid var(--code-border); border-radius: 10px;
    background: var(--code-bg);
    overflow: hidden;
    margin: 1.2rem 0;
    /* Rule 114 again: code has lines where prose has sentences. */
    width: max-content; min-width: 100%;
    max-width: min(60rem, calc(100vw - 2.5rem));
}
.rh-gutter {
    flex: 0 0 auto;
    padding: 1.1rem 0;
    background: color-mix(in srgb, var(--code-ink) 5%, var(--code-bg));
    border-right: 1px solid var(--code-border);
    color: var(--muted);
    font: var(--mono-size)/1.7 var(--mono);
    text-align: right; user-select: none; overflow: hidden; white-space: pre;
}
.rh-gutter div { padding: 0 .6rem 0 .9rem; }
.rh-gutter div.cur { color: var(--accent); font-weight: 700; background: var(--accent-soft); }

/* a static snippet: the <pre> sits inside the shell and keeps its own scroll */
article .rh-shell pre.rh-static {
    margin: 0; border: 0; border-radius: 0; flex: 1 1 auto;
    width: auto; min-width: 0; max-width: none;
    background: transparent;
    padding: 1.1rem 1.1rem;
    font: var(--mono-size)/1.7 var(--mono);
    overflow-x: auto;
}
article .rh-shell pre.rh-static code { font: inherit; background: none; padding: 0; }

/* the live editor: three layers, the textarea on top and transparent */
.rh-wrap { position: relative; flex: 1 1 auto; min-width: 0; }
.rh-code, .rh-input, .runner-code {
    margin: 0; border: 0; padding: 1.1rem;
    font: var(--mono-size)/1.7 var(--mono);
    white-space: pre; overflow-wrap: normal; tab-size: 4;
}
.rh-code {
    position: absolute; inset: 0; overflow: hidden;
    pointer-events: none; color: var(--code-ink);
}
.rh-line {
    position: absolute; left: 0; right: 0;
    background: var(--accent-soft); pointer-events: none;
}
.rh-input {
    position: relative; display: block; width: 100%; min-height: 6rem;
    background: transparent; color: transparent; caret-color: var(--ink);
    outline: none; overflow: auto; resize: vertical;
}
.rh-input::selection { background: #e6d9f7; color: transparent; }

.tok-kw   { color: var(--k); font-weight: 600; }
.tok-str  { color: var(--s); }
.tok-num  { color: var(--n); }
.tok-com  { color: var(--c); font-style: italic; }
.tok-atom { color: var(--a); }
.tok-fn   { color: var(--f); }

/* ------------------------------------------------------------- runner
   The live Ring panel used by the blog posts: a program you can edit, a
   button that runs it in the real runtime, and the output it produced.
   Three parts, each with its own label and its own box, because the first
   version ran them together and you could not tell where the program
   stopped and the answer began.

   It takes the page's spare width for the same reason a code block does
   (rule 114) — code has lines, prose has sentences — and it grows to the
   right, so the reading line does not move. Everything inside it belongs
   to the panel: nothing widens past its border, nothing floats. */
.runner {
    width: min(60rem, calc(100vw - 2.5rem));
    margin: 2.2rem 0;
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--panel);
    overflow: hidden;               /* the border is the panel's promise */
}
.runner-head {
    display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
    padding: .8rem 1.1rem;
    border-bottom: 1px solid var(--border);
    background: var(--accent-soft);
}
.runner-head h3 { margin: 0; font-size: var(--read); }
.runner-head p { margin: 0; flex: 1 1 18rem; font-size: var(--floor); color: var(--ink-soft); }

/* Each part says what it is. Uppercase, so set at the floor and tracked
   out — the optical-parity deviation rule 107 allows, not a size step. */
.runner-label {
    display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
    padding: .7rem 1.1rem .35rem;
    font-size: var(--floor); font-weight: 700;
    text-transform: uppercase; letter-spacing: .06em;
    color: var(--ink-soft);
}
/* The keyboard contract, stated where it applies. Not uppercase — it is a
   sentence, not a label — and never below the floor. */
.runner-hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--muted); }
/* The editor owns the type and the padding of this textarea — the
   highlighted layer sits exactly beneath it and the two must agree to the
   pixel, or the colours drift away from the letters a few lines down.
   Everything here is what the editor does NOT set. */
.runner-code {
    display: block; width: 100%; box-sizing: border-box;
    background: var(--code-bg); color: var(--code-ink); resize: vertical; outline: none;
    /* height is set from the program's own length on load, so the reader
       meets the whole example rather than the middle of it */
    min-height: 6rem;
}
/* the editor's shell replaces the textarea's own frame inside the panel */
.runner .rh-shell {
    margin: 0; border-radius: 0; border-left: 0; border-right: 0;
    width: auto; min-width: 0; max-width: none;
}
.runner-bar {
    display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
    padding: .9rem 1.1rem;
}
/* Rule 106: the label says what the click does. Rule 79: an example you
   can edit owes you a way back, so "restore" is always reachable. */
.runner button {
    font: inherit; font-weight: 600; font-size: var(--floor);
    padding: .5rem 1.3rem; border-radius: 8px; cursor: pointer;
    border: 1px solid var(--accent);
    background: var(--accent); color: var(--accent-ink);
}
.runner button.quiet { background: transparent; color: var(--accent); }
/* A disabled control is dimmed by CONVENTION, and the convention is
   opacity — which took this button to 1.99:1 and made it unreadable. Rule
   105 allows secondary things to be quieter in weight or colour, never in
   legibility, so the disabled state is a real colour pair that still
   clears 4.5:1 rather than a faded one. */
.runner button:disabled {
    background: var(--muted); border-color: var(--muted); color: #fff;
    cursor: default;
}
.runner .runner-status { font-size: var(--floor); color: var(--ink-soft); }
.runner .runner-status.bad { color: var(--error); }
/* The output owns its own box. It keeps every newline and every leading
   space — it is program output, not prose — but a line longer than the
   panel wraps rather than hiding behind a horizontal scrollbar: a Ring
   error message is one long sentence, and making the reader drag it into
   view is the defect rule 114 is about. Only the vertical axis scrolls. */
.runner-out {
    margin: 0; padding: .9rem 1.1rem;
    border-top: 1px solid var(--border);
    background: var(--code-bg); color: var(--code-ink);
    font: var(--mono-size)/1.6 var(--mono);
    white-space: pre-wrap; overflow-wrap: anywhere;
    overflow-x: hidden; overflow-y: auto;
    min-height: 3.2rem; max-height: 26rem;
}
.runner-out:empty::before {
    content: "nothing yet — press Run";
    color: var(--muted); font-style: italic;
}

/* Links inside prose and cards: the site's green, underlined on hover —
   never the browser default blue. Buttons keep their own styling. */
article a:not(.btn), .card a, .pair a, .limit a, .layer a {
    color: var(--accent); text-decoration: none; font-weight: 600;
}
article a:not(.btn):hover, .card a:hover, .pair a:hover { text-decoration: underline; }
.book a:hover { text-decoration: none; }

p code, li code, td code {
    background: var(--accent-soft); color: var(--ink);
    padding: .08em .35em; border-radius: 4px; font-size: var(--mono-size);
    /* A path or long identifier has no spaces to break at, so on a phone it
       would push the whole page sideways. Let it break anywhere it must. */
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------ compare */
/* Stacked, never side by side: the two versions are line-for-line
   equivalents, and reading them one under the other is what makes the
   difference visible. */
.compare { display: grid; grid-template-columns: 1fr; gap: 2rem; align-items: start; max-width: 52rem; }
/* A grid item is min-width:auto by default, so a long code line would push
   the column — and the page — wider than the phone. Pinned to 0, the block
   scrolls inside itself instead, which is what `pre.code` already asks for. */
.compare > div, .grid > *, .limits > *, .pair > * { min-width: 0; }
.compare .label {
    font-size: var(--read); color: var(--ink-soft);
    margin-bottom: .6rem; display: block;
}
.compare .label b { color: var(--ink); }
.compare .label .tag {
    display: inline-block; margin-left: .5rem; padding: .05em .7em;
    border-radius: 100px; font-size: var(--floor); font-weight: 600;
    background: var(--accent-soft); color: var(--accent);
}
.preamble { max-width: 52rem; margin: 0 0 2rem; }
.preamble p { color: var(--ink-soft); margin: 0 0 .6rem; }

/* ---------------------------------------------------------- try panel */
.try { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; max-width: 42rem; margin: 0 auto; }
.try-head { display: flex; justify-content: space-between; padding: .5rem .9rem; font-size: var(--floor); color: var(--muted); border-bottom: 1px solid var(--border); }
.try textarea {
    display: block; width: 100%; border: 0; outline: none; resize: vertical;
    background: var(--code-bg); color: var(--code-ink); caret-color: #fff;
    font: var(--mono-size)/1.6 var(--mono); padding: .8rem 1rem; min-height: 6rem;
}
.try-bar { display: flex; gap: .8rem; align-items: flex-start; padding: .6rem .9rem; background: #0a101a; border-top: 1px solid var(--code-border); }
.try-bar button {
    border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink);
    border-radius: 7px; padding: .4rem 1.2rem; font: 600 var(--floor) system-ui; cursor: pointer; flex: 0 0 auto;
}
.try-bar button:disabled { opacity: .5; cursor: default; }
.try-bar pre { margin: 0; flex: 1; font: var(--floor)/1.5 var(--mono); color: #9aa8ba; white-space: pre-wrap; word-break: break-word; max-height: 10rem; overflow: auto; }
.try-bar pre.has { color: var(--s); }
.try-bar pre .err { color: var(--error); }

/* -------------------------------------------------------------- misc */
.steps { counter-reset: s; list-style: none; padding: 0; max-width: 46rem; }
.steps li { counter-increment: s; position: relative; padding-left: 2.8rem; margin-bottom: 1rem; color: var(--ink-soft); }
.steps li::before {
    content: counter(s); position: absolute; left: 0; top: .05rem;
    width: 2rem; height: 2rem; border-radius: 50%;
    background: var(--accent-soft); color: var(--accent);
    font-weight: 700; font-size: var(--floor); display: grid; place-items: center;
}
.steps li b { color: var(--ink); }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1rem; background: var(--accent-soft); border: 1px solid var(--border); border-radius: 12px; padding: 1.4rem; text-align: center; }
.stats .num { font-size: 1.9rem; font-weight: 700; color: var(--accent); }
.stats .what { font-size: var(--read); color: var(--ink-soft); }
/* The race panel on the front page: what was won on the left, what was
   lost on the right, at the same size and the same weight. A comparison
   that shrinks its own losses is an advertisement. */
.race { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; margin: 1.6rem 0; }
@media (max-width: 767px) { .race { grid-template-columns: 1fr; } }
.race > div { border-radius: 12px; padding: 1.2rem 1.3rem; border: 1px solid var(--border); }
.race .won { background: var(--accent-soft); border-color: transparent; }
.race .cap { margin: 0 0 .7rem; font-weight: 700; color: var(--ink); }
.race .won .cap { color: var(--accent); }
.race ul { margin: 0; padding-left: 1.1rem; }
.race li { color: var(--ink-soft); margin-bottom: .55rem; }
.race li:last-child { margin-bottom: 0; }
.race .num { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink); }

figure { margin: 0; }
/* height:auto is not optional beside width:100%. An image SHOULD carry
   its intrinsic width and height so the page reserves its space and
   nothing jumps when it lands (Zui rule 38) — and without this line
   that honest height attribute is exactly what squashes it. */
figure img { width: 100%; height: auto; border: 1px solid var(--border);
             border-radius: 12px; display: block; }
figcaption { font-size: var(--read); color: var(--ink-soft); text-align: center; margin-top: .9rem; line-height: 1.6; }

footer { border-top: 1px solid var(--border); margin-top: 2rem; padding: 2rem 0 3rem; color: var(--ink-soft); font-size: var(--floor); }
footer a { color: var(--accent); text-decoration: none; }
footer .wrap { display: flex; gap: 1.2rem; flex-wrap: wrap; align-items: center; }
footer .sp { margin-left: auto; }

/* ------------------------------------------------------------------
   RingServ signature: the logo's violet-to-blue sweep, as a gradient,
   in EXACTLY three places -- the hero's one word, the top bar's
   Download, and the primary button. The father site's own rule (Zui
   118, quoted in its source): a colour that appears everywhere stops
   meaning anything. So the sweep is the page's rarest thing.
   Both gradient ends measure over 4.5 on white, so every point
   between them does too. */
.btn.primary, nav .cta {
    background: linear-gradient(115deg, #8a2be2 45%, #2d5fd3);
}
.btn.primary:hover, nav .cta:hover {
    background: linear-gradient(115deg, #9b3ff2 45%, #3a6ce0);
    opacity: 1;
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
    .hero h1 em {
        /* Mostly violet, resolving to indigo -- never to full blue: the
           word is the page's signature, and the point of the change was
           to stop blue from wearing it. */
        background: linear-gradient(100deg, #8a2be2 55%, #5a35d8);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
    }
}

/* The GitHub mark in the nav: an icon, aligned with the text links,
   quiet until hovered -- it is a doorway, not a call to action. */
nav a.gh { display: inline-flex; align-items: center; color: var(--ink-soft); }
nav a.gh:hover { color: var(--accent); }
nav a.gh svg { display: block; }
