/*
 * Omarchy Guild design system, adapted from The Rails Guild's Console Ledger.
 * Replaces the scaffold application.css entirely. Propshaft-ready, no preprocessing.
 * Load order inside this file matters: tokens, then element base, then components.
 * Webfonts are declared in fonts.css (vendored woff2).
 *
 * Placeholder palette pending the A2 design note: dark-first, tuned to sit next
 * to a Tokyo Night desktop screenshot. Variable names are kept identical to the
 * Rails Guild sheet (parchment/wax/gold/ruby) so components port cleanly; only
 * the hues differ. Swap tokens here, never in components.
 */
/* ===== colors.css ===== */
:root{
/* Base surfaces (dark) */
--parchment:#1a1b26;--parchment-deep:#16161e;--parchment-bright:#1f2335;--parchment-shadow:#000;
--line:rgba(122,162,247,.24);--line-strong:#3d59a1;
--ink:#c0caf5;--ink-soft:#a9b1d6;--ink-faint:#565f89;
/* Blue, the accent */
--wax:#7aa2f7;--wax-dark:#3d59a1;--wax-bright:#b4c4fa;
--gold:#5d7ab8;--gold-dark:#3d59a1;--gold-bright:#9bb2ef;
/* Green, verification only */
--ruby:#9ece6a;--ruby-dark:#73a944;--ruby-bright:#c5ea9b;
--moss:#9ece6a;--moss-dark:#73a944;
--text-on-wax:#16161e;
/* Pop: one hue per theme that wax, gold and ruby never use, so whatever wears
   it is the only thing on the page in that colour. Spent on the tip jar. */
--pop:#ff9e64;
--glow:rgba(122,162,247,.45);--glow-soft:rgba(122,162,247,.22);--glow-ruby:rgba(158,206,106,.45);
/* Globe: read by the canvas, so the theme still owns every colour on it. */
--globe-ocean:#16161e;--globe-land:#3d59a1;--globe-grid:rgba(122,162,247,.14);
--globe-dot:#9ece6a;--globe-halo:rgba(158,206,106,.7);
}
/* Light theme: same system on daylight surfaces. Set data-theme="light" on <html>. */
[data-theme="light"]{
--parchment:#e1e2e7;--parchment-deep:#d5d6db;--parchment-bright:#f2f3f7;--parchment-shadow:#c8c9ce;
--line:rgba(46,125,233,.3);--line-strong:#2e7de9;
--ink:#343b58;--ink-soft:#4c536e;--ink-faint:#8990b3;
--wax:#2e7de9;--wax-dark:#1f5bb8;--wax-bright:#4a91f0;
--gold:#2e7de9;--gold-dark:#1f5bb8;--gold-bright:#1f5bb8;
--ruby:#587539;--ruby-dark:#455c2e;--ruby-bright:#455c2e;
--moss:#587539;--moss-dark:#455c2e;
--text-on-wax:#f2f3f7;
--pop:#b15c00;
--glow:rgba(46,125,233,.35);--glow-soft:rgba(46,125,233,.18);--glow-ruby:rgba(88,117,57,.25);
--globe-ocean:#d5d6db;--globe-land:#2e7de9;--globe-grid:rgba(46,125,233,.2);
--globe-dot:#587539;--globe-halo:rgba(88,117,57,.55);
}

/* ===== typography.css ===== */
/* IBM Plex Mono everywhere, the terminal voice. Georgia retained only as a fallback stack. */
:root{
--font-serif:Georgia,'Times New Roman',serif;
--font-mono:'IBM Plex Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace;
--font-rank:var(--font-mono);
--text-xs:.7rem;--text-sm:.8rem;--text-md:.95rem;--text-lg:1.05rem;--text-xl:1.35rem;--text-2xl:2rem;--text-3xl:2.6rem;
--leading-tight:1.25;--leading-body:1.6;
--tracking-caps:.08em;
--letterpress:none;
--letterpress-deep:none;
}

/* ===== spacing.css ===== */
:root{
--space-1:.25rem;--space-2:.5rem;--space-3:.75rem;--space-4:1rem;--space-5:1.5rem;--space-6:2rem;--space-7:3rem;--space-8:4rem;
--radius-sm:0;--radius-md:2px;--radius-lg:4px;--radius-full:50%;
--measure:38rem;--page-max:64rem;
}

/* ===== effects.css ===== */
/* Same reasoning as the derived tokens below: these read --gold, --line and
   --glow, so they must be declared on the themed element as well. */
:root,[data-theme]{
/* Flat frames + gem glow. Hairlines are the structure; glow is reserved for gems and focus. */
--shadow-card:none;
--shadow-inset:none;
/* The 1px rim keeps the silhouette's edges reading at full width on the dark ground. */
--seal-rim:drop-shadow(0 0 1px var(--gold)) drop-shadow(0 0 1px var(--gold));
--seal-glow:var(--seal-rim) drop-shadow(0 0 6px rgba(0,0,0,.6)) drop-shadow(0 0 7px var(--glow));
/* A clean heater shield, symmetric on purpose: it belongs to no logo, so a
   future Omarchy Guild mark can replace it without ever having matched it. */
--seal-shape:polygon(50% 0%,58% 3%,70% 6.5%,84% 9%,100% 10.5%,100% 42%,98.5% 52%,95% 62%,89% 72%,80.5% 81.5%,69% 90%,50% 100%,31% 90%,19.5% 81.5%,11% 72%,5% 62%,1.5% 52%,0% 42%,0% 10.5%,16% 9%,30% 6.5%,42% 3%);
--rule-major:1px solid var(--gold);--rule-double:1px solid var(--gold);--rule-hairline:1px solid var(--line);
--focus-outline:1px solid var(--focus-ring);
}

/* ===== themes.css ===== */
/* The member-selectable themes, named for their Omarchy desktop counterparts.
   :root above IS tokyo_night, the default; each block below overrides only raw
   hues plus the radius character, and the semantic aliases cascade through.
   These sit AFTER every :root token block on purpose: [data-theme] ties :root
   on specificity, so source order is what lets the radius overrides win. */
/* Catppuccin Mocha: mauve primary, peach buttons, green verification. Roundest of the set. */
[data-theme="catppuccin"]{
--parchment:#1e1e2e;--parchment-deep:#181825;--parchment-bright:#252539;--parchment-shadow:#11111b;
--line:rgba(203,166,247,.22);--line-strong:#7e5cae;
--ink:#cdd6f4;--ink-soft:#bac2de;--ink-faint:#6c7086;
--wax:#cba6f7;--wax-dark:#7e5cae;--wax-bright:#e2d0fb;
--gold:#c98f6f;--gold-dark:#8f6249;--gold-bright:#fab387;
--ruby:#a6e3a1;--ruby-dark:#6faf69;--ruby-bright:#c9f0c5;
--moss:#a6e3a1;--moss-dark:#6faf69;
--text-on-wax:#11111b;
--pop:#f9e2af;
--glow:rgba(203,166,247,.45);--glow-soft:rgba(203,166,247,.22);--glow-ruby:rgba(166,227,161,.45);
--globe-ocean:#181825;--globe-land:#7e5cae;--globe-grid:rgba(203,166,247,.14);
--globe-dot:#a6e3a1;--globe-halo:rgba(166,227,161,.7);
--radius-sm:6px;--radius-md:8px;--radius-lg:12px;
}
/* Gruvbox dark: yellow primary, orange buttons, aqua verification. Sharp corners, matte glow. */
[data-theme="gruvbox"]{
--parchment:#282828;--parchment-deep:#1d2021;--parchment-bright:#32302f;--parchment-shadow:#141617;
--line:rgba(250,189,47,.18);--line-strong:#b57614;
--ink:#ebdbb2;--ink-soft:#d5c4a1;--ink-faint:#928374;
--wax:#fabd2f;--wax-dark:#d79921;--wax-bright:#fce094;
--gold:#d65d0e;--gold-dark:#af3a03;--gold-bright:#fe8019;
--ruby:#8ec07c;--ruby-dark:#689d6a;--ruby-bright:#b8dcaa;
--moss:#8ec07c;--moss-dark:#689d6a;
--text-on-wax:#1d2021;
--pop:#d3869b;
--glow:rgba(250,189,47,.3);--glow-soft:rgba(250,189,47,.15);--glow-ruby:rgba(142,192,124,.35);
--globe-ocean:#1d2021;--globe-land:#b57614;--globe-grid:rgba(250,189,47,.12);
--globe-dot:#8ec07c;--globe-halo:rgba(142,192,124,.7);
--radius-sm:0;--radius-md:0;--radius-lg:0;
}
/* Rose Pine main: rose primary, gold buttons, foam verification, pine landmasses. Soft corners. */
[data-theme="rose_pine"]{
--parchment:#191724;--parchment-deep:#15121e;--parchment-bright:#1f1d2e;--parchment-shadow:#0e0c15;
--line:rgba(235,188,186,.18);--line-strong:#6e6a86;
--ink:#e0def4;--ink-soft:#908caa;--ink-faint:#6e6a86;
--wax:#ebbcba;--wax-dark:#b0807d;--wax-bright:#f4dcda;
--gold:#a98953;--gold-dark:#7a6238;--gold-bright:#f6c177;
--ruby:#9ccfd8;--ruby-dark:#31748f;--ruby-bright:#c3e5eb;
--moss:#9ccfd8;--moss-dark:#31748f;
--text-on-wax:#191724;
--pop:#eb6f92;
--glow:rgba(235,188,186,.4);--glow-soft:rgba(235,188,186,.2);--glow-ruby:rgba(156,207,216,.45);
--globe-ocean:#15121e;--globe-land:#31748f;--globe-grid:rgba(196,167,231,.14);
--globe-dot:#9ccfd8;--globe-halo:rgba(156,207,216,.7);
--radius-sm:3px;--radius-md:4px;--radius-lg:8px;
}
/* Everforest: aqua primary, yellow buttons, its own green for verification.
   Green is the theme's signature but verification owns green here, so aqua
   leads and the two never collide. Soft, organic corners. */
[data-theme="everforest"]{
--parchment:#2d353b;--parchment-deep:#272f35;--parchment-bright:#374145;--parchment-shadow:#1e2326;
--line:rgba(127,187,179,.2);--line-strong:#558b83;
--ink:#d3c6aa;--ink-soft:#b9ad94;--ink-faint:#859289;
--wax:#7fbbb3;--wax-dark:#558b83;--wax-bright:#a5d3cc;
--gold:#a8905f;--gold-dark:#7d6a45;--gold-bright:#dbbc7f;
--ruby:#a7c080;--ruby-dark:#7d9660;--ruby-bright:#c0d4a0;
--moss:#a7c080;--moss-dark:#7d9660;
--text-on-wax:#272f35;
--pop:#e69875;
--glow:rgba(127,187,179,.4);--glow-soft:rgba(127,187,179,.2);--glow-ruby:rgba(167,192,128,.45);
--globe-ocean:#272f35;--globe-land:#558b83;--globe-grid:rgba(127,187,179,.14);
--globe-dot:#a7c080;--globe-halo:rgba(167,192,128,.7);
--radius-sm:4px;--radius-md:6px;--radius-lg:10px;
}
/* Kanagawa: crystal blue primary, boat yellow buttons, autumn green verified.
   Ink-brush softness in the corners, deep sumi ground. */
[data-theme="kanagawa"]{
--parchment:#1f1f28;--parchment-deep:#16161d;--parchment-bright:#2a2a37;--parchment-shadow:#090618;
--line:rgba(126,156,216,.2);--line-strong:#54638f;
--ink:#dcd7ba;--ink-soft:#c8c093;--ink-faint:#727169;
--wax:#7e9cd8;--wax-dark:#54638f;--wax-bright:#a3b8e8;
--gold:#c0a36e;--gold-dark:#8a7449;--gold-bright:#e6c384;
--ruby:#98bb6c;--ruby-dark:#557046;--ruby-bright:#b5d18e;
--moss:#98bb6c;--moss-dark:#557046;
--text-on-wax:#16161d;
--pop:#ffa066;
--glow:rgba(126,156,216,.4);--glow-soft:rgba(126,156,216,.2);--glow-ruby:rgba(152,187,108,.45);
--globe-ocean:#16161d;--globe-land:#2d4f67;--globe-grid:rgba(126,156,216,.14);
--globe-dot:#98bb6c;--globe-halo:rgba(152,187,108,.7);
--radius-sm:3px;--radius-md:5px;--radius-lg:9px;
}
/* Nord: frost cyan primary, aurora yellow buttons, aurora green verified.
   The flattest of the set, crisp corners to match its poster cleanliness. */
[data-theme="nord"]{
--parchment:#2e3440;--parchment-deep:#272c36;--parchment-bright:#3b4252;--parchment-shadow:#1e222a;
--line:rgba(136,192,208,.2);--line-strong:#5e8a99;
--ink:#d8dee9;--ink-soft:#b8c1cf;--ink-faint:#6d7789;
--wax:#88c0d0;--wax-dark:#5e8a99;--wax-bright:#a9d4e0;
--gold:#b9a06d;--gold-dark:#8a7650;--gold-bright:#ebcb8b;
--ruby:#a3be8c;--ruby-dark:#7d9669;--ruby-bright:#bcd3aa;
--moss:#a3be8c;--moss-dark:#7d9669;
--text-on-wax:#2e3440;
--pop:#d08770;
--glow:rgba(136,192,208,.4);--glow-soft:rgba(136,192,208,.2);--glow-ruby:rgba(163,190,140,.45);
--globe-ocean:#272c36;--globe-land:#5e8a99;--globe-grid:rgba(136,192,208,.14);
--globe-dot:#a3be8c;--globe-halo:rgba(163,190,140,.7);
--radius-sm:2px;--radius-md:3px;--radius-lg:5px;
}
/* Osaka Jade: jade primary on a deep green-black ground, brass buttons.
   The most saturated accent of the set, so the glow is dialled back. */
[data-theme="osaka_jade"]{
--parchment:#111c18;--parchment-deep:#0c1512;--parchment-bright:#17251f;--parchment-shadow:#060b09;
--line:rgba(45,213,183,.18);--line-strong:#1a8a78;
--ink:#c1c497;--ink-soft:#a8ab84;--ink-faint:#53685b;
--wax:#2dd5b7;--wax-dark:#1a8a78;--wax-bright:#8cd3cb;
--gold:#b39a2a;--gold-dark:#7d6b1d;--gold-bright:#e5c736;
--ruby:#549e6a;--ruby-dark:#3d7a4f;--ruby-bright:#9eebb3;
--moss:#549e6a;--moss-dark:#3d7a4f;
--text-on-wax:#0c1512;
--pop:#d2689c;
--glow:rgba(45,213,183,.35);--glow-soft:rgba(45,213,183,.16);--glow-ruby:rgba(84,158,106,.45);
--globe-ocean:#0c1512;--globe-land:#1a8a78;--globe-grid:rgba(45,213,183,.14);
--globe-dot:#9eebb3;--globe-halo:rgba(158,235,179,.7);
--radius-sm:4px;--radius-md:6px;--radius-lg:10px;
}
/* Ristretto: periwinkle primary, warm yellow buttons, on roasted coffee.
   The warmest ground of the set; corners rounded to match. */
[data-theme="ristretto"]{
--parchment:#2c2525;--parchment-deep:#241e1e;--parchment-bright:#363030;--parchment-shadow:#1a1515;
--line:rgba(168,169,235,.2);--line-strong:#6f70a8;
--ink:#e6d9db;--ink-soft:#c3b7b8;--ink-faint:#948a8b;
--wax:#a8a9eb;--wax-dark:#6f70a8;--wax-bright:#bebffd;
--gold:#c19a4a;--gold-dark:#8a6d2f;--gold-bright:#fcd675;
--ruby:#adda78;--ruby-dark:#7ea253;--ruby-bright:#c8e292;
--moss:#adda78;--moss-dark:#7ea253;
--text-on-wax:#241e1e;
--pop:#fd6883;
--glow:rgba(168,169,235,.4);--glow-soft:rgba(168,169,235,.2);--glow-ruby:rgba(173,218,120,.45);
--globe-ocean:#241e1e;--globe-land:#6f70a8;--globe-grid:rgba(168,169,235,.14);
--globe-dot:#adda78;--globe-halo:rgba(173,218,120,.7);
--radius-sm:5px;--radius-md:7px;--radius-lg:11px;
}
/* Catppuccin Latte: the one light theme. Everything inverts, so "bright" stops
   meaning lighter and starts meaning more contrast against a pale ground:
   wax-bright is darker than wax, because it is what hover and emphasis use. */
[data-theme="catppuccin_latte"]{
--parchment:#eff1f5;--parchment-deep:#e6e9ef;--parchment-bright:#ffffff;--parchment-shadow:#dce0e8;
--line:rgba(30,102,245,.26);--line-strong:#1e66f5;
--ink:#4c4f69;--ink-soft:#5c5f77;--ink-faint:#8c8fa1;
--wax:#1e66f5;--wax-dark:#1a4fbd;--wax-bright:#0b4fd8;
--gold:#8839ef;--gold-dark:#5b21b6;--gold-bright:#7c3aed;
--ruby:#40a02b;--ruby-dark:#2f7d21;--ruby-bright:#2f7d21;
--moss:#40a02b;--moss-dark:#2f7d21;
--text-on-wax:#eff1f5;
--pop:#fe640b;
--glow:rgba(30,102,245,.3);--glow-soft:rgba(30,102,245,.15);--glow-ruby:rgba(64,160,43,.3);
--globe-ocean:#e6e9ef;--globe-land:#1e66f5;--globe-grid:rgba(30,102,245,.2);
--globe-dot:#40a02b;--globe-halo:rgba(64,160,43,.55);
--radius-sm:6px;--radius-md:8px;--radius-lg:12px;
}
/* Matte Black: greys and one amber. The chrome goes monochrome, which is the
   whole point of the theme, so amber is spent only on links and focus.
   Verification keeps a green even though this palette has none: matte-black
   maps its green slot to amber, and a verified badge that matches the link
   colour stops meaning anything. Sharpest corners of the set. */
[data-theme="matte_black"]{
--parchment:#121212;--parchment-deep:#0a0a0a;--parchment-bright:#1c1c1c;--parchment-shadow:#000;
--line:rgba(255,193,7,.16);--line-strong:#5c5c5e;
--ink:#bebebe;--ink-soft:#a0a0a0;--ink-faint:#8a8a8d;
--wax:#ffc107;--wax-dark:#a87d05;--wax-bright:#ffd75f;
--gold:#8a8a8d;--gold-dark:#5c5c5e;--gold-bright:#bebebe;
--ruby:#7cb342;--ruby-dark:#5a8a2f;--ruby-bright:#a5d16a;
--moss:#7cb342;--moss-dark:#5a8a2f;
--text-on-wax:#121212;
--pop:#ffc107;
--glow:rgba(255,193,7,.35);--glow-soft:rgba(255,193,7,.15);--glow-ruby:rgba(124,179,66,.4);
--globe-ocean:#0a0a0a;--globe-land:#5c5c5e;--globe-grid:rgba(255,193,7,.12);
--globe-dot:#7cb342;--globe-halo:rgba(124,179,66,.7);
--radius-sm:0;--radius-md:1px;--radius-lg:2px;
}

/* ===== derived tokens ===== */
/* Declared on :root AND on the themed element on purpose. A custom property
   that says var(--ink) resolves where it is declared, so these living only on
   :root would resolve against the default hues on <html> and inherit into the
   page already frozen, leaving every theme with Tokyo Night's text. The theme
   is stamped on <body> (Turbo never updates <html> attributes), so [data-theme]
   has to be in this selector list. Raw hues stay in :root above; only things
   derived from them belong here. */
:root,[data-theme]{
--surface-page:var(--parchment);--surface-card:var(--parchment-bright);--surface-inset:var(--parchment-deep);
--text-body:var(--ink);--text-muted:var(--ink-soft);--text-faint:var(--ink-faint);
--accent:var(--wax);--accent-hover:var(--wax-bright);
--link:var(--wax);--link-hover:var(--wax-bright);
--border-default:var(--line);--border-strong:var(--line-strong);--border-ink:var(--gold);
--verified:var(--ruby);--verified-bright:var(--ruby-bright);--unverified:var(--ink-faint);
/* Good and wrong, in one green and one red that never change with the theme.
   The twelve grounds run from near black to near white, and no single pair of
   text colours clears contrast at both ends, so an alert brings its own dark
   ground and is the one thing here a theme does not colour. */
--alert-ok:#86efac;--alert-bad:#ff6b6b;--alert-ground:#14141b;
--focus-ring:var(--wax);
}

/* ===== base.css ===== */
*{box-sizing:border-box}
body{margin:0;color:var(--text-body);font-family:var(--font-mono);font-size:var(--text-md);line-height:var(--leading-body);background:var(--parchment)}
h1,h2,h3,h4{font-family:var(--font-mono);font-weight:600;line-height:var(--leading-tight);color:var(--text-body);margin:0 0 var(--space-3)}
h1{font-size:var(--text-2xl)}
h2{font-size:var(--text-lg);text-transform:uppercase;letter-spacing:.08em}
h2::before{content:"// ";color:var(--gold)}
h3{font-size:1rem}
p{margin:0 0 var(--space-3)}
a{color:var(--link);text-decoration:underline;text-decoration-color:var(--line);text-underline-offset:2px}
a:hover{color:var(--link-hover);text-decoration-color:currentColor}
code{font-family:var(--font-mono);font-size:.85em;background:var(--surface-inset);border:var(--rule-hairline);border-radius:var(--radius-md);padding:.1em .35em;color:var(--text-muted)}
small{font-size:var(--text-sm);color:var(--text-faint)}
hr{border:0;border-top:var(--rule-major);margin:var(--space-5) 0}
:focus-visible{outline:var(--focus-outline);outline-offset:2px}
::selection{background:var(--wax);color:var(--parchment)}
label{display:inline-block;font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-muted);letter-spacing:.04em;text-transform:lowercase}
input[type="text"],input[type="email"],input[type="search"],input[type="url"],input[type="number"],textarea,select{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-body);background:transparent;border:1px solid var(--line);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);width:100%;max-width:var(--measure)}
input::placeholder,textarea::placeholder{color:var(--text-faint);opacity:1}
input:focus-visible,textarea:focus-visible,select:focus-visible{outline:1px solid var(--gold);outline-offset:0}
/* Ticks and choices, drawn rather than borrowed. The native radio is the one
   control a browser paints in its own blue whatever the page says, which reads
   as a piece of somebody else's form; accent-color only tints it. Both are
   built from the same parts so they look related: square for many, round for
   one. The checkbox radius is capped because the rounder themes would take a
   16px box most of the way to a circle and lose that distinction. */
input[type="checkbox"],input[type="radio"]{appearance:none;-webkit-appearance:none;flex:none;display:inline-grid;place-content:center;width:1rem;height:1rem;margin:0;border:1px solid var(--line);background:transparent;cursor:pointer;vertical-align:-2px}
input[type="checkbox"]{border-radius:min(var(--radius-sm),3px)}
input[type="radio"]{border-radius:var(--radius-full)}
input[type="checkbox"]:hover,input[type="radio"]:hover{border-color:var(--gold)}
input[type="checkbox"]:checked,input[type="radio"]:checked{border-color:var(--gold-bright)}
input[type="checkbox"]:focus-visible,input[type="radio"]:focus-visible{outline:1px solid var(--gold);outline-offset:2px}
/* The mark itself. Clipped and drawn rather than set as a glyph, so a form
   still reads correctly before the mono face has loaded. */
input[type="checkbox"]::before,input[type="radio"]::before{content:"";background:var(--gold-bright);transform:scale(0)}
input[type="checkbox"]::before{width:.62rem;height:.62rem;clip-path:polygon(14% 44%,0 60%,39% 100%,100% 18%,84% 4%,39% 70%)}
input[type="radio"]::before{width:.5rem;height:.5rem;border-radius:var(--radius-full)}
input[type="checkbox"]:checked::before,input[type="radio"]:checked::before{transform:scale(1)}
input[type="file"]{font-family:var(--font-mono);font-size:var(--text-sm)}
input[type="file"]{font-family:var(--font-mono);font-size:var(--text-sm)}
/* Consent sits outside the lowercase label convention: it is a legal notice and
   has to read as written. */
.consent{max-width:var(--measure)}
.consent label{display:inline;text-transform:none;letter-spacing:normal;font-size:var(--text-sm);color:var(--text-body);line-height:1.5}

/* ===== guild.css ===== */
/* Guild components. */
.button,button,input[type="submit"]{display:inline-block;font-family:var(--font-mono);font-weight:600;font-size:var(--text-sm);color:var(--gold-bright);background:transparent;border:1px solid var(--gold);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-4);cursor:pointer;text-decoration:none;text-transform:lowercase;white-space:nowrap;line-height:1.3}
.button:hover,button:hover,input[type="submit"]:hover{background:var(--gold);color:var(--parchment)}
.button--secondary{color:var(--text-muted);border-color:var(--line)}
.prompt-empty{color:var(--text-muted)}
.button--secondary:hover{background:transparent;border-color:var(--gold);color:var(--gold-bright)}
.button--quiet{color:var(--text-faint);border-color:transparent;font-weight:400;text-decoration:underline;text-decoration-color:var(--line)}
.button--quiet:hover{background:transparent;color:var(--gold-bright)}
/* Share: the one green link on the page, so it pops out of the blue and gold. */
.profile__share{font-family:var(--font-mono);font-weight:600;font-size:var(--text-sm);color:var(--ruby-bright);text-decoration-color:var(--ruby)}
.profile__share:hover{color:var(--ruby);text-decoration-color:currentColor}
/* Buy me a coffee: a TUI action. Brackets instead of a border, the way a
   terminal draws a button, in the theme's pop colour, so it is the one thing
   on the page in that hue whatever the theme. Hover inverts it like a selected
   row. The cup is Buy Me a Coffee's mark as a mask painted in currentColor,
   so it takes the pop colour too and needs no per-theme artwork. */
.coffee{display:inline-flex;align-items:baseline;gap:.45em;font-family:var(--font-mono);font-weight:600;font-size:var(--text-sm);color:var(--pop);background:transparent;padding:var(--space-1) var(--space-2);margin:0 calc(-1 * var(--space-2));text-decoration:none;text-transform:lowercase;white-space:nowrap;line-height:1.3}
/* The brackets run a little large, as a terminal's would, and tall enough to
   frame the mug and its steam. */
/* The side padding is pulled back out with a matching negative margin, so the
   opening bracket sits flush with the text above it and the hover fill still
   has room around the words. */
/* The footer paints its links faint; the button in that row keeps its pop
   colour and its inverted hover. It also keeps the negative side margin: on a
   wide screen that makes the space after Contact equal the row's gap, and on
   a narrow one, where the button wraps to its own line, it puts the bracket
   on the same left edge as the links above it. */
.site-footer__links a.coffee{color:var(--pop)}
.site-footer__links a.coffee:hover{color:var(--parchment)}
.coffee::before{content:"[";font-size:1.2em}
.coffee::after{content:"]";font-size:1.2em}
.coffee:hover{background:var(--pop);color:var(--parchment)}
.coffee:focus-visible{outline:1px solid var(--pop);outline-offset:2px}
/* The mug and its steam share one pixel size, so both sit on the same grid.
   The mug is an eight by five pixel drawing: a cup a little wider than it is
   tall, with a handle on the right, as a mask painted in currentColor. Sized in pixels, not ems,
   so the mask lands on whole pixels and stays crisp. The button aligns its
   parts by baseline, and a box with no text puts its baseline at its bottom
   margin edge, so the mug sits on the text line like a glyph would, sunk a
   pixel the way an emoji is. The top margin is the steam's room; the steam
   may rise past the brackets, the mug itself does not. */
.coffee__mug{--px:2px;position:relative;display:inline-flex;flex:none;margin-top:calc(4 * var(--px));margin-bottom:calc(-1 * var(--px))}
.coffee__cup{display:inline-block;width:calc(8 * var(--px));height:calc(5 * var(--px));flex:none;background:currentColor;-webkit-mask:url("/assets/mug-28b18699.svg") center/contain no-repeat;mask:url("/assets/mug-28b18699.svg") center/contain no-repeat}
/* Steam: two wisps of four pixels each, drawn as box-shadows on one box that
   paints nothing itself. Each wisp snakes one pixel left and right as it
   climbs, both bending the same way together, and every frame moves the wave
   one pixel up. Four frames, stepped rather than eased, is what makes it read
   as pixels; the pixels thin out with height. Centred over the cup body, not
   the handle. The rest state (no motion preference) is the first frame. */
.coffee__steam{--s0:color-mix(in srgb,currentColor 60%,transparent);--s1:color-mix(in srgb,currentColor 45%,transparent);--s2:color-mix(in srgb,currentColor 30%,transparent);--s3:color-mix(in srgb,currentColor 15%,transparent);position:absolute;left:calc(2 * var(--px));top:calc(-1 * var(--px));width:var(--px);height:var(--px);box-shadow:calc(-2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(-1 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(3 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(-2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(-3 * var(--px)) calc(-3 * var(--px)) var(--s3),calc(1 * var(--px)) calc(-3 * var(--px)) var(--s3)}
@keyframes coffee-steam{
0%{box-shadow:calc(-2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(-1 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(3 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(-2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(-3 * var(--px)) calc(-3 * var(--px)) var(--s3),calc(1 * var(--px)) calc(-3 * var(--px)) var(--s3)}
25%{box-shadow:calc(-3 * var(--px)) calc(0 * var(--px)) var(--s0),calc(1 * var(--px)) calc(0 * var(--px)) var(--s0),calc(-2 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(2 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(-1 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(3 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(-2 * var(--px)) calc(-3 * var(--px)) var(--s3),calc(2 * var(--px)) calc(-3 * var(--px)) var(--s3)}
50%{box-shadow:calc(-2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(-3 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(1 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(-2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(-1 * var(--px)) calc(-3 * var(--px)) var(--s3),calc(3 * var(--px)) calc(-3 * var(--px)) var(--s3)}
75%{box-shadow:calc(-1 * var(--px)) calc(0 * var(--px)) var(--s0),calc(3 * var(--px)) calc(0 * var(--px)) var(--s0),calc(-2 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(2 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(-3 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(1 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(-2 * var(--px)) calc(-3 * var(--px)) var(--s3),calc(2 * var(--px)) calc(-3 * var(--px)) var(--s3)}
100%{box-shadow:calc(-2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(2 * var(--px)) calc(0 * var(--px)) var(--s0),calc(-1 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(3 * var(--px)) calc(-1 * var(--px)) var(--s1),calc(-2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(2 * var(--px)) calc(-2 * var(--px)) var(--s2),calc(-3 * var(--px)) calc(-3 * var(--px)) var(--s3),calc(1 * var(--px)) calc(-3 * var(--px)) var(--s3)}
}
/* Chrome */
.page{max-width:var(--page-max);margin:0 auto;padding:0 var(--space-5)}
.site-header{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;padding:var(--space-4) 0;border-bottom:1px solid var(--gold)}
.site-header nav{display:flex;gap:1rem;align-items:baseline}
.site-header__brand{font-family:var(--font-mono);font-size:1rem;font-weight:600;color:var(--text-body);text-decoration:none}
.site-header__brand{display:inline-flex;align-items:center;gap:.5em;white-space:nowrap}
.site-header__brand:hover{color:var(--gold-bright)}
.site-header nav a{color:var(--text-muted);text-decoration:none;font-size:var(--text-sm);padding:var(--space-2) 0;white-space:nowrap}
.site-header nav a:hover{color:var(--gold-bright)}
.site-header nav form{display:inline}
.site-header nav button{background:none;border:0;padding:var(--space-2) 0;margin:0;color:var(--text-muted);font-weight:400;text-transform:none;font-size:var(--text-sm);cursor:pointer;white-space:nowrap}
.site-header nav button:hover{background:none;color:var(--gold-bright)}
.site-header__logo{height:1.5em;width:auto;vertical-align:middle}
.site-footer{margin-top:var(--space-8);padding:var(--space-5) 0;border-top:var(--rule-hairline);color:var(--text-faint)}
.site-footer small{color:var(--text-faint);font-size:var(--text-xs)}
/* Baseline, not centre: the coffee button at the end of the row is taller than
   the links (its steam needs headroom), and baseline keeps its words on the
   same line as theirs. */
.site-footer__links{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--space-4);font-size:var(--text-xs);margin-bottom:var(--space-3)}
.site-footer__links a{color:var(--text-faint);text-decoration-color:var(--line)}
.site-footer__links a:hover{color:var(--gold-bright)}
/* The copyright and the fine print under it read as one block. Both are set
   small inside a body-sized paragraph, so the line box, not the margin, is
   most of the gap: size the paragraphs down too and leave a hair between. */
.site-footer__copyright,.site-footer__disclaimer{font-size:var(--text-xs);line-height:1.4}
.site-footer__copyright{margin-bottom:var(--space-1)}
/* Fine print; muted rather than faint because it sits below 12px. */
.site-footer__disclaimer small{font-size:.6rem;color:var(--text-muted)}
/* Legal pages */
.legal{margin-top:var(--space-6);max-width:var(--measure)}
.legal h2{margin-top:var(--space-6)}
.legal ul{padding-left:var(--space-5)}
.legal li{margin-bottom:var(--space-2)}
/* Keys in prose. The nav's .hotkey chips hide until Shift is held; a key
   named in a sentence has to be legible all the time, so it borrows the
   same look without the visibility trick. */
.legal kbd{display:inline-block;font-family:var(--font-mono);font-size:.78rem;font-weight:600;color:var(--wax);border:1px solid var(--wax-dark);background:var(--surface-inset);padding:0 .45em;vertical-align:1px}
/* The badge list reads as prose, so the seal sits at the cap height of the
   line rather than at the 44px it gets on a profile. */
.legal .badge__seal::before{display:inline-flex;width:26px;height:30px;padding-bottom:4px;font-size:.7rem}
.legal .badge__seal{vertical-align:-9px;margin-right:var(--space-1)}
/* One key a line, no bullets: this is a reference to scan down, not prose.
   Every key is one character wide, so the meanings line up on their own. */
.legal .keys{list-style:none;padding-left:0}
.legal .keys li{margin-bottom:var(--space-1)}
/* Hero: a boxed frame */
.hero{border:1px solid var(--gold);margin-top:var(--space-5);padding:var(--space-6) var(--space-4);text-align:center}
.hero h1{font-size:var(--text-2xl)}
.hero__mark{margin:0 auto var(--space-3)}
.hero__mark img{height:110px;width:auto}

.hero p{max-width:var(--measure);margin:0 auto var(--space-4);color:var(--text-muted)}
.hero__tagline{font-size:var(--text-lg);color:var(--text-body);margin-bottom:var(--space-2)}
.hero__sub{font-size:var(--text-sm);color:var(--text-muted)}
.hero form{display:flex;justify-content:center;gap:var(--space-2);max-width:24rem;margin:0 auto}
/* Flash */
.flash{padding:var(--space-3) var(--space-4);border:1px solid;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-sm);margin:var(--space-3) 0;background:var(--alert-ground)}
.flash--notice{border-color:var(--alert-ok);color:var(--alert-ok)}
.flash--alert{border-color:var(--alert-bad);color:var(--alert-bad)}
/* On a phone the flash lands at the top of the document, and a suggestion sent
   from the foot of a long page comes back to the foot of that page: you press
   send and nothing appears to happen. So on a narrow screen it sits in the
   middle instead, over whatever you came back to. pointer-events:none so it can
   never swallow a tap while it is there. A confirmation takes itself away
   again; a failure stays put, because that one has to be read. */
@media (max-width:640px){
.flash{position:fixed;z-index:30;top:50%;left:50%;transform:translate(-50%,-50%);width:calc(100vw - var(--space-6));max-width:22rem;margin:0;text-align:center;pointer-events:none;box-shadow:0 4px 24px rgba(0,0,0,.45)}
.flash--notice{animation:flash-away 4s forwards}
}
@keyframes flash-away{0%,72%{opacity:1}100%{opacity:0;visibility:hidden}}
.errors{background:var(--alert-ground);border:1px solid var(--alert-bad);border-radius:var(--radius-sm);padding:var(--space-3) var(--space-4);margin-bottom:var(--space-4);color:var(--alert-bad);font-size:var(--text-sm)}
.errors ul{margin:0;padding-left:var(--space-4)}
/* Avatars: square, metal-set */
.avatar{border-radius:var(--radius-md);object-fit:cover;border:1px solid var(--gold);background:var(--surface-inset)}
.avatar--card{width:36px;height:36px}
.avatar--profile{width:160px;height:160px;border-radius:var(--radius-lg)}
.avatar--monogram{display:inline-flex;align-items:center;justify-content:center;color:var(--gold-bright);font-weight:600}
.avatar--monogram.avatar--card{font-size:.9rem}
.avatar--monogram.avatar--profile{font-size:3rem}
/* Member page */
.profile{margin-top:var(--space-6)}
/* Rank */
.rank{font-family:var(--font-mono);font-weight:600;text-transform:lowercase;color:var(--gold-bright)}
.card__rank,.profile__rank{color:var(--text-muted);font-size:var(--text-xs)}
.card__rank .rank,.profile__rank .rank{font-size:1.1em}
/* The team line: mono small caps in the seal's green, with the seal's glow,
   flanked by diamonds so it reads as an insignia rather than another link. */
.profile__teams{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-3);margin:calc(-1 * var(--space-2)) 0 var(--space-4);font-family:var(--font-mono);font-size:var(--text-sm);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ruby-bright);text-shadow:0 0 10px var(--glow-ruby)}
.profile__team::before{content:"\25C6\00A0";color:var(--ruby)}
.profile__team:last-child::after{content:"\00A0\25C6";color:var(--ruby)}
/* Directory filter bar */
.filter-bar{display:flex;gap:var(--space-2);align-items:center;margin:var(--space-5) 0 var(--space-4);flex-wrap:wrap}
.filter-bar input[type="search"]{max-width:16rem}
.filter-bar select{width:auto;font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-body);background:var(--parchment);border:1px solid var(--line);padding:var(--space-2) var(--space-3)}
/* Directory: one line per member */
.directory{display:flex;gap:var(--space-6);align-items:flex-start}
.directory__main{flex:1}
.directory__list{display:block}
@media (max-width:640px){.directory{flex-direction:column}
.site-header{flex-wrap:wrap;row-gap:var(--space-1)}
.hero{padding:var(--space-5) var(--space-3)}}
/* Same rhythm as .leaderboard li, so the roster and the board read as one system. */
.card{display:flex;align-items:center;gap:var(--space-3);width:100%;background:transparent;border:0;border-bottom:1px solid var(--line);border-radius:var(--radius-sm);padding:var(--space-2);text-align:left}
.card:hover{background:var(--surface-inset)}
.card>a{display:flex;align-items:center;gap:var(--space-3);flex:none;text-decoration:none;color:inherit}
.card h3{margin:0;font-size:1rem}
.card>a:hover h3{color:var(--gold-bright)}
.card p{margin:0;font-size:var(--text-sm);color:var(--text-muted)}
.card__rank{margin-left:auto;text-align:right;white-space:nowrap}
/* Small screens: list rows keep one primary line each; anything marked
   wide-only (meta lines, score bars) waits for the page behind the row.
   Placed after the component rules because the media query alone does not
   beat them. */
@media (max-width:640px){
.wide-only{display:none}
}
/* Claims list on settings */
/* The badge embed on a member's own page. Folded by default: it is a tool,
   not part of the page's reading. */
.badge-embed{margin:var(--space-5) 0;max-width:var(--measure)}
.badge-embed summary{cursor:pointer;color:var(--gold-bright)}
.badge-embed__preview{display:block;margin:var(--space-3) 0}
.badge-embed__label{font-size:var(--text-xs);text-transform:lowercase;letter-spacing:.04em;color:var(--text-faint);margin:var(--space-3) 0 var(--space-1)}
/* Snippet and its copy button on one row. The code scrolls rather than wraps,
   so a long URL cannot push the button off the end. */
.badge-embed__line{display:flex;align-items:stretch;gap:var(--space-2);margin:0}
.badge-embed__line code{flex:1;display:flex;align-items:center;padding:var(--space-2) var(--space-3);overflow-x:auto;white-space:nowrap;font-size:var(--text-sm)}
.badge-embed__line button{flex:none;font-size:var(--text-xs);padding:var(--space-2) var(--space-3)}
.claims{list-style:none;padding:0;max-width:var(--measure)}
.claims li{padding:var(--space-2) 0;border-bottom:1px solid var(--line)}
.claims form{display:inline;margin-left:var(--space-2)}
.claim__status{font-size:var(--text-xs);letter-spacing:.04em;text-transform:lowercase;border:1px solid;border-radius:var(--radius-sm);padding:.1em .5em;white-space:nowrap;margin-left:var(--space-2)}
.claim__status--verified{color:var(--ruby-bright);border-color:var(--ruby)}
.claim__status--pending{color:var(--text-muted);border-color:var(--line);border-style:dashed}
.claim__status--rejected{color:var(--wax-bright);border-color:var(--wax)}
/* Alert recipients list on the admin panel */
.recipients{list-style:none;padding:0;max-width:var(--measure)}
.recipients li{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-2) 0;border-bottom:1px solid var(--line)}
.recipients form{display:inline-flex;align-items:center;gap:var(--space-3);margin:0}
.recipient__toggles{margin-left:auto}
.recipients label{white-space:nowrap}
/* Users list on the admin panel */
/* Admin chrome. One sub-nav on every page, one list shape for every list, so
   the panel reads as one tool rather than six pages that grew separately. */
.admin-nav{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-3);align-items:baseline;margin:0 0 var(--space-5);padding-bottom:var(--space-2);border-bottom:1px solid var(--line);font-family:var(--font-mono);font-size:var(--text-sm)}
.admin-nav a{color:var(--text-muted);text-decoration:none}
.admin-nav a:hover{color:var(--gold-bright)}
.admin-nav__current{color:var(--gold-bright);font-weight:600;border-bottom:1px solid var(--gold)}
/* A section with something waiting on the admin: lit, and carrying the count. */
.admin-nav a.admin-nav__waiting{color:var(--moss)}
.admin-nav__count{display:inline-block;min-width:1.6em;padding:0 .4em;border-radius:1em;background:var(--moss);color:var(--text-on-wax);font-size:var(--text-xs);font-weight:600;line-height:1.5;text-align:center}
/* Rows: a primary line, a faint meta line under it, actions pushed right. */
.admin-list{list-style:none;padding:0;margin:var(--space-4) 0}
.admin-list li{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) 0;border-bottom:1px solid var(--line)}
.admin-list form{margin:0}
.admin-list__actions{margin-left:auto;display:flex;gap:var(--space-2);align-items:center}
.admin-list__meta{flex-basis:100%;margin:0;font-size:var(--text-xs);color:var(--text-faint)}
/* A reply, quoted: sent, on the admin's pages, or received, on a member's
   settings page. The rule is in the verification green so on the admin side
   it cannot be mistaken for a draft. Same partial everywhere: shared/reply. */
.reply{flex-basis:100%;margin:var(--space-2) 0;padding:var(--space-2) var(--space-3);border-left:3px solid var(--ruby);background:var(--surface-inset);font-size:var(--text-sm);color:var(--text-body)}
.reply__label{display:block;font-size:var(--text-xs);color:var(--ruby-bright);text-transform:lowercase;letter-spacing:.04em;margin-bottom:var(--space-1)}
.admin-list__meta code{font-size:.95em}
/* Kept: the users list styling other pages already share. */
.admin-users{list-style:none;padding:0}
.admin-users li{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-2) 0;border-bottom:1px solid var(--line)}
.admin-users form{margin:0;margin-left:auto}
/* Filter chips above a list. */
.admin-filters{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-3);align-items:baseline;margin:var(--space-3) 0;font-size:var(--text-sm)}
.admin-filters a{color:var(--text-muted);text-decoration:none}
.admin-filters a:hover{color:var(--gold-bright)}
.admin-filters__current{color:var(--gold-bright);font-weight:600}
/* Dashboard stats as a readout, not cards: one line each, dot leaders doing
   the aligning, which is both far more compact than eight boxes and the way
   this site already talks. Hairlines are the structure everywhere else too. */
.admin-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:0 var(--space-6);margin:var(--space-3) 0 var(--space-5);padding:0;list-style:none;font-family:var(--font-mono);font-size:var(--text-sm)}
.admin-stats li{display:flex;align-items:baseline;gap:var(--space-2);padding:.2rem 0;border:0}
.admin-stats__label{flex:none;color:var(--text-muted)}
.admin-stats__lead{flex:1;min-width:var(--space-4);border-bottom:1px dotted var(--line);transform:translateY(-.3em)}
.admin-stats__value{flex:none;font-weight:600;color:var(--gold-bright);font-variant-numeric:tabular-nums}
.admin-stats__note{flex:none;font-size:var(--text-xs);color:var(--text-faint)}
details{max-width:var(--measure);margin-bottom:var(--space-3)}
details summary{cursor:pointer;color:var(--gold-bright);font-size:var(--text-sm)}
/* The way in, at the foot of the board: a dashed frame, because it is an empty
   line waiting for a name rather than a row that exists. Reuses .tui-cursor
   and its keyframes, so there is still one blink in the system. */
.recruit{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:var(--space-2) var(--space-4);
max-width:var(--measure);margin:var(--space-5) 0;padding:var(--space-3);
border:1px dashed var(--wax-bright);background:var(--surface-inset)}
.recruit__prompt{margin:0;display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);color:var(--wax-bright)}
.recruit__mark{font-weight:600}
.recruit__placeholder{color:var(--text-faint)}
.recruit__prompt .tui-cursor{background:var(--wax-bright)}
.recruit__form{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)}
.recruit__form input[type="email"]{width:14rem}
.recruit__copy{margin:0;font-size:var(--text-xs);color:var(--text-muted)}
/* Board tabs. The same quiet voice as the header nav, so a slice reads as a
   place you are standing rather than a filter widget you have set. */
.boards{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-3);align-items:baseline;margin:var(--space-4) 0 0;font-family:var(--font-mono);font-size:var(--text-sm)}
.boards a{color:var(--text-muted);text-decoration:none}
.boards a:hover{color:var(--gold-bright)}
.boards__current{color:var(--gold-bright);font-weight:600;border-bottom:1px solid var(--gold)}
/* The cuts read one step down from the board they hang off: smaller, quieter,
   and behind a label that says what they are. */
.boards__by{color:var(--text-faint);font-size:var(--text-xs)}
.boards__by::before{content:"·";margin-right:var(--space-3);color:var(--text-faint)}
.boards__kinds{display:inline-flex;flex-wrap:wrap;gap:var(--space-2);align-items:baseline;font-size:var(--text-xs)}
.boards__dot{color:var(--text-faint)}
.boards__caption{margin:var(--space-2) 0 0;font-size:var(--text-xs);color:var(--text-faint)}
/* Changelog: a dated list of dated lists. The date is the heading, so it takes
   the h2 rule's // marker and needs nothing else. */
.changelog{list-style:none;padding:0;margin:var(--space-5) 0;max-width:var(--measure)}
/* Admin changelog editor: one form per day, framed by a hairline so the days
   read as separate entries. A draft carries the accent rule down its side. */
.changelog-editor{max-width:var(--measure);margin:var(--space-4) 0 var(--space-5);padding:var(--space-3) var(--space-4);border:1px solid var(--line)}
.changelog-editor--draft{border-left:3px solid var(--wax)}
.changelog-editor h3{margin-bottom:var(--space-2)}
.changelog-editor textarea{max-width:none}
.changelog-editor__actions{display:flex;align-items:center;gap:var(--space-4);flex-wrap:wrap;margin:0}
.changelog-editor__actions label{text-transform:none;letter-spacing:normal;font-size:var(--text-sm)}
.changelog-pull{display:inline;margin-left:var(--space-2)}
.changelog-share{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;margin:var(--space-4) 0 var(--space-5)}
.changelog-share select{width:auto}
.changelog-editor__source{margin:0 0 var(--space-3);font-size:var(--text-xs)}
.changelog-editor__source summary{cursor:pointer;color:var(--text-faint)}
.changelog-editor__source summary:hover{color:var(--gold-bright)}
.changelog-editor__source pre{margin:var(--space-2) 0 0;padding:var(--space-2) var(--space-3);background:var(--surface-inset);border:var(--rule-hairline);color:var(--text-muted);white-space:pre-wrap;overflow-wrap:anywhere}
.changelog-editor__delete{margin:calc(-1 * var(--space-4)) 0 var(--space-5);font-size:var(--text-xs)}
.changelog > li{padding-bottom:var(--space-5);border-bottom:1px solid var(--line);margin-bottom:var(--space-5)}
.changelog > li:last-child{border-bottom:0;margin-bottom:0}
.changelog h2{font-size:var(--text-sm)}
.changelog ul{margin:0;padding-left:var(--space-5)}
.changelog ul li{margin-bottom:var(--space-2)}
/* Leaderboard: the one numeric page */
.leaderboard{margin:var(--space-5) 0;padding-left:0;list-style:none;max-width:var(--measure)}
.leaderboard li{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) 0;border-bottom:1px solid var(--line)}
/* Fixed width so the column stays true from 1 into three digits. */
.leaderboard__place{flex:none;min-width:1.9em;text-align:right;font-size:var(--text-sm);font-weight:600;color:var(--gold)}
.leaderboard a{display:flex;align-items:center;gap:var(--space-3);text-decoration:none;color:inherit;flex:1}
.leaderboard a:hover .leaderboard__name{color:var(--gold-bright)}
.leaderboard__score{margin-left:auto;font-weight:600;color:var(--gold-bright)}
/* Movement since the last board shuffle: a small triangle and places moved.
   Up is green (earned, like verification); down stays faint rather than loud,
   losing a place to someone else's work is not an alarm. */
.movement{flex:none;min-width:2.2em;display:inline-flex;align-items:center;gap:.3em;font-size:var(--text-xs);font-weight:600;font-family:var(--font-mono)}
.movement::before{content:"";border-left:.34em solid transparent;border-right:.34em solid transparent}
.movement--up{color:var(--moss)}
.movement--up::before{border-bottom:.5em solid currentColor}
.movement--down{color:var(--text-faint)}
.movement--down::before{border-top:.5em solid currentColor}
/* A debut is a green dot, not a triangle: nobody was passed, someone arrived. */
.movement--new{color:var(--moss)}
.movement--new::before{border:0;width:.45em;height:.45em;border-radius:var(--radius-full);background:currentColor}
/* Contributions list */
.contributions li{margin-bottom:var(--space-2)}
.contributions small{margin-left:var(--space-2)}
/* Setups wall: screenshots are the hero */
.wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));gap:var(--space-4);margin:var(--space-5) 0}
.wall__card a{display:block;text-decoration:none;color:inherit}
.wall__card img{width:100%;aspect-ratio:16/10;object-fit:cover;border:1px solid var(--line);display:block}
.wall__card a:hover img{border-color:var(--gold)}
.wall__card h3{margin:var(--space-2) 0 0;font-size:var(--text-md)}
.wall__card a:hover h3{color:var(--gold-bright)}
.wall__card p{margin:0;font-size:var(--text-xs);color:var(--text-muted)}
.home-door{margin-top:var(--space-7)}
/* Setup page */
.setup{margin-top:var(--space-6)}
.setup__shots{display:grid;grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));gap:var(--space-3);margin:var(--space-4) 0}
.setup__shots button{padding:0;border:1px solid var(--line);background:none}
.setup__shots button:hover{border-color:var(--gold);background:none}
.setup__shots img{width:100%;display:block}
.setup__specs{display:grid;grid-template-columns:auto 1fr;gap:var(--space-1) var(--space-4);max-width:var(--measure);margin:var(--space-4) 0}
.setup__specs dt{font-size:var(--text-xs);color:var(--text-muted);text-transform:lowercase}
.setup__specs dd{margin:0;font-size:var(--text-sm)}
.setup__notes{max-width:var(--measure)}
/* Setups with no picture, under the wall of the ones that have. A list of
   names reads better than a grid of empty frames, and it stays out of the
   grid's way rather than leaving holes in it. */
.wall__plain{list-style:none;padding:0;margin:var(--space-4) 0 0;border-top:1px solid var(--line)}
.wall__plain li{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2);padding:var(--space-3) 0;border-bottom:1px solid var(--line)}
.wall__plain span{color:var(--text-muted);font-size:var(--text-sm)}
.lightbox{border:1px solid var(--gold);background:var(--parchment);padding:var(--space-2);max-width:92vw;max-height:92vh}
.lightbox::backdrop{background:rgba(0,0,0,.8)}
.lightbox img{max-width:100%;max-height:86vh;display:block}
/* Badges: gems cut to the shield. Glow sits on the seal span, the clip on its
   ::before, because a drop-shadow on the clipped element itself would be cut off. */
.badges{display:grid;grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));gap:var(--space-3) var(--space-5);margin:0 0 var(--space-4);padding:0}
.badge{display:flex;align-items:center;gap:var(--space-3);list-style:none;margin:0;font-size:var(--text-sm)}
.badge__seal{flex:none;filter:var(--seal-glow)}
.badge__seal::before{content:attr(data-seal);display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:44px;height:51px;padding-bottom:6px;font-family:var(--font-mono);font-weight:600;font-size:1rem;color:rgba(255,255,255,.9);text-shadow:0 0 8px rgba(255,255,255,.4);background:linear-gradient(135deg,var(--wax-bright) 0%,var(--wax) 45%,var(--wax-dark) 100%);clip-path:var(--seal-shape)}
/* Globe: a canvas beside the same roster in words. The list is the fallback,
   so it stays readable on its own and the canvas only ever selects into it. */
.globe{display:flex;gap:var(--space-6);align-items:flex-start;margin:var(--space-5) 0}
.globe__canvas{flex:none;width:min(30rem,52vw);aspect-ratio:1;touch-action:none;cursor:grab}
.globe__canvas:active{cursor:grabbing}
.globe__canvas:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.globe__roster{flex:1;min-width:0;list-style:none;margin:0;padding:0;max-height:min(30rem,52vw);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--line) transparent}
.globe__entry{padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--line);font-size:var(--text-sm);cursor:default}
.globe__entry:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
.globe__entry.is-selected{background:var(--surface-inset);border-left:2px solid var(--globe-dot)}
.globe__handle,.globe__place{color:var(--text-muted);font-size:var(--text-xs);margin-left:var(--space-2)}
.globe__anonymous{color:var(--text-faint);font-style:italic}
.globe__entry--empty{color:var(--text-faint)}
.globe__cta{font-size:var(--text-sm)}
/* The home page globe: the same canvas, turning, with none of the handles.
   It is a door, so it takes the pointer of one. */
/* The globe and the contributors share a row, and the globe leads once they
   stack: it is the thing that says "people, everywhere" at a glance. */
/* justify-content earns its keep only when the globe is alone: with the
   contributors beside it they take the slack, so there is none to hand out.
   A guild with nobody scored yet gets a centred globe, not one shoved left. */
.home-split{display:flex;gap:var(--space-6);align-items:center;justify-content:center;margin-top:var(--space-7)}
/* Sized to its content, not grown to fill: the list inside is max-content, so
   a growing aside just parks dead space on its right and the pair reads as
   shoved left. Shrunk to fit, justify-content has something to centre. */
.home-split__aside{flex:0 1 auto;min-width:0}
.home-split__aside h2{margin-top:0}
/* The card's divider is full width by default, which here runs about twice as
   long as the name it sits under, now that the handle and setup are gone from
   this column. Shrinking the list rather than each card keeps the rules a
   matched length, set by the longest name, instead of leaving a ragged edge. */
.home-split__aside .directory__list{width:max-content;max-width:100%}
.home-globe__door{display:block;flex:none;text-decoration:none;width:max-content}
.globe__canvas--still{width:min(22rem,64vw);cursor:pointer}
.home-globe__door:focus-visible{outline:2px solid var(--focus-ring);outline-offset:4px}
@media (max-width:52rem){
.home-split{flex-direction:column;align-items:stretch;gap:var(--space-5)}
.home-globe__door{align-self:center}
}
.hero__globe{font-size:var(--text-sm)}
/* The picker on the member form: the same globe, one dot, and fields that can
   do the job on their own for anyone not using a pointer. */
.globe-picker{border:1px solid var(--line);border-radius:var(--radius-sm);padding:var(--space-4);margin:var(--space-5) 0;max-width:var(--measure)}
.globe-picker legend{font-size:var(--text-sm);color:var(--gold-bright);padding:0 var(--space-2)}
.globe-picker p{font-size:var(--text-sm)}
.globe-picker .globe__canvas{width:min(22rem,70vw);margin:var(--space-3) 0}
.globe-picker__readout{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}
.globe-picker__readout span{font-family:var(--font-mono);color:var(--gold-bright)}
/* Empty until the browser has something to say, so it takes no room when silent. */
.globe-picker__status:empty{display:none}
.globe-picker__status{font-size:var(--text-xs);margin:0 0 var(--space-3)}
.globe-picker__fields{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}
.globe-picker__fields input{width:7rem}
@media (max-width:720px){.globe{flex-direction:column}
.globe__canvas{width:min(30rem,86vw)}
.globe__roster{max-height:none;width:100%}}

/* ===== ascii wordmark ===== */
/* The banner replaces the hero's frame on the homepage, so that variant drops
   the border. Two stacked words, 61 columns at the widest (OMARCHY);
   font-size:min(21px,2.2vw) fills the hero and shrinks on narrow viewports. */
.hero--banner{border:0;margin-top:var(--space-3);padding-top:var(--space-4)}
.ascii-banner{font-family:var(--font-mono);font-size:min(21px,2.2vw);line-height:1.15;margin:0 auto;display:inline-block;text-align:left;background:linear-gradient(180deg,var(--wax-bright),var(--wax) 55%,var(--wax-dark));-webkit-background-clip:text;background-clip:text;color:transparent;user-select:none;white-space:pre}
/* ASCII sparkles on the wordmark: few, small, come and go. The wrap holds only
   the <pre>, so a sparkle never lands on the tagline. */
.ascii-banner-wrap{position:relative;display:inline-block}
.ascii-sparkle{position:absolute;font-size:1.15rem;color:var(--wax-bright);pointer-events:none;user-select:none;opacity:0;text-shadow:0 0 6px var(--glow)}
@keyframes guild-sparkle{0%,100%{opacity:0;transform:scale(.5)}45%,60%{opacity:.9;transform:scale(1)}}
/* Hotkeys: space is always reserved (visibility, not display) so revealing
   them never shifts layout. */
.hotkey{visibility:hidden;display:inline-block;font-family:var(--font-mono);font-size:.65rem;font-weight:600;color:var(--wax);border:1px solid var(--wax-dark);background:var(--surface-inset);padding:0 .45em;margin-left:.5em;vertical-align:1px}
.show-hotkeys .hotkey{visibility:visible}
/* The line under the header holds one of two things, so it never changes
   height: the invitation, or the keys themselves once Shift is down. */
.hotkey-hint{font-size:var(--text-xs);color:var(--text-faint)}
.hotkey-hint__keys{display:none}
.show-hotkeys .hotkey-hint__idle{display:none}
.show-hotkeys .hotkey-hint__keys{display:inline}
/* A phone has no Shift to press, so it is told nothing about one. The keys
   still resolve if a keyboard is attached; only the invitation goes. */
@media (max-width:640px){
.hotkey-hint__idle{display:none}
}
/* TUI: a block cursor after the hero tagline */
.tui-cursor{display:inline-block;width:.55em;height:1.05em;background:var(--wax);vertical-align:text-bottom;margin-left:.15em}
@keyframes guild-blink{50%{opacity:0}}
/* Row selection marker: reserved gutter, filled on hover like a TUI list */
.card>a h3::before{content:"  ";white-space:pre;color:var(--wax)}
.card:hover>a h3::before{content:"> "}
/* Contribution lists read as a tree */
.contributions{list-style:none;padding-left:var(--space-2)}
.contributions li::before{content:"├──  ";color:var(--gold)}
.contributions li:last-child::before{content:"└──  "}
/* Empty states speak shell */
.prompt-empty::before{content:"$ ";color:var(--ruby)}
/* Leaderboard score bars */
.scorebar{font-size:.75rem;color:var(--wax);letter-spacing:-.05em}
.scorebar .off{color:var(--wax-dark);opacity:.45}
/* The sparkles, the cursor and the coffee steam are the only animations in
   the system, and all stand down for anyone who asks for less motion: the
   sparkle spans still spawn and expire, they just never fade in, the cursor
   holds steady, and the steam sits in its first frame. */
@media (prefers-reduced-motion:no-preference){
input[type="checkbox"]::before,input[type="radio"]::before{transition:transform .08s ease-out}
.ascii-sparkle{animation:guild-sparkle 2.4s ease-in-out both}
.tui-cursor{animation:guild-blink 1.1s steps(1) infinite}
.coffee__steam{animation:coffee-steam 1.6s steps(1,end) infinite}}

/* Theme picker on settings. Swatch dot hexes are hardcoded on purpose: each
   dot advertises its target theme, whatever theme is active. Classes, not
   inline styles, so the CSP stays quiet. */
.theme-picker{display:flex;flex-wrap:wrap;gap:var(--space-3);margin:var(--space-3) 0}
.theme-picker form{display:contents}
.theme-swatch{display:inline-flex;align-items:center;gap:var(--space-2)}
.theme-swatch--current{border-color:var(--wax);color:var(--wax-bright);box-shadow:0 0 6px var(--glow-soft)}
.theme-swatch__dots{display:inline-flex;gap:3px}
.theme-swatch__dots span{width:10px;height:10px;border-radius:var(--radius-full);border:1px solid rgba(0,0,0,.4)}
/* page / primary / button-accent per theme */
.theme-swatch--tokyo_night .theme-swatch__dots span:nth-child(1){background:#1a1b26}
.theme-swatch--tokyo_night .theme-swatch__dots span:nth-child(2){background:#7aa2f7}
.theme-swatch--tokyo_night .theme-swatch__dots span:nth-child(3){background:#9bb2ef}
.theme-swatch--catppuccin .theme-swatch__dots span:nth-child(1){background:#1e1e2e}
.theme-swatch--catppuccin .theme-swatch__dots span:nth-child(2){background:#cba6f7}
.theme-swatch--catppuccin .theme-swatch__dots span:nth-child(3){background:#fab387}
.theme-swatch--gruvbox .theme-swatch__dots span:nth-child(1){background:#282828}
.theme-swatch--gruvbox .theme-swatch__dots span:nth-child(2){background:#fabd2f}
.theme-swatch--gruvbox .theme-swatch__dots span:nth-child(3){background:#fe8019}
.theme-swatch--rose_pine .theme-swatch__dots span:nth-child(1){background:#191724}
.theme-swatch--rose_pine .theme-swatch__dots span:nth-child(2){background:#ebbcba}
.theme-swatch--rose_pine .theme-swatch__dots span:nth-child(3){background:#f6c177}
.theme-swatch--everforest .theme-swatch__dots span:nth-child(1){background:#2d353b}
.theme-swatch--everforest .theme-swatch__dots span:nth-child(2){background:#7fbbb3}
.theme-swatch--everforest .theme-swatch__dots span:nth-child(3){background:#dbbc7f}
.theme-swatch--kanagawa .theme-swatch__dots span:nth-child(1){background:#1f1f28}
.theme-swatch--kanagawa .theme-swatch__dots span:nth-child(2){background:#7e9cd8}
.theme-swatch--kanagawa .theme-swatch__dots span:nth-child(3){background:#e6c384}
.theme-swatch--nord .theme-swatch__dots span:nth-child(1){background:#2e3440}
.theme-swatch--nord .theme-swatch__dots span:nth-child(2){background:#88c0d0}
.theme-swatch--nord .theme-swatch__dots span:nth-child(3){background:#ebcb8b}
.theme-swatch--osaka_jade .theme-swatch__dots span:nth-child(1){background:#111c18}
.theme-swatch--osaka_jade .theme-swatch__dots span:nth-child(2){background:#2dd5b7}
.theme-swatch--osaka_jade .theme-swatch__dots span:nth-child(3){background:#e5c736}
.theme-swatch--ristretto .theme-swatch__dots span:nth-child(1){background:#2c2525}
.theme-swatch--ristretto .theme-swatch__dots span:nth-child(2){background:#a8a9eb}
.theme-swatch--ristretto .theme-swatch__dots span:nth-child(3){background:#fcd675}
.theme-swatch--catppuccin_latte .theme-swatch__dots span:nth-child(1){background:#eff1f5}
.theme-swatch--catppuccin_latte .theme-swatch__dots span:nth-child(2){background:#1e66f5}
.theme-swatch--catppuccin_latte .theme-swatch__dots span:nth-child(3){background:#8839ef}
.theme-swatch--matte_black .theme-swatch__dots span:nth-child(1){background:#121212}
.theme-swatch--matte_black .theme-swatch__dots span:nth-child(2){background:#ffc107}
.theme-swatch--matte_black .theme-swatch__dots span:nth-child(3){background:#bebebe}
/* Shift+T. Same furniture as the lightbox; the picker turns into a column so
   each swatch is a full-width row you can tab down. */
/* An explicit width on purpose: a dialog sizes to its content, and auto-fit
   columns cannot resolve against an intrinsic width, so the grid below would
   collapse to a single column and push Close off the bottom. */
/* The suggestion box. The launcher pins to the bottom corner on every page,
   clear of the footer links on a phone and of the scrollbar on a desktop. It
   sits above the page but below a dialog's backdrop, so opening one covers it.
   env(safe-area-inset-*) keeps it off the home indicator on an iPhone. */
.suggest-launcher{position:fixed;z-index:20;right:calc(var(--space-4) + env(safe-area-inset-right));bottom:calc(var(--space-4) + env(safe-area-inset-bottom));display:flex;align-items:center;gap:var(--space-1);font-family:var(--font-mono);font-size:var(--text-sm);color:var(--gold-bright);background:var(--parchment-deep);border:1px solid var(--gold);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,.45)}
.suggest-launcher:hover{background:var(--surface-inset);border-color:var(--gold-bright)}
.suggest-kind{display:flex;flex-wrap:wrap;gap:var(--space-4);border:0;padding:0;margin:0 0 var(--space-4)}
.suggest-kind label{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--text-body);cursor:pointer}
.suggest-kind label:hover{color:var(--gold-bright)}
.suggest-form label{display:block;margin-bottom:var(--space-1);font-size:var(--text-sm);color:var(--text-muted)}
.suggest-form input[type=text],.suggest-form textarea{width:100%}
.suggest-actions{display:flex;align-items:center;gap:var(--space-3)}
/* Present for screen readers, absent from the page. */
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* A member's own asks, on their own page. Same status chips as the claims list
   so a pending thing reads the same wherever it appears. */
.asks{list-style:none;padding:0;margin:0 0 var(--space-4)}
.asks li{padding:var(--space-3) 0;border-bottom:1px solid var(--line)}
.asks li:last-child{border-bottom:0}
.theme-dialog{border:1px solid var(--gold);background:var(--parchment);color:var(--text-body);padding:var(--space-4);width:min(30rem,92vw);max-height:85vh;overflow-y:auto;border-radius:var(--radius-sm)}
.theme-dialog::backdrop{background:rgba(0,0,0,.8)}
.theme-dialog h2{margin-top:0}
/* Two columns once there is room, so nine themes do not push Close off the
   bottom; one column on a narrow phone. The forms are display:contents, so
   the swatches themselves are the grid items. */
.theme-dialog .theme-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(10.5rem,1fr));align-items:stretch}

/* ===== theme character ===== */
/* The little more-than-color layer. These override component rules above, so
   they must stay at the end of the file. */
/* Gruvbox: chunky borders on buttons, padding compensated so sizes do not shift. */
[data-theme="gruvbox"] .button,[data-theme="gruvbox"] button,[data-theme="gruvbox"] input[type="submit"]{border-width:2px;padding:calc(var(--space-2) - 1px) calc(var(--space-4) - 1px)}
/* White seal text on bright yellow or amber fails contrast; ink the numeral. */
[data-theme="gruvbox"] .badge__seal::before,
[data-theme="matte_black"] .badge__seal::before{color:rgba(18,18,18,.92);text-shadow:none}
[data-theme="rose_pine"] a{text-underline-offset:3px}
