/* ============================================================
   MWT Studios — shared stylesheet
   Identity: void violet base, orchid-violet primary, lava accent,
   azure reserved for NexusCore surfaces.
   ============================================================ */

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

:root{
  /* ══ SURFACES ═══════════════════════════════════════════════
     Near-black with a violet cast, the way the emblem's field reads — not
     neutral grey, and not the flat purple a "dark theme" usually means. Each
     step is a real elevation, so a panel on a panel is still legible without
     a border doing all the work. */
  --bg-primary:#05040A;          /* the page itself */
  --bg-secondary:#0B0812;        /* banded sections */
  --surface-obsidian:#0D0B12;    /* recessed: code, wells, inputs */
  --surface-primary:#12101A;     /* cards and panels */
  --surface-elevated:#17131F;    /* dialogs, dropdowns, hovered cards */
  --surface-deepslate:#1B1726;   /* the highest tier, used sparingly */
  --surface-nav:rgba(9,7,15,.88);/* translucent header over content */

  /* ══ FRAMING ════════════════════════════════════════════════
     Brushed dark metal, from the emblem's plating. Violet-cast greys rather
     than saturated purple, so borders frame content instead of competing
     with it. */
  --edge-strong:#3A3450;
  --edge-base:#2A2637;
  --edge-soft-2:#1E1B29;
  --divider:#191622;

  /* ══ BRAND ══════════════════════════════════════════════════ */
  --violet-primary:#8B5CFF;
  --violet-orchid:#A56DFF;
  --violet-deep:#5A2DBA;
  --violet-crystal:#D6C2FF;      /* the lit core of the emblem's cube */

  /* Technical accents. Cyan is for METADATA — versions, hashes, loaders —
     never for primary actions, or it stops meaning "technical". */
  --technical-cyan:#42C8FF;
  --technical-blue:#637CFF;

  /* Molten orange is a WARNING COLOUR AND NOTHING ELSE. It is the one hue on
     this site that must never be decorative: the moment it appears next to
     something that is merely emphatic, it stops being able to say "careful". */
  --molten-orange:#FF6A2A;
  --molten-soft:#FFA070;

  /* ══ TEXT ═══════════════════════════════════════════════════
     Four tiers, all measured against --surface-primary. --text-muted is the
     floor at 4.6:1; nothing below it is permitted for prose.

     THERE IS A CEILING AS WELL AS A FLOOR, which is the less obvious half.
     --text-primary was #F4F1FA, which is 18.3:1 against the page — very nearly
     the most contrast this palette can produce. Maximum contrast is not
     maximum legibility on a dark background: light text on near-black blooms
     at the edges, and the effect gets worse the heavier and more letterspaced
     the type is, which is exactly what this site does with its uppercase
     labels and headings. It was reported as text being "so bright it is hard
     to read", and it was.

     #DED8EA is 14.7:1 — still far above AAA at 7:1, still crisp for a heading,
     and no longer glaring. The three tiers below it are unchanged, so the
     steps between them stay clearly apart. */
  --text-primary:#DED8EA;
  --text-secondary:#B8B0C7;
  --text-muted:#8B8399;
  /* THE FAINTEST TEXT COLOUR, and it is text — the old comment said
     "decorative only — never body copy", which was not what it was being used
     for: keyboard-shortcut hints, palette kind labels, chat role labels, the
     text of a deleted message, and FORM PLACEHOLDERS. All of those are read.
     At #6E6780 it measured 3.65:1 on --surface-obsidian and 3.51:1 on
     --surface-primary, and every rule using it sets a font-size under 14px —
     so the large-text exemption never applied and all ten uses failed WCAG AA.
     Raised to the nearest value clearing 4.5:1 on both surfaces (4.82 / 4.65)
     while staying visibly the faintest of the four text tokens.
     tools/test-contrast.mjs holds the line. */
  --text-faint:#827A97;

  /* ══ STATUS ═════════════════════════════════════════════════ */
  --status-success:#54E08A;
  --status-warning:#FFB648;
  --status-error:#FF6B6B;
  --status-info:#42C8FF;

  /* Release and support state. Distinct from defect severity on purpose:
     "unsupported" is a fact about a version, "critical" is a judgement about
     a bug, and colouring them the same makes an old release look dangerous. */
  --state-released:#54E08A;
  --state-stable:#54E08A;
  --state-development:#FFB648;
  --state-planned:#8B8399;
  --state-unsupported:#FF6B6B;
  --state-unverified:#FFA070;

  /* Defect severity. Four steps, and only the top two are red-adjacent, so a
     low-severity row does not read as an emergency. */
  --defect-critical:#FF5A5A;
  --defect-high:#FF6A2A;
  --defect-medium:#FFB648;
  --defect-low:#8B8399;

  /* ══ DEPTH ══════════════════════════════════════════════════
     Shadows carry the elevation; glow is a separate, optional layer. THE SITE
     MUST STILL READ CORRECTLY WITH EVERY GLOW SET TO none — that is the test
     for whether the hierarchy is real or painted on. */
  --shadow-sm:0 1px 2px rgba(0,0,0,.5);
  --shadow-md:0 6px 18px rgba(0,0,0,.45);
  --shadow-lg:0 18px 50px rgba(0,0,0,.55);
  --shadow-xl:0 28px 80px rgba(0,0,0,.6);
  --glow-subtle:0 0 0 1px rgba(139,92,255,.16);
  --glow-medium:0 0 22px rgba(139,92,255,.18);
  --glow-strong:0 0 44px rgba(139,92,255,.26);
  --focus-ring:0 0 0 2px var(--bg-primary),0 0 0 4px var(--violet-orchid);

  /* THE RING FOR A CHAMFERED ELEMENT, and the reason there are two.
     ------------------------------------------------------------
     --focus-ring above draws OUTSIDE the element's box. Every button on this
     site carries a clip-path polygon for its cut corners, and clip-path clips
     the whole rendering of the element — outline and box-shadow included. So
     every .btn, every .dl-btn and every one of the admin panel's controls
     computed a focus ring and then painted none of it: the hero calls to
     action, the jar downloads and the entire panel had NO visible keyboard
     focus at all. The chamfer that gives the site its character was quietly
     eating its own accessibility affordance.

     Switching to `outline` does not fix it — that was measured, not assumed,
     in a browser: outline is clipped exactly the same way. An INSET shadow is
     drawn within the box, so the clip keeps it. Light band first, then a dark
     one just inside it, so the ring reads against both a filled violet button
     and a dark panel. */
  --focus-ring-inset:inset 0 0 0 2px var(--violet-crystal),inset 0 0 0 4px var(--bg-primary);
  --focus-ring-inset-cyan:inset 0 0 0 2px var(--technical-cyan),inset 0 0 0 4px var(--bg-primary);

  /* ══ SPACING ════════════════════════════════════════════════
     A 4px-based scale. Anything not on it is a decision that needs a reason. */
  --space-1:4px;   --space-2:8px;   --space-3:12px;  --space-4:16px;
  --space-5:20px;  --space-6:24px;  --space-8:32px;  --space-10:40px;
  --space-12:48px; --space-16:64px; --space-20:80px; --space-24:96px;

  /* ══ GEOMETRY ═══════════════════════════════════════════════
     Near-square, from the emblem's voxel construction. Not the 12px pill of
     every SaaS template — the corners are what stop this looking generic. */
  --radius-none:0;
  --radius-sm:2px;
  --radius-md:3px;
  --radius-lg:4px;
  --radius-pill:999px;           /* chips and counters only */

  /* ══ WIDTHS ═════════════════════════════════════════════════ */
  --width-prose:68ch;            /* long-form: docs, posts, legal */
  --width-content:1160px;
  --width-wide:1320px;

  /* ══ TYPE ═══════════════════════════════════════════════════
     Three roles. Display for headings, sans for prose, mono for anything a
     machine produced — commands, hashes, versions, loaders, file names.
     System stacks: a webfont here would cost real load time and the brief
     asks for performance to improve, not regress. */
  --font-display:'Segoe UI Variable Display','Segoe UI',system-ui,-apple-system,'Inter',Roboto,Helvetica,Arial,sans-serif;
  --font-body:'Segoe UI',system-ui,-apple-system,'Inter',Roboto,Helvetica,Arial,sans-serif;
  --font-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;

  --text-xs:11.5px; --text-sm:13px;  --text-base:15.5px; --text-md:17px;
  --text-lg:clamp(1.05rem,1.6vw,1.2rem);
  --text-xl:clamp(1.25rem,2.2vw,1.6rem);
  --text-2xl:clamp(1.5rem,3.2vw,2.1rem);
  --text-3xl:clamp(1.9rem,4.4vw,2.9rem);
  --text-4xl:clamp(2.3rem,6vw,4rem);
  --leading-tight:1.18; --leading-snug:1.4; --leading-normal:1.68; --leading-loose:1.8;
  --tracking-label:.18em;        /* uppercase micro-labels */

  /* ══ MOTION ═════════════════════════════════════════════════ */
  --duration-instant:90ms; --duration-fast:150ms;
  --duration-base:220ms;   --duration-slow:340ms;
  --ease-out:cubic-bezier(.22,.61,.36,1);
  --ease-in-out:cubic-bezier(.65,.05,.36,1);

  /* ══ COMPATIBILITY ══════════════════════════════════════════
     THE ORIGINAL NAMES, MAPPED ONTO THE NEW SYSTEM. 761 lines of this
     stylesheet and eleven scripts already use them. Renaming would turn a
     visual overhaul into a rewrite of every rule that references a colour —
     exactly what the brief says not to do. New work should use the names
     above; these keep the existing rules correct while it happens. */
  --void:var(--bg-primary);
  --deep:var(--bg-secondary);
  --panel:var(--surface-primary);
  --panel-hi:var(--surface-elevated);
  --edge:var(--edge-strong);
  --edge-soft:var(--edge-base);
  --vio:var(--violet-orchid);
  --vio-hi:var(--violet-crystal);
  --vio-deep:var(--violet-deep);
  /* Kept as an alias so any older rule still resolves, but the UI accents that
     used it (nav current-page, unread badge and rule, announcement pulse) are
     violet now. #FF6A2A was simultaneously "high severity defect" and "you are
     here" / "unread" — and both appear on /nexuscore/ at once, which is exactly
     the dilution the guidelines describe. Orange now means severity, nothing
     else. */
  --lava:var(--molten-orange);
  --lava-hi:var(--molten-soft);
  --azu:var(--technical-cyan);
  --azu-hi:#8CEAFF;
  --azu-line:#1D3C56;
  --azu-dim:#6F97B0;
  --ok:var(--status-success);
  --warn:var(--status-warning);
  --bad:var(--status-error);
  --ink:var(--text-primary);
  --mut:var(--text-secondary);
  --dim:var(--text-muted);
  --faint:var(--text-faint);
  --max:var(--width-content);
  --step:clamp(56px,7vw,92px);
  --mono:var(--font-mono);
}

/* The site is dark by design, but form controls the BROWSER paints — a
   <select>'s open popup list, autofill, scrollbars — follow the declared
   color-scheme, not the stylesheet. Without this line the OS renders those
   popups white-on-light inside an otherwise dark page. `option` is styled
   explicitly as well because Chromium on Windows honors it and the popup
   would otherwise ignore the page palette entirely. */
:root{color-scheme:dark}
select option{background:var(--panel);color:var(--ink)}

html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{
  margin:0;background:var(--void);color:var(--ink);
  font:16px/1.68 'Segoe UI',system-ui,-apple-system,Inter,Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
a{color:inherit}
img{max-width:100%;display:block}
.wrap{max-width:var(--max);margin:0 auto;padding:0 22px}
.center{text-align:center}
:focus-visible{outline:2px solid var(--vio-hi);outline-offset:3px;border-radius:2px}

/* skip link — real keyboard accessibility, not decoration */
.skip{
  position:absolute;left:-9999px;top:0;z-index:200;padding:12px 18px;
  background:var(--vio);color:#fff;font-weight:800;text-decoration:none;
}
.skip:focus{left:12px;top:12px}

/* ─── backdrop ─── */
.bg{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.bg::before{
  content:"";position:absolute;inset:0;opacity:.5;
  background:
    linear-gradient(rgba(58,42,99,.30) 1px,transparent 1px) 0 0/32px 32px,
    linear-gradient(90deg,rgba(58,42,99,.30) 1px,transparent 1px) 0 0/32px 32px,
    linear-gradient(rgba(58,42,99,.13) 1px,transparent 1px) 0 0/8px 8px,
    linear-gradient(90deg,rgba(58,42,99,.13) 1px,transparent 1px) 0 0/8px 8px;
  -webkit-mask-image:radial-gradient(ellipse 120% 68% at 50% 0%,#000 22%,transparent 74%);
          mask-image:radial-gradient(ellipse 120% 68% at 50% 0%,#000 22%,transparent 74%);
}
.bg::after{
  content:"";position:absolute;left:50%;top:-340px;width:1150px;height:720px;
  transform:translateX(-50%);
  background:radial-gradient(ellipse at center,rgba(106,63,212,.42),transparent 62%);
  filter:blur(62px);
}
.hero-art{
  position:absolute;left:50%;top:-40px;transform:translateX(-50%);
  width:min(1700px,124vw);aspect-ratio:16/9;pointer-events:none;
  background:var(--hero-img,url('../assets/hero-bg.webp')) center top/cover no-repeat;
  opacity:.46;
  -webkit-mask-image:radial-gradient(ellipse 80% 64% at 50% 40%,transparent 24%,#000 56%,transparent 86%);
          mask-image:radial-gradient(ellipse 80% 64% at 50% 40%,transparent 24%,#000 56%,transparent 86%);
}
.hero-art::after{
  content:"";position:absolute;inset:0;
  background:
    radial-gradient(ellipse 52% 46% at 50% 44%,rgba(11,7,20,.92),transparent 70%),
    linear-gradient(180deg,transparent 55%,var(--void) 96%);
}
.lava{position:fixed;top:0;bottom:0;width:190px;z-index:0;pointer-events:none;opacity:.85}
.lava.l{left:0}.lava.r{right:0;transform:scaleX(-1)}
.lava i{
  position:absolute;inset:0;
  background:
    linear-gradient(100deg,transparent 0 28%,rgba(255,122,61,.30) 33%,transparent 38%),
    linear-gradient(78deg,transparent 0 44%,rgba(255,176,106,.26) 49%,transparent 54%),
    linear-gradient(92deg,transparent 0 12%,rgba(255,122,61,.20) 17%,transparent 22%);
  filter:blur(14px);animation:emb 7s ease-in-out infinite alternate;
}
.lava b{
  position:absolute;left:-60px;top:8%;width:170px;height:84%;
  background:radial-gradient(ellipse at left center,rgba(255,122,61,.34),transparent 68%);
  filter:blur(46px);
}
@keyframes emb{from{opacity:.6;transform:translateY(-8px)}to{opacity:1;transform:translateY(8px)}}

header,main,footer{position:relative;z-index:2}

/* ─── header / nav ─── */
header{
  position:sticky;top:0;z-index:50;padding:var(--space-3) 0;
  backdrop-filter:blur(16px) saturate(130%);-webkit-backdrop-filter:blur(16px) saturate(130%);
  background:var(--surface-nav);border-bottom:1px solid transparent;
  transition:border-color var(--duration-base) var(--ease-out),
             background var(--duration-base) var(--ease-out),
             box-shadow var(--duration-base) var(--ease-out);
}
/* Once it detaches from the top it earns a hairline of violet edge light —
   the separation the brief asks for, done with one border and one low shadow
   rather than a glow that would compete with the content underneath it. */
header.stuck{
  border-bottom-color:var(--edge-base);
  background:rgba(5,4,10,.94);
  box-shadow:0 1px 0 rgba(139,92,255,.14),var(--shadow-md);
}
.nav{display:flex;align-items:center;justify-content:space-between;gap:16px}
.brand{display:flex;align-items:center;gap:11px;text-decoration:none;flex:none}
/* The emblem now carries its own frame, plating and glow. Wrapping it in a
   rounded violet chip fought all three and rounded off a mark whose whole
   character is voxel geometry — so the chrome comes off and the mark is left
   to be itself. */
.brand img{
  width:42px;height:42px;border-radius:var(--radius-sm);
  filter:drop-shadow(0 0 10px rgba(139,92,255,.35));
  transition:filter var(--duration-base) var(--ease-out);
}
.brand:hover img{filter:drop-shadow(0 0 15px rgba(165,109,255,.55))}
.brand:focus-visible{outline:none}
.brand:focus-visible img{box-shadow:var(--focus-ring)}
.brand .wm b{
  display:block;font-family:var(--font-display);font-size:17px;font-weight:900;
  letter-spacing:.11em;line-height:1;
  background:linear-gradient(180deg,var(--text-primary) 26%,var(--violet-orchid) 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.brand .wm i{display:block;font-style:normal;font-size:9px;letter-spacing:.4em;color:var(--dim);margin-top:4px}

/* ─── primary navigation ───────────────────────────────────────────────
   Eight destinations is a lot for one row, so the bar is grouped rather than
   evenly spaced: content links sit together on the left of the group, and the
   two ACTIONS — Account and GitHub — are separated by a hairline and styled as
   what they are. Grouping is what stops it reading as an undifferentiated list;
   it needs no extra markup beyond a class on those two.

   `gap` shrinks with the viewport via clamp() instead of a breakpoint, so the
   row degrades smoothly to the burger instead of crowding right up to it.

   ACCESSIBILITY IS NOT TRADED FOR ANY OF THIS:
     * the underline is decorative — the CURRENT page is also marked with
       aria-current and a colour change, so it does not rely on a 1px line;
     * :focus-visible gets a real ring, distinct from hover, because a keyboard
       user needs to see where they are and hover styling alone never shows it;
     * hit areas keep vertical padding, so a link is a comfortable target rather
       than a text-height sliver;
     * the transition is dropped under prefers-reduced-motion.                */
.nav nav{
  display:flex;align-items:center;
  gap:clamp(13px,1.7vw,24px);
  font-size:12.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut);
}
.nav nav a{
  text-decoration:none;position:relative;padding:7px 0;
  transition:color .18s;white-space:nowrap;border-radius:2px;
}
.nav nav a::after{
  content:"";position:absolute;left:0;right:100%;bottom:2px;height:1px;
  background:var(--vio);transition:right .25s;
}
.nav nav a:hover{color:var(--vio-hi)}
.nav nav a:hover::after{right:0}
.nav nav a:focus-visible{
  outline:2px solid var(--vio-hi);outline-offset:4px;color:var(--ink);
}
.nav nav a[aria-current="page"]{color:var(--ink)}
/* A dropdown whose contents include the current page. `data-current` rather
   than aria-current: the button does not navigate anywhere, so claiming it is
   the current PAGE would be a lie to a screen reader. The child link inside
   the menu carries the real aria-current. */
.navdropbtn::after{
  content:"";position:absolute;left:0;right:100%;bottom:2px;height:1px;
  background:var(--vio);transition:right .25s;
}
.navdropbtn:hover::after{right:0}
.navdropbtn[data-current="page"]{color:var(--ink)}
.navdropbtn[data-current="page"]::after{right:0;background:var(--violet-primary)}
.nav nav .navmenu a[aria-current="page"]{color:var(--violet-crystal);background:rgba(139,92,255,.12)}
.nav nav a[aria-current="page"]::after{right:0;background:var(--violet-primary)}

/* The action group: separated from the content links, and Account reads as a
   control rather than another destination. */
.nav nav .navsep{
  width:1px;height:18px;background:var(--edge);flex:none;margin:0 2px;
}
.nav nav a.navcta{
  padding:7px 14px;border:1px solid var(--edge);color:var(--ink);
  background:rgba(165,109,255,.09);
}
.nav nav a.navcta:hover{border-color:var(--vio);background:rgba(165,109,255,.18)}
.nav nav a.navcta::after{display:none}
.nav nav a.navcta[aria-current="page"]{border-color:var(--vio);background:rgba(165,109,255,.2)}

@media(prefers-reduced-motion:reduce){
  .nav nav a,.nav nav a::after{transition:none}
}

/* The notification bell and its dropdown. Injected by site.js for a signed-in
   member only — see there for why it is a <button> rather than a link.

   The panel is absolutely positioned inside a relative wrapper so it hangs
   below the bell without disturbing the nav row's layout. On mobile the nav is
   already a stacked sheet, so the panel goes static and simply takes its place
   in the list instead of floating over it. */
.nbwrap{position:relative;display:flex;align-items:center}
.nav nav button.navbell{
  position:relative;padding:7px 10px;background:none;border:0;cursor:pointer;
  color:var(--mut);font:inherit;font-size:12.5px;letter-spacing:.1em;
  border-radius:2px;
}
.nav nav button.navbell:hover{color:var(--vio-hi)}
.nav nav button.navbell:focus-visible{outline:2px solid var(--vio-hi);outline-offset:4px;color:var(--ink)}
.nav nav button.navbell[aria-expanded="true"]{color:var(--ink)}
/* Was the literal character "◉" set from site.js — a fisheye, not a bell,
   rendered in whatever system font the browser picked. It read as a stray
   dark circle in the nav. Now the same masked icon set as everything else. */
.navbell .ico{
  display:block;width:17px;height:17px;background:currentColor;
  -webkit-mask:var(--ic-bell) center/contain no-repeat;
  mask:var(--ic-bell) center/contain no-repeat;
}
.navbell .dot{
  position:absolute;top:-1px;right:0;min-width:16px;height:16px;padding:0 4px;
  border-radius:9px;background:var(--violet-primary);color:var(--bg-secondary);
  font-size:10px;font-weight:900;line-height:16px;text-align:center;
  letter-spacing:0;box-shadow:0 0 0 2px rgba(11,7,20,.9);
}
.navbell.quiet .dot{display:none}

.nbpanel{
  position:absolute;top:calc(100% + 12px);right:0;width:min(330px,calc(100vw - 32px));
  z-index:60;text-transform:none;letter-spacing:0;
  border:1px solid var(--edge);background:linear-gradient(165deg,var(--panel-hi),var(--deep));
  box-shadow:0 18px 50px rgba(0,0,0,.55);
}
.nbpanel.hidden{display:none}
.nbhead{display:flex;align-items:center;gap:10px;padding:12px 14px;
        border-bottom:1px solid var(--edge-soft);font-size:13px;color:var(--ink)}
.nbmute{margin-left:auto;background:none;border:1px solid var(--edge);color:var(--mut);
        font:inherit;font-size:11px;padding:4px 9px;cursor:pointer;letter-spacing:0}
.nbmute:hover:not(:disabled){border-color:var(--vio);color:var(--ink)}
.nbmute:disabled{opacity:.45;cursor:default}
.nblist{max-height:min(58vh,340px);overflow-y:auto}
.nbitem{display:flex;gap:10px;align-items:baseline;padding:10px 14px;font-size:12.8px;
        color:var(--mut);text-decoration:none;border-bottom:1px solid var(--edge-soft);
        line-height:1.5}
.nbitem:last-child{border-bottom:none}
a.nbitem:hover{background:rgba(165,109,255,.09);color:var(--ink)}
.nbitem.unread{color:var(--ink);box-shadow:inset 3px 0 0 var(--violet-primary)}
.nbitem .x{flex:1;word-break:break-word}
.nbitem .w{font-size:10.5px;color:var(--faint);white-space:nowrap}
.nbempty{padding:16px 14px;margin:0;font-size:12.5px;color:var(--faint)}
.nbfoot{display:flex;align-items:center;gap:10px;padding:11px 14px;
        border-top:1px solid var(--edge-soft)}
.nbfoot .nbmute{margin-left:0}
.nball{margin-left:auto;font-size:12px;color:var(--vio-hi);text-decoration:none}
.nball:hover{text-decoration:underline}

@media(max-width:1100px){
  .nbwrap{width:100%;display:block}
  .nav nav button.navbell{padding:13px 0;width:100%;text-align:left;
    border-bottom:1px solid rgba(58,42,99,.35)}
  .navbell .ico{display:inline-block}
  .navbell .dot{position:static;display:inline-block;margin-left:8px}
  .nbpanel{position:static;width:100%;margin:0 0 10px;box-shadow:none}
}

/* mobile menu */
.burger{
  display:none;flex:none;width:44px;height:44px;padding:0;cursor:pointer;
  background:rgba(255,255,255,.045);border:1px solid var(--edge);color:var(--ink);
  align-items:center;justify-content:center;
}
.burger span{display:block;width:19px;height:2px;background:currentColor;position:relative}
.burger span::before,.burger span::after{content:"";position:absolute;left:0;width:19px;height:2px;background:currentColor;transition:transform .22s,top .22s}
.burger span::before{top:-6px}.burger span::after{top:6px}
.burger[aria-expanded="true"] span{background:transparent}
.burger[aria-expanded="true"] span::before{top:0;transform:rotate(45deg)}
.burger[aria-expanded="true"] span::after{top:0;transform:rotate(-45deg)}


/* ─── nav dropdown ───
   A <button> rather than a hover-only panel: hover menus are unreachable by
   keyboard and unusable on touch, and this one has to work in a nav that
   collapses to a stacked column at 900px. Desktop floats it; mobile expands it
   inline, because a floating panel inside an already-absolute stacked menu has
   nowhere sensible to go. */
.navdrop{position:relative;display:flex;align-items:center}
.navdropbtn{
  display:inline-flex;align-items:center;gap:6px;background:none;border:0;padding:0;
  font:inherit;font-size:12.5px;letter-spacing:.14em;text-transform:uppercase;
  font-weight:700;color:var(--mut);cursor:pointer;position:relative;
  /* A LABEL IN A BAR THAT CANNOT WRAP MUST NOT WRAP. There was one menu when
     this was written and it was one word, so nothing showed. "Docs & help"
     and "Work with us" wrapped to two lines inside a 30px row — measured at
     56px and 60px wide, which is narrower than the words, so the second line
     was simply cut off. It read as a truncated label rather than a broken
     one, which is why it needed measuring to see. */
  white-space:nowrap;
}
.navdropbtn svg{width:9px;height:6px;transition:transform .18s ease}
.navdropbtn:hover{color:var(--vio-hi)}
.navdropbtn[aria-expanded="true"]{color:var(--ink)}
.navdropbtn[aria-expanded="true"] svg{transform:rotate(180deg)}
.navdropbtn:focus-visible{outline:2px solid var(--vio);outline-offset:4px}
.navmenu{
  position:absolute;top:calc(100% + 14px);left:50%;transform:translateX(-50%) translateY(-6px);
  min-width:190px;display:flex;flex-direction:column;gap:0;padding:8px;
  background:linear-gradient(165deg,var(--panel-hi),var(--deep));
  border:1px solid var(--edge);box-shadow:0 18px 50px rgba(0,0,0,.5);
  opacity:0;visibility:hidden;transition:opacity .16s ease,transform .16s ease;z-index:60;
}
.navdrop.open .navmenu{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0)}
.nav nav .navmenu a{
  padding:9px 12px;font-size:11.5px;letter-spacing:.12em;color:var(--mut);white-space:nowrap;
}
.nav nav .navmenu a::after{display:none}
.nav nav .navmenu a:hover{color:var(--ink);background:rgba(165,109,255,.14)}
.nav nav .navmenu a:focus-visible{outline:2px solid var(--vio);outline-offset:-2px}
@media(prefers-reduced-motion:reduce){
  .navmenu,.navdropbtn svg{transition:none}
}

@media(max-width:1100px){
  /* Stacked, <nav> leaves the flow, so the sound toggle and the burger are the
     only two controls left. The burger is conventionally the rightmost thing
     in a header, so it is ordered past the toggle. */
  .burger{display:flex;order:2}
  .nav nav{
    position:absolute;left:0;right:0;top:100%;flex-direction:column;gap:0;
    background:rgba(11,7,20,.98);border-top:1px solid var(--edge-soft);
    border-bottom:1px solid var(--edge-soft);
    padding:6px 22px 14px;display:none;
    /* THE SHEET MUST SCROLL, OR ITS BOTTOM IS UNREACHABLE.
       It is absolutely positioned under a STICKY header and the page does not
       scroll while it is open, so anything past the fold can never be brought
       into view — not by scrolling the page, not by scrolling the sheet.
       Measured for a signed-in owner at 375x812: the sheet was 1828px tall
       and "Open the panel" sat at y=1727, permanently untappable. It went
       unnoticed because the case that fits is the common one — a visitor has
       seven rows, and a trainee's launcher has six sections and no handbook
       banner. The owner's has twelve, a deadline strip, a chat card and an
       unread count.
       dvh rather than vh: on mobile browsers vh is the tallest viewport, the
       one with the address bar hidden, so a vh-sized sheet is still cut off
       until you scroll the chrome away. */
    max-height:calc(100vh - 69px);
    max-height:calc(100dvh - 69px);
    overflow-y:auto;
    overscroll-behavior:contain;
  }
  .nav nav.open{display:flex}
  .nav nav a{padding:13px 0;width:100%;border-bottom:1px solid rgba(58,42,99,.35);font-size:13px}
  .nav nav a::after{display:none}
  /* Stacked, the pill and the hairline are noise — the list is already clear. */
  .nav nav .navsep{display:none}
  /* Stacked: the panel expands in the flow instead of floating over it. */
  .navdrop{display:block;width:100%}
  .navdropbtn{width:100%;justify-content:space-between;padding:13px 0;
              border-bottom:1px solid rgba(58,42,99,.35);font-size:13px}
  .navmenu{
    position:static;transform:none;opacity:1;visibility:visible;
    display:none;min-width:0;border:0;background:none;box-shadow:none;
    padding:0 0 0 14px;
  }
  .navdrop.open .navmenu{display:flex;transform:none}
  .nav nav .navmenu a{padding:11px 0;border-bottom:1px solid rgba(58,42,99,.22)}
  .nav nav a.navcta{padding:13px 0;border:0;background:none;color:var(--vio-hi)}
  .nav nav a.navcta[aria-current="page"]{background:none}
}

/* ─── buttons ─── */
.btn{
  display:inline-flex;align-items:center;gap:9px;text-decoration:none;cursor:pointer;
  font-weight:800;font-size:12.5px;letter-spacing:.14em;text-transform:uppercase;
  padding:15px 27px;border:1px solid transparent;position:relative;overflow:hidden;
  font-family:inherit;
  clip-path:polygon(11px 0,100% 0,calc(100% - 11px) 100%,0 100%);
  transition:transform .16s,box-shadow .16s,filter .16s,border-color .16s,background .16s;
}
.btn:hover{transform:translateY(-2px)}
.btn:disabled{opacity:.55;cursor:not-allowed;transform:none}
.btn-v:hover{filter:brightness(1.13)}
.btn-v::before{
  content:"";position:absolute;inset:0;transform:translateX(-120%);
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.34),transparent);
}
.btn-v:hover::before{animation:sweep .75s ease}
@keyframes sweep{to{transform:translateX(120%)}}
.btn-o{border-color:var(--edge);}
.btn-sm{padding:11px 18px;font-size:11px}

/* ─── page head ─── */
.phead{padding:clamp(44px,6vw,72px) 0 8px;text-align:center}
.crumb{font-size:10.5px;letter-spacing:.32em;text-transform:uppercase;color:var(--lava-hi);margin:0 0 14px}
h1{margin:0;font-weight:900;text-transform:uppercase;line-height:.98;
   font-size:clamp(2.1rem,5.4vw,3.6rem);
   /* Starts at the text token, not at #fff. These gradients are the biggest,
      heaviest type on the site, so a pure-white top stop is where the glare
      was worst — and hardcoding it meant the ceiling in the token block could
      not reach the very text that needed it most. */
   background:linear-gradient(180deg,var(--text-primary) 14%,#d7c6ff 56%,#a98ce8 100%);
   -webkit-background-clip:text;background-clip:text;color:transparent;
   text-shadow:0 4px 0 rgba(42,30,74,.35);
}
.phead p{color:var(--mut);max-width:64ch;margin:18px auto 0;font-size:clamp(1rem,1.7vw,1.08rem)}

/* ─── hero (home) ─── */
.hero{padding:58px 0 40px;text-align:center}
.crest{
  width:158px;height:158px;margin:0 auto 26px;border-radius:26px;
  border:1px solid rgba(165,109,255,.45);
  box-shadow:0 0 0 6px rgba(165,109,255,.07),0 0 60px rgba(165,109,255,.5),inset 0 0 0 1px rgba(0,0,0,.6);
  animation:bob 6s ease-in-out infinite;
}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-11px)}}
.tag{
  display:inline-flex;align-items:center;gap:9px;margin-bottom:22px;text-decoration:none;
  font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--lava-hi);
  padding:8px 16px;border:1px solid rgba(255,122,61,.4);background:rgba(255,122,61,.08);
  clip-path:polygon(8px 0,100% 0,calc(100% - 8px) 100%,0 100%);
  transition:background .18s,border-color .18s;
}
a.tag:hover{background:rgba(139,92,255,.16);border-color:var(--violet-primary)}
.tag .mk{width:6px;height:6px;background:var(--violet-orchid);box-shadow:0 0 10px var(--violet-orchid);animation:pulse 2.2s ease-in-out infinite;flex:none}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}

.hero h1{font-size:clamp(2.4rem,7.6vw,5.1rem);line-height:.95;background:none;color:var(--ink);text-shadow:none}
.hero h1 span{display:block;text-shadow:0 5px 0 rgba(42,30,74,.55),0 10px 26px rgba(0,0,0,.7)}
.hero h1 .l1{
  background:linear-gradient(180deg,var(--text-primary) 8%,#e6dcff 42%,#b79aee 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 0 22px rgba(165,109,255,.32));
}
.hero h1 .l2{
  background:linear-gradient(180deg,#dcc7ff 4%,var(--vio) 52%,#7b48e0 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 0 26px rgba(165,109,255,.5));
}
.rule{
  max-width:620px;margin:24px auto 0;padding:9px 0;
  border-top:1px solid var(--edge);border-bottom:1px solid var(--edge);
  font-size:11px;letter-spacing:.34em;text-transform:uppercase;color:var(--mut);
}
.lede{color:var(--mut);font-size:clamp(1rem,1.9vw,1.14rem);max-width:58ch;margin:24px auto 32px}
.cta{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}

/* ─── stat strip ─── */
.stats{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1px;
  margin:56px 0 0;background:var(--edge-soft);border:1px solid var(--edge-soft);
}
.stat{background:linear-gradient(165deg,var(--panel),var(--deep));padding:22px 16px;text-align:center}
.stat b{
  display:block;font-size:1.55rem;font-weight:900;line-height:1;
  /* Three stops, not two. Over a glyph this short a straight run from the text
     token to violet spends most of its height in the violet, which left the
     figures looking washed out once the top stop came down off pure white.
     Holding the light tone through the upper two-thirds keeps them as readable
     as they were, without the peak that caused the glare. */
  background:linear-gradient(180deg,var(--text-primary) 18%,var(--violet-crystal) 66%,var(--vio) 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.stat span{display:block;margin-top:7px;font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim)}

/* ─── sections ─── */
section{padding:var(--step) 0}
.head{text-align:center;margin-bottom:38px}
.kick{font-size:10.5px;letter-spacing:.34em;text-transform:uppercase;color:var(--lava-hi);margin:0 0 12px}
h2{
  margin:0;font-weight:900;text-transform:uppercase;letter-spacing:.02em;
  font-size:clamp(1.6rem,4.4vw,2.6rem);line-height:1.06;
  background:linear-gradient(180deg,var(--text-primary) 20%,#bfa8f0 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  text-shadow:0 4px 0 rgba(42,30,74,.5);
}
h3{font-weight:800}
.head p{color:var(--mut);max-width:60ch;margin:15px auto 0}

.panel{
  position:relative;background:linear-gradient(165deg,var(--panel),var(--deep));
  border:1px solid var(--edge);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 22px 54px rgba(0,0,0,.5);
}
.panel>.gem{position:absolute;width:11px;height:11px;background:var(--vio);transform:rotate(45deg);box-shadow:0 0 14px rgba(165,109,255,.85);z-index:3}
.gem.tl{left:-6px;top:-6px}.gem.tr{right:-6px;top:-6px}
.gem.bl{left:-6px;bottom:-6px}.gem.br{right:-6px;bottom:-6px}

/* ─── NexusCore surfaces (azure) ─── */
.nc{background:linear-gradient(165deg,#0d1a2b,#0a1220);border-color:var(--azu-line);overflow:hidden}
.nc>.gem{background:var(--azu);box-shadow:0 0 14px rgba(58,212,240,.85)}
.nc-banner{
  position:relative;border-bottom:1px solid var(--azu-line);padding:30px 22px 22px;
  display:grid;place-items:center;
  background:
    radial-gradient(ellipse 62% 90% at 50% 42%,rgba(58,212,240,.16),transparent 70%),
    linear-gradient(180deg,#0a1524,#0a1220);
}
.nc-banner img{width:min(560px,100%);height:auto;filter:drop-shadow(0 12px 40px rgba(58,212,240,.28))}
.nc-banner::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:70px;pointer-events:none;
  background:linear-gradient(180deg,transparent,rgba(10,18,32,.9));
}
.nc-body{padding:32px}
.nc-top{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;margin-bottom:24px}
.nc-id{display:flex;align-items:center;gap:15px}
.nc-id img{
  width:60px;height:60px;border-radius:13px;
  border:1px solid rgba(58,212,240,.4);
  box-shadow:0 0 22px rgba(58,212,240,.35),inset 0 0 0 1px rgba(0,0,0,.5);
}
.nc-id h3{
  margin:0;font-size:1.65rem;font-weight:900;text-transform:uppercase;letter-spacing:.03em;line-height:1;
  background:linear-gradient(180deg,var(--text-primary) 25%,var(--azu) 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.nc-id p{margin:7px 0 0;font-size:11.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--azu-dim)}
.ver{
  font-size:11.5px;font-weight:800;letter-spacing:.12em;padding:9px 15px;white-space:nowrap;
  background:linear-gradient(160deg,var(--azu-hi),var(--azu));
  clip-path:polygon(8px 0,100% 0,calc(100% - 8px) 100%,0 100%);
}
.specs{display:grid;grid-template-columns:repeat(auto-fit,minmax(142px,1fr));gap:9px;margin-bottom:28px}
.spec{
  border:1px solid var(--azu-line);background:rgba(58,212,240,.05);padding:11px 13px;
  clip-path:polygon(7px 0,100% 0,100% calc(100% - 7px),calc(100% - 7px) 100%,0 100%,0 7px);
}
.spec b{display:block;font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:#5f88a3;margin-bottom:4px}
.spec span{font-size:13px;color:#d5eefb;font-weight:600}
.feats{display:grid;grid-template-columns:repeat(auto-fit,minmax(248px,1fr));gap:20px}
.feat{display:flex;gap:13px}
.feat .hex{
  width:40px;height:40px;flex:none;display:grid;place-items:center;font-size:17px;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(160deg,rgba(58,212,240,.24),rgba(29,132,168,.12));
}
.feat h4{margin:0 0 5px;font-size:13.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#e6f6ff}
.feat p{margin:0;font-size:13.2px;line-height:1.6;color:#8fb3c7}
.pills{display:flex;flex-wrap:wrap;gap:7px;margin-top:26px;padding-top:22px;border-top:1px solid var(--azu-line)}
.pill{font-family:var(--mono);font-size:11.5px;padding:7px 12px;color:#a9d6ea;border:1px solid var(--azu-line);background:rgba(58,212,240,.06)}
.pill.more{color:#5f88a3}

/* ─── downloads ─── */
.dl{
  margin-top:26px;padding:24px;border:1px solid rgba(58,212,240,.34);
  background:linear-gradient(165deg,rgba(58,212,240,.09),rgba(58,212,240,.03));
  clip-path:polygon(11px 0,100% 0,100% calc(100% - 11px),calc(100% - 11px) 100%,0 100%,0 11px);
}
.dl-head h4{
  margin:0 0 7px;font-size:1.05rem;font-weight:900;letter-spacing:.05em;text-transform:uppercase;color:#e6f6ff;
}
.dl-head p{margin:0 0 18px;font-size:13.3px;line-height:1.6;color:#8fb3c7}
.dl-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:11px}
.dl-btn{
  display:flex;flex-direction:column;gap:4px;text-decoration:none;padding:15px 17px;
  border:1px solid var(--azu-line);background:rgba(10,18,32,.66);
  transition:border-color .18s,background .18s,transform .18s;
  clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);
}
.dl-btn:hover{transform:translateY(-2px)}
.dl-btn b{font-size:14px;font-weight:900;letter-spacing:.06em;text-transform:uppercase;color:var(--azu-hi)}
.dl-btn span{font-size:11.5px;color:var(--azu-dim);font-family:var(--mono)}
.dl-foot{
  margin:18px 0 0;font-size:12.5px;color:var(--azu-dim);
  display:flex;gap:10px;flex-wrap:wrap;align-items:center;
}
/* Taller to TAP than they look. Three 12.5px links in a wrapping row measured
   21px tall at 375px — under the 24px floor for a target. The padding widens
   the hit area and the equal negative margin hands the space back, so the row
   lays out exactly as before. */
.dl-foot a{color:var(--azu-hi);text-decoration:none;padding:6px 0;margin:-6px 0}
.dl-foot a:hover{text-decoration:underline}

/* ─── release list ─── */
.rel{display:grid;gap:14px}
.relcard{
  position:relative;padding:24px;border:1px solid var(--edge);
  background:linear-gradient(165deg,var(--panel),var(--deep));
}
.relcard.old{opacity:.86;border-color:var(--edge-soft)}
.rel-top{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.rel-top h3{margin:0;font-size:1.3rem;font-weight:900;letter-spacing:.03em}
.rel-date{margin-left:auto;font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}
.relcard>p{margin:0 0 14px;color:var(--mut);font-size:13.6px;line-height:1.65}
.rel-links{display:flex;gap:10px;flex-wrap:wrap;font-size:12px}
.rel-links a{
  color:var(--vio-hi);text-decoration:none;padding:8px 13px;
  border:1px solid var(--edge-soft);background:rgba(165,109,255,.08);
}
.rel-links a:hover{border-color:var(--vio);background:rgba(165,109,255,.18)}

/* ─── notes / callouts ─── */
.note{
  display:flex;gap:13px;align-items:flex-start;margin-top:24px;padding:16px 18px;
  border:1px solid rgba(255,122,61,.34);background:rgba(255,122,61,.07);
  clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);
}
.note i{font-style:normal;font-size:17px;flex:none}
.note p{margin:0;font-size:13.4px;color:#f0dccb}
.note strong{color:var(--lava-hi)}
.note.v{border-color:rgba(165,109,255,.34);background:rgba(165,109,255,.08)}
.note.v p{color:#e4dbff}
.note.v strong{color:var(--vio-hi)}

/* ─── cards ─── */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(252px,1fr));gap:15px}
.card{
  /* Column flex so .go can be pushed to the bottom. As display:block the
     call-to-action floated up under a short paragraph, and four cards in a
     row showed four different CTA baselines. */
  display:flex;flex-direction:column;text-decoration:none;padding:24px;position:relative;overflow:hidden;
  border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));
  clip-path:polygon(13px 0,100% 0,100% calc(100% - 13px),calc(100% - 13px) 100%,0 100%,0 13px);
  transition:border-color .2s,transform .2s,box-shadow .2s;
}
.card::after{
  content:"";position:absolute;inset:0;opacity:0;transition:opacity .25s;pointer-events:none;
  background:radial-gradient(420px circle at var(--mx,50%) var(--my,0),rgba(165,109,255,.13),transparent 62%);
}
.card:hover{border-color:var(--vio);transform:translateY(-4px);box-shadow:0 16px 38px rgba(106,63,212,.3)}
.card:hover::after{opacity:1}
.card .hex{
  width:42px;height:42px;display:grid;place-items:center;font-size:18px;margin-bottom:14px;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(160deg,rgba(165,109,255,.26),rgba(106,63,212,.12));
}
.card h3{margin:0 0 6px;font-size:14px;font-weight:800;letter-spacing:.07em;text-transform:uppercase}
.card p{margin:0;font-size:13.2px;line-height:1.58;color:var(--mut)}
.card .go{margin-top:auto;padding-top:13px;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--vio);font-weight:800}

/* ─── project cards (home) ─── */
.projects{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:18px}
.proj{
  position:relative;display:flex;flex-direction:column;text-decoration:none;overflow:hidden;
  border:1px solid var(--edge);background:linear-gradient(165deg,var(--panel),var(--deep));
  transition:border-color .2s,transform .2s,box-shadow .2s;
}
.proj:hover{transform:translateY(-4px);box-shadow:0 18px 44px rgba(0,0,0,.5)}
.proj.is-nc:hover{border-color:var(--azu)}
.proj.is-soon:hover{border-color:var(--vio)}
.proj .shot{position:relative;aspect-ratio:16/7;overflow:hidden;background:#0a1220;display:grid;place-items:center;border-bottom:1px solid var(--edge-soft)}
.proj .shot img{width:100%;height:100%;object-fit:cover}
.proj .body{padding:22px;display:flex;flex-direction:column;gap:10px;flex:1}
.proj h3{margin:0;font-size:1.25rem;font-weight:900;text-transform:uppercase;letter-spacing:.03em}
.proj p{margin:0;color:var(--mut);font-size:13.4px;flex:1}
.proj .meta{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:4px}
.badge{
  font-size:10px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;
  padding:6px 11px;border:1px solid var(--edge);color:var(--mut);background:rgba(255,255,255,.04);
}
.badge.ok{border-color:rgba(84,224,138,.45);color:var(--ok);background:rgba(84,224,138,.09)}
.badge.soon{border-color:rgba(255,182,72,.45);color:var(--warn);background:rgba(255,182,72,.09)}
.badge.azu{border-color:rgba(58,212,240,.45);color:var(--azu);background:rgba(58,212,240,.09)}

/* ─── community split ─── */
.split{display:grid;grid-template-columns:1.05fr .95fr;gap:0;align-items:stretch}
@media(max-width:860px){.split{grid-template-columns:1fr}}
.split .art{position:relative;overflow:hidden;border-right:1px solid var(--edge)}
@media(max-width:860px){.split .art{border-right:0;border-bottom:1px solid var(--edge)}}
.split .art img{width:100%;height:100%;object-fit:cover;min-height:230px}
.split .art::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent 40%,rgba(18,12,34,.85) 100%)}
@media(max-width:860px){.split .art::after{background:linear-gradient(180deg,transparent 45%,rgba(18,12,34,.9) 100%)}}
.split .txt{padding:32px;display:flex;flex-direction:column;justify-content:center}
.split .txt h3{margin:0 0 10px;font-size:1.3rem;font-weight:900;text-transform:uppercase;letter-spacing:.04em}
.split .txt p{margin:0 0 20px;color:var(--mut);font-size:14px}
.split .txt ul{margin:0 0 22px;padding:0;list-style:none;display:grid;gap:8px}
.split .txt li{font-size:13.5px;color:var(--mut);display:flex;gap:9px;align-items:flex-start}
.split .txt li .mk{color:var(--vio);flex:none}

/* ─── tables ─── */
.tablewrap{overflow-x:auto;border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep))}
table{border-collapse:collapse;width:100%;min-width:520px;font-size:13.4px}
th,td{text-align:left;padding:13px 16px;border-bottom:1px solid var(--edge-soft);vertical-align:top}
th{background:rgba(0,0,0,.24)}
td{color:var(--mut)}
td code,p code,li code{font-family:var(--mono);font-size:12.4px;color:var(--vio-hi);background:rgba(165,109,255,.1);padding:2px 6px;border:1px solid var(--edge-soft)}
tr:last-child td{border-bottom:0}
.sev{font-size:10px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;padding:4px 9px;white-space:nowrap}
.sev.high{color:var(--bad);border:1px solid rgba(255,107,107,.4);background:rgba(255,107,107,.1)}
.sev.med{color:var(--warn);border:1px solid rgba(255,182,72,.4);background:rgba(255,182,72,.1)}
.sev.low{color:var(--dim);border:1px solid var(--edge-soft);background:rgba(255,255,255,.04)}

/* ─── command explorer ─── */
.filterbar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:18px}
.field{
  flex:1;min-width:220px;padding:13px 16px;font-family:inherit;font-size:14px;color:var(--ink);
  background:rgba(255,255,255,.045);border:1px solid var(--edge);
}
.field::placeholder{color:var(--faint)}
.field:focus{outline:none;border-color:var(--vio);background:rgba(165,109,255,.1)}
.chip{
  padding:10px 15px;font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--mut);background:rgba(255,255,255,.04);border:1px solid var(--edge-soft);cursor:pointer;
  font-family:inherit;transition:border-color .16s,color .16s,background .16s;
}
.chip:hover{border-color:var(--vio);color:var(--ink)}
/* --ink, not #fff. A selected chip sits on a translucent violet over the page,
   which composites to something dark — so pure white here is white-on-dark and
   subject to the same ceiling as everything else, not the safe white-on-solid-
   violet of a filled button. */
.chip[aria-pressed="true"]{background:rgba(165,109,255,.2);border-color:var(--vio);color:var(--ink)}
.count{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim)}
.cmdrow td:first-child{font-family:var(--mono);color:var(--azu-hi);white-space:nowrap}
.empty{padding:34px;text-align:center;color:var(--dim);font-size:13.5px}

/* ─── support ─── */
.sup{display:flex;gap:26px;align-items:center;justify-content:space-between;flex-wrap:wrap;padding:32px}
.sup h3{font-weight:800;letter-spacing:.05em;text-transform:uppercase}
.sup p{max-width:52ch}
.mail{
  /* inline-flex, not inline: vertical padding on an inline box does not grow
     the line box, so it overlaps whatever is above it. That is exactly what
     happened here once .sup stopped being a flex container. */
  display:inline-flex;align-items:center;
  font-family:var(--mono);font-size:14.5px;
  text-decoration:none;padding:15px 22px;white-space:nowrap;color:#eadfff;
  border:1px solid var(--edge);background:rgba(165,109,255,.12);
  clip-path:polygon(10px 0,100% 0,calc(100% - 10px) 100%,0 100%);
  transition:background .18s,border-color .18s;
}
.mail:hover{background:rgba(165,109,255,.26);border-color:var(--vio)}

/* ─── FAQ ─── */
.faq{display:grid;gap:10px}
details{border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep))}
details[open]{border-color:var(--edge)}
summary{
  padding:17px 20px;cursor:pointer;font-weight:800;font-size:14px;list-style:none;
  display:flex;justify-content:space-between;gap:14px;align-items:center;
}
summary::-webkit-details-marker{display:none}
summary::after{content:"+";color:var(--vio);font-size:19px;font-weight:900;flex:none}
details[open] summary::after{content:"–"}
summary:hover{color:var(--vio-hi)}
details .in{padding:0 20px 18px;color:var(--mut);font-size:13.6px}
details .in p{margin:0 0 10px}
details .in p:last-child{margin:0}


/* ─── application forms ─────────────────────────────────────────────
   /join/, /hire/, /apply-partner/ and /apply-media/ are one form with four
   sets of questions. Their styling was a byte-identical nineteen-line <style>
   block pasted into all four — and into /privacy/, which has no form at all,
   so every rule there including the honeypot was dead.

   The .fld rules in those copies also RE-DECLARED what site.css already sets a
   few hundred lines below, in slightly different values, so two visually
   different form fields shipped on one site depending on which page you were
   on. Only the genuinely form-specific parts are kept here; .fld itself is
   left to the one definition that owns it. */
.appform{max-width:720px;margin:0 auto}
.appform label{
  display:block;font-size:10.5px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--text-muted);font-weight:800;margin:0 0 7px;
}
.appform .fld{margin-bottom:18px}
.appform textarea.fld{min-height:140px;resize:vertical;line-height:1.6}
.appform select.fld{appearance:none;-webkit-appearance:none;cursor:pointer}
.appform .row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media(max-width:640px){.appform .row2{grid-template-columns:1fr}}
.appform .msg{
  padding:13px 16px;font-size:13.4px;margin-bottom:18px;
  border:1px solid var(--edge-soft-2);border-left:3px solid var(--edge-strong);
  border-radius:var(--radius-md);
}
.appform .msg.err{border-left-color:var(--status-error);background:rgba(255,107,107,.09);color:#FFD5D5}
.appform .msg.ok{border-left-color:var(--status-success);background:rgba(84,224,138,.09);color:#CFF7E0}
.appform .hint{color:var(--text-muted);font-size:12px;margin:-10px 0 18px}
/* The honeypot. Removed from layout AND from the accessibility tree — real
   people never see or tab to it; bots that fill every input fill it. */
.leave-blank{position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;overflow:hidden}

/* ─── search ────────────────────────────────────────────────────────
   One box that reaches every page, heading, command and release. The site had
   no search at all — two page-local filters and nothing that crossed a page.

   The trigger is deliberately visible and labelled rather than a bare icon
   with a shortcut: a keyboard shortcut nobody can see is a feature for the
   person who wrote it. */
/* THE KEYBOARD HINT IS THE FIRST THING TO GO, and it goes a hundred pixels
   before the bar does. Between 1061 and 1160 the bar is showing and a staff
   member's header also carries the notification bell and the studio launcher
   — two items injected by site.js that the generator cannot count. Measured,
   that header needs 1053px with the hint and 1008 without it, against a bar
   that starts appearing at 1061. Forty-five pixels of keyboard shortcut is
   what stands between "fits" and "clipped off the edge".

   THE LABEL STAYS. The note above this component says why: a shortcut nobody
   can see is a feature for the person who wrote it. That argument is about
   the word "Search", not about the Ctrl-K chip beside it — the chip is a
   reminder for people who already know, and it is the pixels nobody loses
   anything by dropping. */
@media(max-width:1160px){
  .navsearchkbd{display:none}
}
@media(max-width:1100px){
  .navsearch{width:100%;justify-content:flex-start;padding:12px 0;border:0;background:none;font-size:14px}
}

/* ─── the studio launcher ──────────────────────────────
   One icon in the header, for staff only, on every page.

   IT WAS TWO THINGS. A labelled pill in the header AND a full card on the home
   page — two affordances for one job, two places to maintain, and two copies
   of the same six links free to disagree. The card's content is now what the
   icon opens, so there is one list and it lives in one place.

   CYAN, NOT VIOLET, deliberately. Violet is the site talking to a visitor;
   this is not part of that argument and should not read as another promotion.
   Cyan is the palette's technical register, which is what a door to the
   operator's panel is. */
.navstudiowrap{position:relative;display:inline-flex}
.navstudio{
  display:inline-flex;align-items:center;justify-content:center;gap:0;
  width:32px;height:32px;padding:0;font:inherit;cursor:pointer;
  color:var(--technical-cyan);
  background:rgba(66,200,255,.07);
  border:1px solid rgba(66,200,255,.30);border-radius:var(--radius-sm);
  transition:border-color var(--duration-fast) var(--ease-out),
             background var(--duration-fast) var(--ease-out);
}
.navstudio:hover,.navstudio[aria-expanded="true"]{
  border-color:var(--technical-cyan);background:rgba(66,200,255,.14);
}
.navstudioic{font-size:15px;line-height:0}
/* The word is present for a screen reader at every width, and shown to
   everybody on narrow ones — the nav becomes a vertical sheet there, and a
   lone icon in a column of words reads as something that failed to load. */
.navstudiotext{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

.studiopop{
  position:absolute;top:calc(100% + 12px);right:0;z-index:60;
  min-width:250px;padding:12px;
  background:linear-gradient(165deg,var(--surface-elevated),var(--surface-obsidian));
  border:1px solid var(--edge-strong);border-radius:var(--radius-md);
  box-shadow:var(--shadow-lg);
}

/* ══ THE BOUNDARY, and it is load-bearing ═══════════════════════════
   This popup is APPENDED INSIDE `.nav nav`, so until this rule existed it
   inherited the header's link styling — rules written for six one-word nav
   items, applied to a panel of prose. Three separate defects, one cause, all
   three measured on the live site rather than reasoned about:

     1. `.nav nav{text-transform:uppercase;letter-spacing:1.25px}` shouted
        EVERYTHING. Not just the labels: the timestamps ("5 HOURS AGO"), the
        section blurbs ("WHO IS ON, AND WHEN."), the handbook copy, and — the
        one that decided this — the staff-room preview, which is a colleague's
        actual sentence rendered in capitals with 1.25px of tracking.

     2. `.nav nav a{padding:7px 0}` is (0,1,2); `.splink{padding:7px 9px}` is
        (0,1,0). The nav won. So the horizontal padding on .splink, .spchat
        and .spgo HAS NEVER RENDERED — every row's text sat flush against its
        own border, which is most of why the thing read as cramped.

     3. `.nav nav a::after` injected the header's VIOLET hover underline into
        every row of a panel that is deliberately cyan, because cyan is the
        technical register and this is a door to the operator's panel.

   Reset at the boundary rather than fought row by row: a rule that has to be
   re-beaten in every new child is a rule that loses to the next one added. */
/* SPECIFICITY MATTERS HERE AND IT CAUGHT ME ONCE. `.studiopop a` is (0,1,1)
   and `.nav nav a` is (0,1,2), so the naive reset LOSES to the very rule it is
   trying to undo — the staff-room message stayed on one clipped line because
   white-space:nowrap was still winning. Anything that undoes a `.nav nav a`
   declaration has to be written `.nav nav .studiopop a` (0,2,2) or it does
   nothing. text-transform and letter-spacing are different: `.nav nav` sets
   those on the NAV and they arrive by inheritance, so resetting them on the
   popup itself is enough. */
.studiopop{text-transform:none;letter-spacing:normal}
.nav nav .studiopop a{
  text-transform:none;letter-spacing:normal;white-space:normal;
  padding:0;border-radius:var(--radius-sm);
}
.nav nav .studiopop a::after{display:none}
.spwho{padding:0 4px 10px;margin-bottom:10px;border-bottom:1px solid var(--edge-soft-2)}
.spwho b{display:block;font-size:13.5px;color:var(--text-primary);line-height:1.3}
.spwho span{
  display:block;margin-top:3px;font-size:9.5px;font-weight:800;
  letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--technical-cyan);
}
.spgrid{display:grid;grid-template-columns:1fr;gap:3px;margin-bottom:10px}
.splink{
  display:block;padding:7px 9px;font-size:12px;text-decoration:none;
  color:var(--text-secondary);background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-sm);
}
.splink:hover,.splink:focus-visible{border-color:var(--technical-cyan);color:var(--technical-cyan)}
.spgo{
  display:block;text-align:center;padding:9px;text-decoration:none;
  font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  color:var(--bg-primary);background:var(--technical-cyan);border-radius:var(--radius-sm);
}
.spgo:hover,.spgo:focus-visible{filter:brightness(1.12)}

/* ─── the launcher as a lightweight panel ──────────────────────────
   It was six links. It is now what the panel would tell you if you opened it
   and looked at twelve screens: the handbook deadline that locks the account,
   notices addressed to you, the staff room's last line, and which sections
   have moved since you last had this open. All of it from one request made
   when you click and at no other time — see functions/api/studio.js for what
   that costs and for what is deliberately not in it.

   IT IS A MENU THAT GREW A BODY, not a new component. The width goes up
   because a two-column grid of labels-plus-timestamps at 250px is unreadable,
   and the body scrolls rather than growing past the viewport on a laptop. */
.studiopop{min-width:320px;max-width:min(360px,calc(100vw - 24px))}
.spbody{max-height:min(64vh,520px);overflow-y:auto;margin:0 -4px;padding:0 4px}
/* THERE IS MORE BELOW, said out loud. A hidden overlay scrollbar is not an
   affordance: 37% of this menu sat under an edge that looked like the end of
   it. `.more` is set by paint() only when the body actually overflows, so on
   the ordinary day the rows now fit there is nothing to see.

   NO `position` HERE, AND THERE WAS ONE. A `.studiopop{position:relative}`
   sat on this line to anchor a pseudo-element that was deleted before it
   shipped. Same specificity as the rule at the top of this block and LATER
   in the file, so it silently beat `position:absolute`: the popup dropped
   into normal flow, the header grew to its full height, the nav was pushed
   into the middle of the viewport and the Studio button floated above its
   own menu. Desktop only — the mobile sheet sets `position:static` on
   purpose. Reported from a screenshot 2026-09-11; missed in my own
   post-deploy check because the harness forced the popup static. The mask
   below needs no positioning at all. */
.studiopop.more .spbody{
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 26px),transparent);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 26px),transparent);
}


/* "3 sections have changed since you last looked." */
.spsum{margin:0 4px 10px;font-size:11.5px;line-height:1.5;color:var(--technical-cyan)}
.spnote{margin:0 4px 10px;font-size:11.5px;line-height:1.5;color:var(--text-faint)}

/* A group heading, matching the panel's own rail. */
/* A group heading. It was 9px in --text-faint — the SAME colour as the
   timestamps sitting beside it — so four headings separated nothing. Brighter,
   a shade larger, and carrying the rule that actually does the separating. */
.spgroup{
  margin:14px 0 4px;padding-bottom:5px;font-size:9.5px;font-weight:800;
  letter-spacing:var(--tracking-label);text-transform:uppercase;
  color:var(--text-muted);border-bottom:1px solid var(--edge-base);
}
.spbody > .spgroup:first-child{margin-top:2px}

/* THE SECTION ROWS STACK NOW — see .spgrid above, which was two columns.
   Two fitted six bare labels; a label with "12 minutes ago" under it needs
   the width, and reading down one column is how a list of twelve is read
   anyway. */
/* ── THE SECTION ROWS ────────────────────────────────────────────────
   MEASURED BEFORE AND AFTER, because "it looks cramped" is not a brief.

   Before: twelve rows at 56.6px each, 830px of body in a 520px window — 310px,
   37% of the menu, below a fold with nothing to say it was there. The label
   sat on one line and its timestamp on another, which is what bought the
   height; twelve bordered boxes bought the rest.

   THE TWELVE BORDERS WERE INVISIBLE ANYWAY. --edge-soft-2 #1E1B29 on
   --surface-obsidian #0D0B12 measures 1.16:1. That is the exact texture of
   cheapness — the cost of a card with none of the benefit of one. They are
   gone; the rows are ruled instead, in a colour you can actually see.

   After: one line per row, icon and time on the same baseline as the label,
   ~31px. Twelve rows cost ~372px instead of 679px, and the whole body fits
   the window on an ordinary day. */
.spgrid{display:grid;grid-template-columns:1fr;gap:0;margin-bottom:12px}
.nav nav .studiopop .splink{
  display:grid;grid-template-columns:15px 1fr auto;align-items:center;gap:9px;
  padding:7px 8px;background:none;border:0;
  border-bottom:1px solid var(--divider);
  color:var(--text-secondary);text-decoration:none;
}
.spgrid .splink:last-child{border-bottom:0}
.splink .spic{
  font-size:14px;line-height:0;color:var(--text-faint);
  transition:color var(--duration-fast) var(--ease-out);
}
.splink .spname{
  display:block;font-size:12.5px;color:var(--text-secondary);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* The timestamp moves onto the label's line and gets short. "12 minutes ago"
   under every row was the single biggest contributor to the height, and at
   the right-hand edge of a row you are scanning, "12m" says the same thing. */
.splink i{
  display:block;font-style:normal;font-size:10.5px;color:var(--text-faint);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.nav nav .studiopop .splink:hover,
.nav nav .studiopop .splink:focus-visible{
  background:rgba(66,200,255,.07);
}
.splink:hover .spname,.splink:focus-visible .spname{color:var(--technical-cyan)}
.splink:hover .spic,.splink:focus-visible .spic{color:var(--technical-cyan)}
.nav nav .studiopop .splink:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}
/* The two plain-text links — "Read and sign it" and the unread count — carry
   no icon and no timestamp, so they are one column, not three. */
.nav nav .studiopop .splink.wide{
  grid-template-columns:1fr;margin-bottom:10px;padding:8px 10px;
  border:1px solid var(--edge-base);border-radius:var(--radius-sm);
}
.nav nav .studiopop .splink.count{
  font-weight:700;color:var(--technical-cyan);
  border:1px solid var(--technical-cyan);border-radius:var(--radius-sm);
  padding:8px 10px;margin-bottom:10px;grid-template-columns:1fr;
}

/* SOMETHING MOVED HERE SINCE YOU LAST HAD THIS OPEN. A left edge and a
   brighter label rather than a dot: the row already carries a timestamp, and
   a dot beside a time is two ways of saying one thing. */
.nav nav .studiopop .splink.fresh{
  border-left:2px solid var(--technical-cyan);padding-left:8px;
  background:rgba(66,200,255,.05);
}
.spchat.fresh{border-left:2px solid var(--technical-cyan)}
.splink.fresh .spname{color:var(--text-primary);font-weight:700}
.splink.fresh i{color:var(--technical-cyan)}
.splink.fresh .spic{color:var(--technical-cyan)}

/* The staff room's last line. It is somebody talking, so it is the one place
   here that wraps: a colleague's sentence cut off at one line with an ellipsis
   is a worse quotation than two lines of it. */
.nav nav .studiopop .spchat{
  display:block;padding:9px 10px;margin-bottom:10px;text-decoration:none;
  background:var(--surface-obsidian);border:1px solid var(--edge-base);
  border-radius:var(--radius-sm);
}
.nav nav .studiopop .spchat:hover,
.nav nav .studiopop .spchat:focus-visible{border-color:var(--technical-cyan)}
.nav nav .studiopop .spchat:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}
.spchat b{display:block;font-size:11px;color:var(--technical-cyan)}
.spchat span{
  display:block;margin-top:3px;font-size:12px;line-height:1.45;
  color:var(--text-secondary);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.spchat i{display:block;margin-top:4px;font-style:normal;font-size:10px;color:var(--text-faint)}

/* Something with a deadline on it. `spstrip` is the quiet version, `spalert`
   the one for a week or less — the same information, weighted by how much
   time is left, because a warning that looks identical on day thirty and day
   two teaches people to ignore day two. */
.spstrip,.spalert{
  padding:10px;margin-bottom:8px;border-radius:var(--radius-sm);
  border:1px solid var(--edge-base);background:var(--surface-obsidian);
}
.spalert{border-color:var(--status-warning);background:rgba(255,176,32,.08)}
.spstrip b,.spalert b{display:block;font-size:12px;color:var(--text-primary)}
.spstrip span,.spalert span{display:block;margin-top:3px;font-size:11px;line-height:1.5;color:var(--text-muted)}
.spalert b{color:var(--status-warning)}

@media(max-width:1100px){
  /* In the mobile sheet the nav items are full-width rows, so the icon becomes
     one too, with its label visible. */
  .navstudiowrap{display:block;width:100%}
  .navstudio{width:100%;justify-content:flex-start;gap:10px;height:auto;padding:12px 0;
             background:none;border:0;border-radius:0;font-size:14px}
  .navstudiotext{position:static;width:auto;height:auto;margin:0;clip:auto;overflow:visible}
  .studiopop{position:static;min-width:0;max-width:none;margin:0 0 10px;box-shadow:none}
  .spbody{max-height:none;overflow:visible}
}

/* ─── search ────────────────────────────────────────────────────────
   One box that reaches every page, heading, command and release. The site had
   no search at all — two page-local filters and nothing that crossed a page.

   The trigger is deliberately visible and labelled rather than a bare icon
   with a shortcut: a keyboard shortcut nobody can see is a feature for the
   person who wrote it. */
.navsearch{
  display:inline-flex;align-items:center;gap:7px;
  font:inherit;font-size:12px;letter-spacing:.06em;cursor:pointer;
  padding:6px 10px;color:var(--text-muted);
  background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-sm);
  transition:border-color var(--duration-fast) var(--ease-out),color var(--duration-fast) var(--ease-out);
}
.navsearch:hover{border-color:var(--violet-deep);color:var(--text-primary)}
.navsearch:focus-visible{outline:2px solid var(--vio-hi);outline-offset:3px}
.navsearchkbd{
  font-family:var(--font-mono);font-size:9.5px;padding:1px 5px;
  color:var(--text-faint);background:var(--bg-primary);
  border:1px solid var(--edge-soft-2);border-radius:2px;
}

html.plocked,html.plocked body{overflow:hidden}
.pback{
  position:fixed;inset:0;z-index:200;background:rgba(5,4,10,.72);
  backdrop-filter:blur(3px);opacity:0;pointer-events:none;
  transition:opacity var(--duration-fast) var(--ease-out);
}
.pback.on{opacity:1;pointer-events:auto}
.pcard{
  position:fixed;z-index:201;left:50%;top:12vh;transform:translateX(-50%) translateY(-8px);
  width:min(620px,calc(100vw - 28px));max-height:74vh;display:none;flex-direction:column;
  background:linear-gradient(165deg,var(--surface-elevated),var(--surface-obsidian));
  border:1px solid var(--edge-strong);border-radius:var(--radius-md);
  box-shadow:var(--shadow-xl);overflow:hidden;
}
.pcard.on{display:flex;transform:translateX(-50%) translateY(0)}
@media(prefers-reduced-motion:reduce){
  .pback,.pcard{transition:none}
}
.ptop{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--edge-soft-2)}
.pinput{
  flex:1;min-width:0;background:none;border:0;outline:none;font:inherit;
  font-size:16px;color:var(--text-primary);
}
.pinput::placeholder{color:var(--text-muted)}
.pkbd{
  font-family:var(--font-mono);font-size:10px;padding:2px 6px;flex:none;
  color:var(--text-faint);background:var(--bg-primary);
  border:1px solid var(--edge-soft-2);border-radius:2px;
}
.plist{overflow-y:auto;flex:1;padding:6px}
.prow{
  display:grid;grid-template-columns:82px 1fr;gap:4px 12px;align-items:baseline;
  padding:9px 11px;text-decoration:none;border-radius:var(--radius-sm);
}
.prow.on{background:rgba(139,92,255,.14)}
.pkind{
  grid-row:span 2;font-size:9.5px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;font-weight:800;color:var(--text-faint);padding-top:3px;
}
.prow.on .pkind{color:var(--violet-orchid)}
.ptitle{font-size:13.5px;color:var(--text-primary);overflow-wrap:anywhere}
.pdesc{
  font-size:11.5px;color:var(--text-muted);line-height:1.45;
  display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;
}
.phint{
  margin:0;padding:10px 16px;font-size:11.5px;color:var(--text-muted);
  border-top:1px solid var(--edge-soft-2);
}
@media(max-width:560px){
  .pcard{top:0;max-height:100vh;height:100vh;width:100vw;border-radius:0;border:0}
  .prow{grid-template-columns:1fr}
  .pkind{grid-row:auto}
}

/* ─── partners ──────────────────────────────────────────────────────
   A partner card is an outbound link to somebody else's project, so it reads
   as a link and not as a panel: the whole card is the anchor, the logo is
   optional and removes itself if it 404s, and the kind is a label rather than
   a badge — "Minecraft server" tells a reader what they are about to open.

   DELIBERATELY NOT THE SPONSOR TREATMENT. Those carry a paid-placement
   disclosure and should look like what they are; borrowing their styling here
   would make an unpaid arrangement look bought, which is the same lie the
   separate data model exists to avoid. See functions/api/_partners.js.

   The empty / loading / error states reuse the shared .state component rather
   than inventing a second one — its own comment already makes the point that
   "nothing here yet" and "we could not find out" must read differently. */
.partnercard{display:flex;gap:13px;align-items:flex-start;text-decoration:none}
.partnercard:hover,.partnercard:focus-visible{border-color:var(--vio-hi)}
.partnerlogo{
  width:44px;height:44px;flex:none;object-fit:cover;border-radius:var(--radius-sm);
  border:1px solid var(--edge-soft-2);background:var(--surface-obsidian);
}
.partnerbody{min-width:0;flex:1}
.partnerbody h3{margin:0 0 4px}
.partnerkind{
  margin:0 0 6px;font-size:9.5px;font-weight:800;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--text-faint);
}
.partnerhandle{margin-top:6px;font-family:var(--font-mono);font-size:11.5px;color:var(--text-faint)}

/* ─── the studio launcher ──────────────────────────────────
   The way in to the panel from the public site, for staff only. Styled to
   read as a DIFFERENT KIND OF THING from everything around it — cyan rather
   than violet, a spine rather than a card — because it is not part of the
   page's argument to a visitor. It is a door that only some people can see,
   and it should look like one rather than like another promotion. */
.studiolaunch{padding:var(--space-8) 0 0}
.slcard{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-6);
  padding:var(--space-5) var(--space-6);
  background:linear-gradient(168deg,var(--surface-primary),var(--surface-obsidian));
  border:1px solid var(--edge-soft-2);border-left:3px solid var(--technical-cyan);
  border-radius:var(--radius-md);
}
.slhead{flex:0 0 auto}
.slhead .kick{margin:0 0 3px;color:var(--technical-cyan)}
.slhead h2{margin:0;font-size:var(--text-md);line-height:1.25}
.sllede{flex:1 1 280px;margin:0;font-size:13px;line-height:1.6;color:var(--text-muted);min-width:0}
.sllinks{display:flex;flex-wrap:wrap;gap:7px;flex:1 1 100%}
.sllink{
  font-size:11.5px;font-weight:700;letter-spacing:.06em;text-decoration:none;
  padding:6px 11px;color:var(--text-secondary);
  background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-sm);
  transition:border-color var(--duration-fast) var(--ease-out),color var(--duration-fast) var(--ease-out);
}
.sllink:hover,.sllink:focus-visible{border-color:var(--technical-cyan);color:var(--technical-cyan)}
.studiolaunch .btn{flex:0 0 auto;margin-left:auto}
@media(max-width:700px){
  .slcard{flex-direction:column;align-items:flex-start}
  .studiolaunch .btn{margin-left:0}
}

/* The compact one in the nav, on every page. Deliberately the quietest thing
   in the bar: an outline, not a fill, so it never competes with Account. */
.navstudio{
  display:inline-flex;align-items:center;gap:7px;
  font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  text-decoration:none;padding:7px 11px;
  color:var(--technical-cyan);
  border:1px solid rgba(66,200,255,.32);border-radius:var(--radius-sm);
  background:rgba(66,200,255,.06);
}
.navstudio:hover{border-color:var(--technical-cyan);background:rgba(66,200,255,.12)}
.navstudio:focus-visible{outline:2px solid var(--technical-cyan);outline-offset:3px}
.navstudiodot{width:6px;height:6px;border-radius:50%;background:var(--technical-cyan);flex:none}

/* ─── the studio panel ─────────────────────────────────────────────
   THE PANEL WEARS THE SITE. It used to open on a bare centred card with no
   header, no brand and no footer, and a flat row of ten identical outline
   buttons for navigation — visibly a different product from the site it
   edits, and the reason it drifted every time either changed. These rules
   live HERE and not in the panel's own <style> block, deliberately: that is
   what stops the two diverging again, and what makes the panel a use of the
   design system rather than a copy of it.

   Nothing here is a new visual language. The masthead is the public header's
   geometry, the rail is the nav's current-page treatment turned vertical, and
   every colour is a token. */

.panelhead{
  position:sticky;top:0;z-index:40;
  background:var(--surface-nav);backdrop-filter:blur(10px);
  border-bottom:1px solid var(--edge-soft-2);
}
.panelheadin{display:flex;align-items:center;gap:var(--space-4);padding-top:12px;padding-bottom:12px}
.panelhead .brand img{width:34px;height:34px}
.panelhead .wm b{font-size:15px}
.panelhead .wm i{font-size:8px}
.panelwho{margin-left:auto;text-align:right;min-width:0;line-height:1.25}
.pwname{display:block;font-size:13px;font-weight:700;color:var(--text-primary);
        overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pwrole{display:block;font-size:10px;letter-spacing:var(--tracking-label);
        text-transform:uppercase;color:var(--text-muted);font-weight:800}
.panelacts{display:flex;gap:8px;flex:none}
@media(max-width:720px){
  .panelheadin{flex-wrap:wrap}
  .panelwho{margin-left:auto;text-align:right}
  .panelacts{width:100%;justify-content:flex-start}
}

/* ── the rail ──
   Three labelled groups instead of one undifferentiated row. Ten peer buttons
   with no grouping is a wall; "Studio / People / Records" is three short
   lists, and a person looking for the audit log knows which one to read. */
/* FOUR GROUPS, FOUR COLUMNS. This said repeat(3, 1fr) — correct when it was
   written and wrong from the moment a fourth group was added: Records wrapped
   onto a row of its own, beside three empty columns, which is what the rail
   actually looked like. Counted rather than assumed now, at three widths. */
.panelnav{display:grid;gap:var(--space-5);margin:var(--space-6) 0 var(--space-8);align-items:start}
@media(min-width:640px){ .panelnav{grid-template-columns:repeat(2,1fr)} }
@media(min-width:1040px){ .panelnav{grid-template-columns:repeat(4,1fr)} }
.navgroup{min-width:0}
/* The category tab. It was a <p>; it is a button now, so it needs the reset a
   button does not get for free — and a visible open state, because a rail that
   shows one group at a time has to say which one. */
/* A LABEL, NOT A CONTROL. It was a button with a +/- affordance because one
   group opened at a time; every group is open now, so a button here would be
   a control that does nothing. */
.navgrouph{
  margin:0 0 8px;font-size:10px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--text-muted);font-weight:800;
  padding-bottom:6px;border-bottom:1px solid var(--edge-soft-2);
}
.navgroup + .navgroup{margin-top:2px}
.navlist{display:grid;gap:3px}
/* ONE LINE EACH. The row used to carry the section's description as well,
   which is what made seventeen rows too tall to show at once — and the
   Overview already prints every section with that same sentence. Aligned
   centre rather than to the top now that there is a single line to align. */
.navitem{
  display:flex;align-items:center;gap:10px;width:100%;text-align:left;
  padding:8px 11px;font:inherit;cursor:pointer;position:relative;
  background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-left:3px solid transparent;
  border-radius:var(--radius-md);color:var(--text-secondary);
  transition:border-color var(--duration-fast) var(--ease-out),
             background var(--duration-fast) var(--ease-out);
}
.navitem:hover{background:var(--surface-primary);border-color:var(--edge-base)}
.navitem.is-on{
  background:rgba(139,92,255,.10);
  border-color:var(--edge-base);border-left-color:var(--violet-primary);
}
.navic{
  flex:none;width:15px;height:15px;color:var(--text-muted);
}
.navitem.is-on .navic{color:var(--violet-orchid)}
.navbody{min-width:0;flex:1}
.navbody b{
  display:block;font-size:13px;font-weight:650;color:var(--text-primary);
  line-height:1.35;
  /* A long label truncates rather than wrapping the row to two lines and
     breaking the alignment of the column beside it. */
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.navitem.is-on .navbody b{color:var(--violet-crystal)}
/* The description is part of the control, not a tooltip: thirteen sections
   named in one or two words each explain themselves to whoever built them and
   to nobody else. A title attribute would have hidden this from touch and
   from keyboards, which are the two ways an unfamiliar reader is most likely
   to arrive. */
.navbody i{
  display:block;font-style:normal;font-size:11.5px;line-height:1.45;
  color:var(--text-muted);margin-top:3px;
}
/* "Something changed here while you were on another screen". A dot and never
   a count — a count would mean a list(), which is the rule this codebase
   already wrote down the hard way. */
.navdot{
  position:absolute;top:50%;transform:translateY(-50%);right:9px;
  width:7px;height:7px;border-radius:50%;
  background:var(--molten-orange);box-shadow:0 0 0 3px rgba(255,106,42,.16);
}

/* ── the overview's two panels ──
   The landing screen used to be a card per section — .ovgrid and .ovcard,
   removed with the last consumer — which repeated the rail directly above it.
   These replace it: what is waiting for you, and what moved since you were
   last here. Two columns on a wide screen, stacked below, because they are
   read as a pair. */
.ovcols{display:grid;gap:var(--space-4);align-items:start}
@media(min-width:860px){ .ovcols{grid-template-columns:1fr 1fr} }
.ovpanel{
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-lg);
  background:var(--surface-obsidian);padding:var(--space-4) var(--space-4) var(--space-3);
}
.ovpanelh{
  margin:0 0 12px;font-size:10px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--text-muted);font-weight:800;
}
/* A row is a button when it goes somewhere and a paragraph when it does not,
   so nothing looks clickable that is not. */
.ovrow{
  display:block;width:100%;text-align:left;font:inherit;
  padding:9px 11px;margin:0 0 4px;border-radius:var(--radius-md);
  border:1px solid transparent;background:none;color:var(--text-secondary);
  font-size:13px;line-height:1.5;
}
button.ovrow{cursor:pointer}
button.ovrow:hover{background:var(--surface-primary);border-color:var(--edge-base)}
button.ovrow:focus-visible{box-shadow:var(--focus-ring-inset)}
/* Something moved. The same orange the rail's dots use, so the two readings
   of "new" on one screen are visibly the same fact. */
.ovrow.is-new{border-left:3px solid var(--molten-orange);padding-left:9px}
.ovnone{margin:0;padding:9px 11px;font-size:13px;line-height:1.5;color:var(--text-muted)}

/* ── second-level tabs ──
   The two biggest screens were seventeen stacked fieldsets between them, in one
   continuous scroll with no in-page navigation at all. The strip is built from
   the legends, so it cannot list a section that is not there or miss one that
   is. Styled as a quiet segmented row rather than as buttons: these switch what
   you are looking at, they do not do anything. */
.subtabs{
  display:flex;flex-wrap:wrap;gap:4px;margin:0 0 var(--space-6);
  padding:4px;background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-md);
}
.subtab{
  font:inherit;font-size:12px;font-weight:700;letter-spacing:.04em;cursor:pointer;
  padding:8px 13px;color:var(--text-muted);background:none;border:0;
  border-radius:var(--radius-sm);
  transition:color var(--duration-fast) var(--ease-out),background var(--duration-fast) var(--ease-out);
}
.subtab:hover{color:var(--text-primary);background:rgba(255,255,255,.03)}
.subtab.is-on{color:var(--violet-crystal);background:rgba(139,92,255,.14)}
.subtab:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}
@media(max-width:640px){
  .subtabs{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .subtab{white-space:nowrap}
}
/* The legend is the section's heading and receives focus when its tab is
   chosen, so it must not draw a ring of its own on top of that. */
legend:focus{outline:none}

/* ── the panel's own view headings ── */
.viewhead{margin:0 0 var(--space-6)}
.viewhead h1{font-size:var(--text-2xl);margin:0 0 6px}
.viewhead h1:focus{outline:none}
.viewhead .lede{margin:0;font-size:13.5px;line-height:1.65;color:var(--text-muted);max-width:70ch}
/* Punishment rows: the KIND leads, because "banned" and "warned" are the two
   things a reader most needs to tell apart at a glance. Red is reserved for
   the one that actually blocks somebody. */
.tag.pun-ban{color:var(--status-error);border-color:rgba(255,107,107,.42);background:rgba(255,107,107,.10)}
.tag.pun-mute{color:var(--status-warning);border-color:rgba(255,182,72,.42);background:rgba(255,182,72,.10)}
.tag.pun-warn{color:var(--text-muted)}
.punishdiscord{margin:14px 0 4px}
.punishdlabel{display:flex;gap:10px;align-items:flex-start;cursor:pointer;
              padding:11px 13px;background:var(--surface-obsidian);
              border:1px solid var(--edge-soft-2);border-left:3px solid var(--technical-cyan);
              border-radius:var(--radius-md)}
.punishdlabel input{margin-top:2px;flex:none;accent-color:var(--technical-cyan)}
.punishdlabel input:disabled{opacity:.45}
.punishdlabel b{display:block;font-size:13px;color:var(--text-primary);
                letter-spacing:0;text-transform:none;font-weight:700}
.punishdlabel i{display:block;font-style:normal;font-size:12px;line-height:1.55;
                color:var(--text-muted);margin-top:4px;max-width:70ch}
.chatnote{margin:12px 0 0;font-size:11.5px;line-height:1.5;color:var(--text-muted)}
/* A room the reader has not joined yet: visible, clearly not open. */
.roombtn.locked{opacity:.72}
.roombtn.locked:hover{opacity:1}
.roomadmin{margin-top:12px;padding-top:12px;border-top:1px solid var(--edge-soft-2)}
.roomnew{width:100%;justify-content:center}
.roomform{margin-top:10px;display:grid;gap:2px}
.roomform label{display:block;font-size:10px;letter-spacing:var(--tracking-label);
                text-transform:uppercase;color:var(--text-muted);font-weight:800;margin:6px 0 5px}
.roomform .fld{font-size:12.5px;padding:8px 10px;margin-bottom:2px}
.roomform .btn{margin-top:10px;justify-content:center}
.roomkinds{display:grid;gap:6px;margin:12px 0 2px}

/* ---- waiting ----
   Nine places across the site said "Checking your account\u2026" or "Loading the
   document\u2026" as bare grey text with an inline style. It reads as a page that
   has stopped rather than one that is working, and it was the single most
   unfinished-feeling thing on the site.

   A dot that pulses, at the size of the text, in front of the same words. No
   spinner graphic, no skeleton blocks, no layout change \u2014 those cost markup on
   nine pages and this costs a class. The words stay: "Checking your account"
   tells a reader what is happening, which a spinner does not.

   The animation is the whole reason this works, so it has to survive: paused
   under reduced motion rather than removed, leaving a visible dot that is
   simply still. A reader who asked for less motion still gets the affordance. */
.waiting{
  display:flex;
  align-items:center;
  gap:9px;
  margin:0;
  font-size:13.5px;
  color:var(--dim);
}
.waiting::before{
  content:'';
  flex:none;
  width:7px;
  height:7px;
  border-radius:50%;
  background:var(--vio-hi);
  animation:waitpulse 1.15s ease-in-out infinite;
}
@keyframes waitpulse{
  0%,100%{opacity:.25;transform:scale(.78)}
  50%    {opacity:1;  transform:scale(1)}
}
@media (prefers-reduced-motion:reduce){
  .waiting::before{animation:none;opacity:.85}
}
/* Inside a table cell the flex row would fight the cell's own alignment. */
td.waiting,.waiting.incell{display:table-cell}
td.waiting::before,.waiting.incell::before{display:inline-block;margin-right:9px;vertical-align:middle}

/* ---- the staff chat's status line ----
   #chatMsg is NOT inside .appform, and the only .msg rules in this stylesheet
   are scoped to .appform — so every say('ok') and say('err') the staff chat has
   ever emitted rendered as unstyled body text. It was legible, which is why it
   went unnoticed; an error looked exactly like a confirmation. Found while
   adding the 'warn' state below, by checking whether that state had a rule and
   discovering none of them did.

   Same three colours the rest of the panel uses, so a staff chat error looks
   like an error everywhere else does. */
#chatMsg .msg{
  margin:8px 0 0;
  padding:9px 11px;
  border-left:3px solid var(--edge);
  border-radius:0 8px 8px 0;
  background:var(--panel-hi);
  font-size:12.5px;
  line-height:1.55;
  color:var(--ink);
}
#chatMsg .msg.ok{border-left-color:var(--status-success);background:rgba(84,224,138,.09)}
#chatMsg .msg.err{border-left-color:var(--status-error);background:rgba(255,107,107,.09)}
/* Partial success — the room WAS created, some of the roster was not added.
   Not red: a red banner sends somebody looking for a room that is sitting
   right there. */
#chatMsg .msg.warn{border-left-color:var(--status-warning);background:rgba(255,182,72,.09)}

/* ---- the invite picker and the room editor ----
   Both live inside the staff-chat rail, which is narrow, so everything here is
   a single column and sized down from the panel's usual controls. */
.pickernote{margin:10px 0 6px;font-size:11.5px;line-height:1.55;color:var(--text-muted)}

.inviterow{margin-top:4px}
.memberpick{display:grid;gap:2px;max-height:190px;overflow-y:auto;
  /* Scrolls rather than growing: a studio with thirty staff would otherwise
     push the Create button off the bottom of the rail with no hint it moved. */
  border:1px solid var(--edge-soft);border-radius:8px;padding:6px}
.memberrow{display:flex;gap:8px;align-items:baseline;cursor:pointer;
  padding:5px 6px;border-radius:6px;font-size:12.5px}
.memberrow:hover{background:var(--panel-hi)}
.memberrow input{margin:0;flex:none}
.memberrow b{font-weight:700;color:var(--ink)}
.memberrow i{font-style:normal;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-muted);margin-left:auto}

.roommanage{margin-top:8px;width:100%;justify-content:center}
.roommanagebox{margin-top:10px;display:grid;gap:10px}

.roomedit{display:grid;gap:4px;padding:10px;border:1px solid var(--edge-soft);
  border-radius:10px;background:var(--panel)}
.roomedithead{display:flex;flex-direction:column;gap:2px;margin-bottom:4px}
.roomedithead b{font-size:12.5px;color:var(--ink)}
.roomedithead i{font-style:normal;font-size:10.5px;color:var(--text-muted)}
.roomedit .fld{font-size:12px;padding:7px 9px}
.roomeditacts{display:flex;gap:8px;margin-top:8px}
.roomeditacts .btn{flex:1;justify-content:center;margin-top:0}

/* The armed state of a two-press delete. It has to look different enough that
   the second press is a decision rather than a reflex — the first press has
   already changed the label to name the room and say the messages go too. */
.roomdel.is-armed{
  /* --status-error, which is what .note.danger already uses for destructive
     things. The first draft of this said var(--status-danger, #ff6a2a) — a
     token that does not exist, so the hardcoded fallback was doing all the
     work and the token gate could not see it, because a var() WITH a fallback
     is legitimately excluded from that check. A fallback is how a phantom
     token hides from the very gate written to catch phantom tokens. */
  border-color:var(--status-error);
  color:var(--status-error);
  font-size:11px;
}
.roomkind{display:flex;gap:9px;align-items:flex-start;cursor:pointer;
          padding:8px 10px;background:var(--surface-obsidian);
          border:1px solid var(--edge-soft-2);border-radius:var(--radius-sm)}
.roomkind:hover{border-color:var(--edge-base)}
.roomkind input{margin-top:3px;flex:none;accent-color:var(--violet-primary)}
.roomkind b{display:block;font-size:12.5px;color:var(--text-primary);
            letter-spacing:0;text-transform:none;font-weight:700}
.roomkind i{display:block;font-style:normal;font-size:11px;line-height:1.4;
            color:var(--text-muted);margin-top:2px}
.chatsub{font-size:11.5px;color:var(--text-muted);margin-left:auto}
/* A tombstoned message: removed, and the room says so rather than closing
   over the gap as though nothing had been there. */
.cmsg.gone .txt{color:var(--text-faint);font-style:italic}
.crole{
  font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;font-weight:800;
  color:var(--text-faint);margin-left:7px;
}
.cduty{
  display:inline-block;width:6px;height:6px;border-radius:50%;
  background:var(--status-success);margin-left:7px;vertical-align:middle;
}

/* ─── chat ─────────────────────────────────────────────────────────
   ONE CHAT SURFACE, TWO PLACES THAT USE IT. These rules were inline in
   public/forum/index.html, which made them unreachable from anywhere else —
   so the staff chat in the admin panel would have had to be a second,
   near-identical block that could drift from this one the first time either
   was touched. They are the same component: a room rail, a scrolling log of
   messages with an avatar and a name, and a composer. The panel adds nothing
   to it but a different set of rooms.

   The panel loads this stylesheet already (public/admin/index.html), so
   moving the rules here is what makes the staff chat a use of an existing
   component rather than a new one. */
.chatlayout{display:grid;grid-template-columns:230px 1fr;gap:18px;align-items:start}
.chatrooms{border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));padding:14px}
.chatroomshead{display:flex;align-items:center;gap:8px;margin-bottom:10px;
               font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);font-weight:800}
.chatroomshead .btn{margin-left:auto;padding:5px 8px;font-size:10px}
.roombtn{display:block;width:100%;text-align:left;background:none;border:0;cursor:pointer;
         color:var(--mut);font:inherit;font-size:13px;padding:9px 10px;border-radius:0}
.roombtn:hover{background:rgba(165,109,255,.1);color:var(--ink)}
.roombtn.on{background:rgba(165,109,255,.16);color:var(--ink)}
.roombtn i{display:block;font-style:normal;font-size:11px;color:var(--faint);margin-top:3px;
           overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.roombtn .dot{display:inline-block;width:7px;height:7px;background:var(--lava);margin-right:6px;border-radius:50%}
.grpform{margin-top:12px;border-top:1px solid var(--edge-soft);padding-top:12px}
.grpform label{display:block;font-size:10px;letter-spacing:.18em;text-transform:uppercase;
               color:var(--dim);font-weight:800;margin:0 0 5px}
.grpform .fld{font-size:12.5px;padding:8px 10px;margin-bottom:10px}
.grphint{letter-spacing:0;text-transform:none;font-weight:400;color:var(--faint)}
.grpactions{display:flex;gap:8px}
.dmopen{display:flex;gap:6px;margin-top:12px;border-top:1px solid var(--edge-soft);padding-top:12px}
.dmopen .fld{font-size:12.5px;padding:9px 10px}
.chatmain{border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));
          display:flex;flex-direction:column;min-height:460px}
.chathead{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--edge-soft)}
.chathead b{font-size:14.5px;color:var(--ink)}
.chatlog{flex:1;overflow-y:auto;max-height:52vh;padding:16px 18px;display:flex;flex-direction:column;gap:12px}
.cmsg{display:flex;gap:10px;align-items:flex-start}
.cmsg .av{width:30px;height:30px;flex:0 0 30px;border:1px solid var(--edge);background:var(--deep);
          object-fit:cover;border-radius:50%}
.cmsg .av-mono{display:inline-flex;align-items:center;justify-content:center;font-size:13px;
          font-weight:800;color:var(--violet-crystal);
          background:linear-gradient(165deg,var(--violet-deep),var(--surface-obsidian));
          border-color:var(--violet-deep)}
.cmsg .cb{min-width:0;flex:1}
.cmsg .who{font-size:12px;color:var(--vio-hi);font-weight:700}
.cmsg .when{font-size:10.5px;color:var(--faint);margin-left:7px}
.cmsg .txt{font-size:13.5px;color:var(--ink);line-height:1.6;white-space:pre-wrap;word-break:break-word;margin-top:2px}
.cmsg .del{background:none;border:0;color:var(--faint);cursor:pointer;font-size:15px;line-height:1;padding:0 4px}
/* The delete x is a 15px glyph with 4px of padding: 17x15 to hit, which a thumb
   covers without finding. On a touch screen it is padded out to about 29x35 and
   margined back by the same amount, so the message line does not move. */
@media(pointer:coarse){.cmsg .del{padding:10px;margin:-10px -6px}}
.cmsg .del:hover{color:var(--bad)}
.cmsg.mine .who{color:var(--lava-hi)}
.chatsend{display:flex;gap:8px;padding:13px 18px;border-top:1px solid var(--edge-soft)}
.chatempty{color:var(--faint);font-size:13px;margin:auto}

/* ─── mentions, actions, commands and being told ───────────────────
   Shared by both chats for the same reason the rest of this block is: they
   are one component used twice, and a mention that looks like a mention in
   one of them and like plain text in the other is somebody assuming they
   were heard. See public/js/chat-kit.js. */

/* A NAME. Marked as a name whoever it belongs to, and marked harder when it
   is yours — you have to be able to find the line that was addressed to you
   by scanning, without reading every line. */
.mn{color:var(--vio-hi);font-weight:700}
.mn.me{background:rgba(165,109,255,.18);color:var(--ink);
       padding:0 4px;border-radius:2px;box-shadow:inset 0 0 0 1px rgba(165,109,255,.35)}
/* The whole message, when it named you. A left edge rather than a fill: the
   log is a column of messages and a filled row reads as a different kind of
   thing rather than as an emphasised one. */
.cmsg.at{border-left:2px solid var(--vio-hi);padding-left:10px;margin-left:-12px}

/* An action — /me makes tea. One sentence, so the words are set as prose
   rather than as speech, and the marker separates the name from the verb. */
.cmsg.emote .txt{font-style:italic;color:var(--mut)}
.emotemark{color:var(--faint);margin-left:6px;font-size:11px}

/* A line only you can see: /help, /who, the reason /go found nothing. Set
   apart from the conversation because it is not part of it. */
.sysline{font-family:var(--font-mono);font-size:12px;line-height:1.65;color:var(--mut);
         white-space:pre-wrap;word-break:break-word;
         border-left:2px solid var(--edge-strong);padding:2px 0 2px 10px}

/* The composer's suggestion list. Fixed, and parented to <body>, because the
   panel's chat sits inside a scrolling section with its own stacking context
   and an absolutely positioned list was clipped to the log. */
.cmdpop{position:fixed;z-index:80;margin:0;padding:4px;list-style:none;max-height:230px;
        overflow-y:auto;background:var(--panel);border:1px solid var(--edge-strong);
        box-shadow:0 10px 30px rgba(0,0,0,.45)}
.cmdpop li{display:flex;gap:10px;align-items:baseline;padding:6px 9px;cursor:pointer}
.cmdpop li.on{background:rgba(165,109,255,.16)}
.cmdpop li b{font-family:var(--font-mono);font-size:12.5px;color:var(--ink);flex:none}
.cmdpop li span{font-size:11.5px;color:var(--faint);overflow:hidden;
                text-overflow:ellipsis;white-space:nowrap}

/* A room with something in it you have not read. The dot is the existing
   .roombtn .dot; this is the weight that makes the row itself readable as
   unread from across the rail. */
.roombtn.fresh{color:var(--ink)}
.roombtn.fresh span{font-weight:700}

/* The card that says somebody spoke while you were elsewhere. Bottom-right,
   clickable, gone in seven seconds, never more than three at once. */
.chattoasts{position:fixed;right:16px;bottom:16px;z-index:90;
            display:flex;flex-direction:column;gap:8px;align-items:flex-end;
            max-width:min(360px,calc(100vw - 32px))}
.chattoast{display:block;text-align:left;max-width:100%;cursor:pointer;
           font:inherit;font-size:12.5px;line-height:1.5;color:var(--ink);
           background:linear-gradient(165deg,var(--panel),var(--deep));
           border:1px solid var(--edge-strong);padding:10px 13px;
           box-shadow:0 12px 34px rgba(0,0,0,.5)}
.chattoast:hover{border-color:var(--vio-hi)}
@media(prefers-reduced-motion:no-preference){
  .chattoast{animation:chattoastin var(--duration-base) var(--ease-out)}
}
@keyframes chattoastin{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media(max-width:760px){
  .chatlayout{grid-template-columns:1fr}
  .chatlog{max-height:44vh}
}

/* A message that has been typed and sent but not yet confirmed by the server.
   Shown immediately, because waiting on the network to display somebody their
   own sentence is the one delay in a chat that is entirely avoidable — the
   text is already on the machine. Dimmed so it is visibly not landed yet, and
   carrying no delete control, because there is nothing to delete until it is
   stored. */
.cmsg.pending{opacity:.55}
.cmsg.pending .when{font-style:italic}
@media(prefers-reduced-motion:no-preference){
  .cmsg.pending{animation:cmsg-wait 1.4s var(--ease-in-out) infinite}
}
@keyframes cmsg-wait{0%,100%{opacity:.45}50%{opacity:.7}}

/* ─── footer ─── */
footer{padding:46px 0 64px;border-top:1px solid var(--edge-soft);color:var(--dim);font-size:12.5px;margin-top:var(--step)}
/* The brand block plus one column per FOOTER_COLUMNS entry in
   tools/build-chrome.py.
   FLEX, NOT GRID, AND THE REASON IS A COUNT. It was
   `grid-template-columns:1.4fr 1fr 1fr 1fr` — a hard four — so adding a fifth
   column ("Work with us", holding the two apply pages that were previously
   unreachable) silently pushed it onto a second row. Replacing the count with
   `repeat(auto-fit,…)` is worse than it looks: auto-fit may not be combined
   with an `fr` track, which makes the whole declaration invalid, and the
   footer collapses to ONE column with no warning anywhere. That was measured.
   Flex wrapping needs no count at all, so the stylesheet cannot fall out of
   step with the generator again. */
.fgrid{display:flex;flex-wrap:wrap;gap:28px;margin-bottom:32px}
.fgrid>div{flex:1 1 150px;min-width:0}
.fgrid>div:first-child{flex:1 1 230px}
@media(max-width:520px){.fgrid>div,.fgrid>div:first-child{flex:1 1 100%}}
.fgrid h4{margin:0 0 12px;font-size:10px;letter-spacing:.24em;text-transform:uppercase;color:var(--mut);font-weight:800}
.fgrid ul{margin:0;padding:0;list-style:none;display:grid;gap:9px}
.fgrid a{color:var(--dim);text-decoration:none;font-size:13px}
.fgrid a:hover{color:var(--vio-hi)}
/* The footer's own brand lockup. These three rules were an inline style="" on
   every page — `margin-bottom:14px` on the link, `width:34px;height:34px` on
   the image, and font sizes on the wordmark — repeated fifteen times because
   the footer markup was repeated fifteen times. tools/build-chrome.py now
   writes one footer, so the styling has somewhere to live that is not the
   markup. */
.fbrand{margin-bottom:14px}
.fbrand img{width:34px;height:34px}
.fbrand .wm b{font-size:14px}
.fbrand .wm i{font-size:8px}
.ftag{margin:0;color:var(--faint);font-size:12.5px;max-width:30ch;line-height:var(--leading-snug)}
.fbar{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;align-items:center;padding-top:24px;border-top:1px solid var(--edge-soft)}
.disc{margin-top:18px;font-size:11.5px;color:var(--faint);max-width:72ch}
/* The footer's last line was a hardcoded #4f4770 — under 4.5:1 on every page
   background, and invisible to the contrast gate, which only checks tokens. Now
   the faintest token that clears AA. */
.strip{text-align:center;margin-top:24px;padding-top:20px;border-top:1px solid var(--edge-soft);font-size:10px;letter-spacing:.32em;text-transform:uppercase;color:var(--text-faint)}

/* ─── reveal ─── */
/* GATED ON html.rv-ready, which site.js sets just before it starts observing.
   Ungated — as this was — a third of the homepage sits at opacity:0 if the
   script fails, is blocked, or the observer never fires. The animation was
   load-bearing for the content, which it must never be. */
html.rv-ready .rv{opacity:0;transform:translateY(22px);
  transition:opacity .7s cubic-bezier(.2,.7,.3,1),transform .7s cubic-bezier(.2,.7,.3,1)}
html.rv-ready .rv.in{opacity:1;transform:none}

@media(max-width:680px){
  .lava{width:105px}
  .nc-body,.split .txt,.sup{padding:22px}
  .hero{padding:38px 0 30px}
  .crest{width:112px;height:112px}
}
@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important}
  html.rv-ready .rv{opacity:1;transform:none}
  html{scroll-behavior:auto}
}


/* ─── team page ───────────────────────────────────────────────────────
   Was: five identical cards in insertion order, each a name, a tiny uppercase
   role and an address — with support@mwtstudios.net appearing twice, under two
   different people, formatted exactly like a personal line.

   Now: bands by what people do, ranked within, the shared inbox named once on
   the group that answers it, and a face on each card. See public/js/team.js
   for the argument; this is only what it looks like. */
/* The OUTER container holds bands; each band holds its own card grid. It used
   to be .teamgrid itself, which quietly made the three bands grid ITEMS and
   laid them out side by side in columns. */
.teamlist{display:block}
/* A band is a <section> because it is a titled group of people, and site.css
   gives every <section> `padding:var(--step) 0` — 92px top and bottom, which is
   right for a page-level band of the home page and absurd inside a list. Reset,
   and the spacing comes from the margin below instead. */
.tband{padding:0;margin-top:34px}
.tband:first-child{margin-top:22px}
.tbandhead{margin-bottom:16px}
.tbandname{
  font-size:1rem;margin:0;color:var(--text-primary);
  letter-spacing:.02em;
}
.tbandblurb{margin:5px 0 0;font-size:13px;line-height:1.6;color:var(--text-muted)}

/* The shared inbox. Recessed, because you are writing to a THING rather than
   to a person, and the whole point of hoisting it here is that a visitor can
   tell those apart at a glance. */
.tshared{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:5px 10px;
  margin-top:30px;padding:12px 15px;
  background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);
  border-left:3px solid var(--violet-primary);
  border-radius:var(--radius-sm);
}
.tsharedlab{
  font-size:9.5px;font-weight:800;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--text-faint);
}
.tsharednote{flex:1 0 100%;font-size:12px;line-height:1.55;color:var(--text-muted)}

/* auto-FILL, not auto-fit. auto-fit collapses the empty tracks, so a band with
   one person in it stretched that person's card across the full width — a
   Helper rendered three times the size of an Executive CEO. auto-fill keeps the
   track, so a card is a card whatever the band's headcount. */
.teamgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;margin-top:0}
.teamcard{
  border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));
  padding:20px 22px;position:relative;
}
/* Face and name share a row, so the picture does not push the whole card
   taller — the card is the same height whether or not somebody has one. */
.tcardtop{display:flex;align-items:flex-start;gap:13px}
.twho{min-width:0;flex:1}
.teamcard h3{font-size:1.08rem;margin:0 0 5px;color:var(--ink);line-height:1.25}
.teamcard .trole{
  font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);
  font-weight:800;margin:0;
}
.teamcard .tmail{
  display:inline-block;margin-top:14px;
  font-size:13.5px;color:var(--vio-hi);text-decoration:none;word-break:break-all;
  border-bottom:1px solid transparent;
}
.teamcard .tmail:hover{border-bottom-color:var(--vio)}
.teamcard .tmail:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}
.tshared .tmail{
  font-size:13.5px;color:var(--vio-hi);text-decoration:none;
  border-bottom:1px solid transparent;
}
.tshared .tmail:hover{border-bottom-color:var(--vio)}
.tshared .tmail:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}

/* THE FACE. A fixed 44px box so the row never reflows when a picture decodes
   late or never arrives at all — the monogram occupies exactly the space the
   image would. */
.tface{
  width:44px;height:44px;flex:none;position:relative;overflow:hidden;
  border-radius:var(--radius-sm);
  border:1px solid var(--edge-soft-2);
  background:var(--surface-obsidian);
  display:flex;align-items:center;justify-content:center;
}
/* Stacked, not swapped: the initials sit underneath and the picture fades over
   them once it has decoded. See face() in team.js for why the image must be in
   the document from the start. */
.tface img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
  opacity:0;transition:opacity var(--duration-fast) var(--ease-out);
}
.tface.has img{opacity:1}
.tface.has .tmono{visibility:hidden}
.tface.has{border-color:var(--edge-base)}
@media(prefers-reduced-motion:reduce){.tface img{transition:none}}
.tmono{
  font-size:14px;font-weight:800;letter-spacing:.04em;
  color:var(--violet-crystal);
  /* The initials sit on a faint violet wash rather than bare obsidian, so a
     card without a picture still reads as deliberate rather than broken. */
}
.tface:not(.has){background:linear-gradient(160deg,rgba(139,92,255,.16),rgba(139,92,255,.05))}

@media(max-width:520px){
  .teamgrid{grid-template-columns:1fr}
}

/* ─── products page ─── */
.catbar{display:flex;flex-wrap:wrap;gap:9px;margin:34px 0 30px}
.catchip{
  display:inline-flex;align-items:center;padding:8px 15px;text-decoration:none;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;font-weight:800;
  color:var(--mut);border:1px solid var(--edge);
}
.catchip:hover{color:var(--ink);border-color:var(--vio)}
.catchip.on{color:var(--ink);border-color:var(--vio);background:rgba(165,109,255,.16)}
.catchip:focus-visible{outline:2px solid var(--vio);outline-offset:3px}
.prodsec{margin-bottom:40px;scroll-margin-top:110px}
.psech{font-size:1.1rem;margin:0 0 6px;color:var(--ink)}
.psecb{font-size:13.5px;color:var(--mut);margin:0 0 18px}

/* An empty category. Listed on purpose — see products.js — but not competing
   with a category that has something in it. */
.prodsec.is-empty{margin-bottom:26px}
.prodsec.is-empty .psech{color:var(--dim);font-size:1rem}
.prodsec.is-empty .psecb{margin-bottom:10px}
/* ---- one empty state, used everywhere something can be empty ----
   Products' empty categories, the forum with no threads, the account page with
   no applications and no notifications. All four said their piece as a bare
   grey sentence, and three of them relied on a class that is only styled inside
   .appform — so outside one they fell back to ordinary body text and an
   "there is nothing here" read as a paragraph that had lost its heading.

   Dashed, like .adcard, which is this stylesheet's existing way of saying
   "a slot rather than a thing". A reader should be able to tell at a glance
   that a page is empty ON PURPOSE rather than broken, and that is the entire
   job. .prodempty is kept as an alias so the products page keeps working if
   this is ever split again. */
.emptystate,.prodempty{
  margin:0;
  padding:18px 20px;
  border:1px dashed var(--edge-soft);
  border-radius:10px;
  background:transparent;
  color:var(--dim);
  font-size:13px;
  line-height:1.6;
}
/* A link inside one still has to look like a link — the muted colour above
   would otherwise swallow "Apply to join the studio". */
.emptystate a{color:var(--vio-hi)}

/* ---- visually hidden, but present for a screen reader ----
   Not display:none and not visibility:hidden — both remove the element from the
   accessibility tree, which defeats the entire purpose. This is the clip-rect
   pattern: zero-size, clipped, still announced. */
.visually-hidden{
  position:absolute;
  width:1px;height:1px;
  margin:-1px;padding:0;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

/* ---- the account dashboard ----
   Four sections that used to be one column separated by hairlines. On a phone
   that was a very long scroll with no way to tell what was further down. */
.acctabs{
  display:flex;
  gap:4px;
  margin:18px 0 22px;
  padding-bottom:2px;
  border-bottom:1px solid var(--edge-soft);
  /* Scrolls sideways rather than wrapping: four tabs wrap to two rows at 360px
     and a two-row tab strip reads as two separate controls. */
  overflow-x:auto;
  scrollbar-width:none;
}
.acctabs::-webkit-scrollbar{display:none}

.acctab{
  flex:none;
  position:relative;
  appearance:none;
  background:none;
  border:0;
  border-bottom:2px solid transparent;
  margin-bottom:-3px;
  padding:10px 14px;
  font:inherit;
  font-size:13px;
  font-weight:700;
  letter-spacing:.02em;
  color:var(--dim);
  cursor:pointer;
  white-space:nowrap;
  transition:color var(--duration-fast) ease,border-color var(--duration-fast) ease;
}
.acctab:hover{color:var(--ink)}
.acctab[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--vio-hi)}
/* No clip-path on these, so a normal outline is visible and is the right one. */
.acctab:focus-visible{outline:2px solid var(--vio-hi);outline-offset:-2px;border-radius:6px}

/* An unread marker beside the Notifications tab. */
.tabdot{
  display:inline-block;
  width:6px;height:6px;
  margin-left:7px;
  border-radius:50%;
  background:var(--vio-hi);
  vertical-align:middle;
}

.accpane{scroll-margin-top:100px}
/* The panel is focused when its tab is chosen, and a focus ring around a whole
   pane is noise — the tab already shows which is selected. */
.accpane:focus{outline:none}
.accpane:focus-visible{outline:2px solid var(--vio-hi);outline-offset:4px;border-radius:8px}

.acch{margin:0 0 6px;font-size:1.05rem;color:var(--ink)}
.acchead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:6px}
.acchead .acch{margin:0}
.acccount{margin:0}
.accacts{margin-left:auto;display:flex;gap:8px}
.accsub{margin:0 0 16px}
.accfoot{margin-top:16px}
.acclist{margin-top:14px}

/* ---- a member's picture ----
   Was defined twice, identically, in the forum and account pages' own <style>
   blocks — and therefore invisible to the admin panel, which loads site.css and
   nothing else. Moving it here is what lets the panel show the same face the
   forum shows, and removes the second copy at the same time.

   .av carries the shape and is used on BOTH an <img> and the fallback <span>,
   so a picture and an initial occupy exactly the same box and a row does not
   reflow when one replaces the other. Size is set inline by the caller,
   because the same avatar appears at 24px in a list and 96px on the profile. */
.av{
  border-radius:50%;
  flex:none;
  object-fit:cover;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--edge);
  background:var(--panel-hi);
}
/* The initial, when there is no picture. letter-spacing:0 because several
   surfaces here set tracking on their containers and a single centred letter
   inherits it as a right-hand gap. */
.av-fallback{
  color:var(--vio-hi);
  font-weight:800;
  letter-spacing:0;
  background:linear-gradient(160deg,var(--panel-hi),var(--deep));
}

/* ---- three classes that replace twenty-six inline styles ----
   The same three style attributes were hand-typed across the account, join,
   hire and both application pages: an "— optional" span in a label, a
   full-width submit button, and the heading on a sign-in form. Repeated
   inline styles are how one page drifts from the others by a pixel nobody
   meant, and how a token rename misses the copies. */

/* "— optional", inside a <label>. Faint on purpose: it is the one piece of
   label text a reader may skip, which is what --faint is for. */
.opt{color:var(--faint);font-weight:400}

/* A submit button that spans its form. Named -block after the convention
   every CSS framework has used for this for fifteen years, so it reads
   correctly to anyone. */
.btn-block{width:100%;justify-content:center}

/* The heading on a sign-in, sign-up or forced-change form. */
.formh{margin:0 0 18px;font-size:1.15rem}

/* The three expanding panels — email, Discord, password — share the tab
   sections' heading and lede classes, plus a divider on top since they open
   beneath whichever tab is showing. */
.accpanel{margin-top:26px;border-top:1px solid var(--edge-soft);padding-top:22px}
.accstate{font-size:13.5px;color:var(--mut);margin-bottom:14px}
.accacts-wrap{display:flex;gap:10px;flex-wrap:wrap}
.accgap{margin-left:8px}
/* profile picture and bio, formerly inline */
.pfpreview{width:96px;height:96px;font-size:42px}
.pfnote{margin:10px 0 0;font-size:11.5px}
.pfbio{min-height:120px;resize:vertical}
.pfcount{margin:-10px 0 14px}

/* ---- finish setting up ----
   One quiet card above the tabs, present only while email or Discord is not
   done. Not a banner and not a modal: the account works fine without either,
   and the card says what you gain rather than what you lack. Violet edge, the
   site's "this is ours" colour, so it reads as a suggestion from the studio
   and not a warning from the system. */
.linknudge{
  margin:18px 0 6px;
  padding:14px 16px;
  border:1px solid var(--edge-soft);
  border-left:3px solid var(--vio-hi);
  border-radius:10px;
  background:var(--panel);
}
.linknudge h2{margin:0 0 4px;font-size:.95rem;color:var(--ink)}
.linknudge .nudgelede{margin:0 0 10px;font-size:12.5px;color:var(--dim);line-height:1.55}
.nudgerow{
  
  
  
  
  padding:8px 0;
  border-top:1px solid var(--edge-soft);
}
.nudgerow:first-of-type{border-top:0}
.nudgerow p{margin:0;flex:1 1 220px;font-size:13px;color:var(--ink);line-height:1.5}
.nudgerow p b{font-weight:700}
.nudgerow .btn{flex:none}
.nudgedismiss{
  margin-top:8px;
  font-size:11.5px;
  color:var(--dim);
  background:none;border:0;padding:0;cursor:pointer;
  text-decoration:underline;text-underline-offset:2px;
}
.nudgedismiss:hover{color:var(--ink)}
.nudgedismiss:focus-visible{outline:2px solid var(--vio-hi);outline-offset:2px;border-radius:3px}

/* ---- the link-your-accounts nudge ----
   Violet-tinted rather than warning-coloured: nothing is wrong, and an amber
   panel on an account that works would read as a problem to fix. It sits above
   the tabs because it is about the account rather than any one section, and it
   is gone entirely once both links exist. */
.nudge{
  margin:18px 0 4px;
  padding:16px 18px;
  border:1px solid var(--edge);
  border-left:3px solid var(--vio-hi);
  border-radius:10px;
  background:var(--panel-hi);
}
.nudgeh{
  margin:0 0 6px;
  font-size:13.5px;
  font-weight:800;
  letter-spacing:.01em;
  color:var(--ink);
}
.nudgep{
  margin:0 0 14px;
  font-size:13px;
  line-height:1.65;
  /* --ink, not --faint: this is the sentence the card exists to have read. */
  color:var(--ink);
}
.nudgerow{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.nudgeno{margin-left:auto;font-size:12px;color:var(--dim)}
.nudgeno:hover{color:var(--ink)}

@media (max-width:520px){
  /* "Not now" pushed to its own line rather than crushed against a primary
     action it could be mistaken for. */
  .nudgeno{margin-left:0;width:100%;text-align:left;padding-top:2px}
}

.prodgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.prodcard{
  border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));
  padding:24px 26px;display:flex;flex-direction:column;align-items:flex-start;
}
.prodcard .ptop{display:flex;align-items:center;gap:12px;width:100%;margin-bottom:8px}
.prodcard h2{font-size:1.1rem;margin:0;color:var(--ink)}
.pstatus,.pprice{
  font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  font-weight:800;padding:4px 9px;border:1px solid var(--edge);color:var(--faint);white-space:nowrap;
}
/* The first badge in the row takes the auto margin, so price+status stay
   together on the right however many badges a card ends up with. */
.prodcard .ptop > .pprice:first-of-type,
.prodcard .ptop > .pstatus:first-of-type{margin-left:auto}
/* One "free" green, wherever the label appears. The products page already had
   it via .pprice.free; the homepage card and the NexusCore hero use their own
   chip elements, so the modifier is shared rather than re-picked per page —
   three slightly different greens is how a label stops reading as one thing.

   The mod IS free, and until now the only place that said so in writing was
   BRAND-GUIDELINES. A fact the studio relies on in its advertising should be
   visible on the product itself. */
.pprice.free,.badge.free,.ver.free{
  color:#c4f5d8;border-color:rgba(84,224,138,.55);background:rgba(84,224,138,.12);
}
.pstatus.released{color:#c4f5d8;border-color:rgba(84,224,138,.45)}
.pstatus.progress{color:#ffe1ae;border-color:rgba(255,182,72,.45)}
.pcat{font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);font-weight:800;margin:0 0 12px}
.psum{font-size:13.5px;color:var(--mut);line-height:1.7;margin:0 0 18px}

/* ─── ambient audio toggle ─── */
.ambtn{
  display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;
  background:none;border:1px solid var(--edge);color:var(--dim);cursor:pointer;margin-left:14px;
}
.ambtn:hover{border-color:var(--vio);color:var(--vio-hi)}
.ambtn:focus-visible{outline:2px solid var(--vio);outline-offset:3px}
.ambtn.on{border-color:var(--vio);color:var(--vio-hi);background:rgba(165,109,255,.14)}
.amicon{display:block;width:13px;height:13px;background:currentColor;
  -webkit-mask:var(--spk) center/contain no-repeat;mask:var(--spk) center/contain no-repeat}
:root{--spk:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9v6h4l5 4V5L8 9H4z'/%3E%3Cpath d='M16 8.8a4 4 0 010 6.4' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E")}
.ambtn.on .amicon{animation:ampulse 2.6s ease-in-out infinite}
@keyframes ampulse{0%,100%{opacity:1}50%{opacity:.55}}
@media(prefers-reduced-motion:reduce){.ambtn.on .amicon{animation:none}}
@media(max-width:1100px){.ambtn{margin-left:auto;margin-right:10px}}


/* ════════════════════════════════════════════════════════════════════
   COMPONENT REFINEMENT — 2026-07-31 emblem overhaul

   Written as a layer ON TOP of the rules above rather than as edits through
   them. The originals are load-bearing across fourteen pages and eleven
   scripts; overriding a handful of properties is a visual upgrade, whereas
   rewriting them would be the rebuild the brief rules out.
   ════════════════════════════════════════════════════════════════════ */

/* ── one corner geometry ──────────────────────────────────────────────
   The site had 2px, 3px, 4px, 6px and 10px corners depending on which
   component you landed on, which reads as inconsistency rather than as
   style. Everything structural is now near-square, from the emblem's voxel
   construction; only genuinely pill-shaped things stay round. */
.btn,.card,.panel,.fld,.stat,.feat,.proj,.relcard,.teamcard,.prodcard,
.dl-btn,.chip,.spec,.note,.faq,.sup,.cmdrow,.nc,.catchip{
  border-radius:var(--radius-md);
}
.badge,.pill,.tag,.sev,.pstatus,.pprice,.ver{border-radius:var(--radius-sm)}

/* ── the rota ────────────────────────────────────────────────────────
   Seven columns that become one on a phone. A rota is read standing up, on
   the way somewhere, so the phone layout is the one that matters most. */
.rotaday{border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));
         padding:12px 13px;border-radius:var(--radius-sm);min-height:96px}
.rotaday h3{margin:0 0 10px;font-size:11px;letter-spacing:.18em;text-transform:uppercase;
            color:var(--dim);display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.rotaday h3 span{letter-spacing:0;text-transform:none;color:var(--faint);font-weight:400;font-size:11.5px}
.rotaday.is-today{border-color:var(--vio);background:linear-gradient(165deg,rgba(165,109,255,.10),var(--deep))}
.rotanone{margin:0;font-size:12px;color:var(--faint);font-style:italic}
#rotaGrid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:8px}

.shift{border:1px solid var(--edge-soft);border-left:3px solid var(--edge);
       padding:8px 10px;margin-bottom:8px;border-radius:var(--radius-sm);
       background:rgba(255,255,255,.03)}
.shift:last-child{margin-bottom:0}
/* Your own shifts, findable at a glance — the question somebody opens the
   rota to answer is almost always "am I on?". */
.shift.is-mine{border-left-color:var(--vio);background:rgba(165,109,255,.09)}
.shift b{display:block;font-size:12.5px}
.shiftwho{display:block;font-size:12px;color:var(--mut);margin-top:2px}
.shift i{display:block;font-size:11.5px;color:var(--faint);font-style:normal;margin-top:4px}
.shift .bar{margin-top:8px;gap:6px}

/* ── the board ───────────────────────────────────────────────────────*/
.rolerow.is-pinned{border-color:var(--edge)}
.rolerow.item-done{opacity:.6}
.rolerow.item-doing{border-left:3px solid var(--vio)}
.pill.is-late{color:#ffc9c9;border-color:rgba(255,107,107,.5)}
/* The item's own history, folded away. Open by default would make a board of
   twenty items unreadable; absent would mean paging the audit log to answer
   "why does this say Thursday". */
.itemhist{margin-left:auto;font-size:12px;color:var(--faint);max-width:100%}
.itemhist summary{cursor:pointer;color:var(--mut);padding:4px 0}
.itemhist p{margin:2px 0 0;font-size:11.5px;line-height:1.5}

@media (max-width:900px){
  #rotaGrid{grid-template-columns:1fr}
  .rotaday{min-height:0}
  .itemhist{margin-left:0;width:100%}
}

/* ── the role and badge editors ──────────────────────────────────────
   Both screens are a list of rows that each carry a name, some metadata, what
   the thing reaches, and the controls for it — so they share one set of
   classes rather than each growing its own. */
.rolerow{border:1px solid var(--edge-soft);background:linear-gradient(165deg,var(--panel),var(--deep));
         padding:16px 18px;margin-bottom:12px;border-radius:var(--radius-sm)}
.rolehead{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.rolehead>b{font-size:15px;letter-spacing:.01em}
/* Pushed right so the count sits at the end of the row however many pills a
   role ends up wearing. */
.rolehold{margin-left:auto;font-size:12px;color:var(--faint);white-space:nowrap}
.roledesc{margin:8px 0 0;font-size:13.5px;color:var(--mut);max-width:74ch}
.rolecaps{display:flex;gap:6px;flex-wrap:wrap;margin-top:12px}
.rolecaps>i{font-size:12.5px;color:var(--faint);font-style:italic}
.rolerow .bar{margin-top:14px;align-items:center}
/* THE REASON A CONTROL IS ABSENT, on the line where the control would be. A
   screen that silently omits Edit for half its rows reads as broken; one that
   says "this role outranks yours" reads as a rule. */
.rolewhy{margin:0;font-size:12.5px;color:var(--faint);max-width:74ch}

.capgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:8px}
.capitem{display:grid;grid-template-columns:auto 1fr;gap:2px 9px;align-items:start;
         padding:9px 11px;border:1px solid var(--edge-soft);border-radius:var(--radius-sm);
         margin:0;text-transform:none;letter-spacing:0;font-weight:400;cursor:pointer}
.capitem input{margin:2px 0 0;grid-row:span 2}
.capitem b{font-size:12.5px;color:var(--ink);font-family:var(--mono,monospace)}
.capitem i{font-size:12px;color:var(--faint);font-style:normal;line-height:1.45}
.capitem:hover{border-color:var(--edge)}
/* Not hidden: somebody wondering why a role lacks a capability is better
   served by seeing it greyed with a reason than by it not being there. */
.capitem.is-off{opacity:.5;cursor:not-allowed}
.capitem.is-off:hover{border-color:var(--edge-soft)}

.capmean{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap;
         padding:9px 0;border-bottom:1px solid var(--edge-soft)}
.capmean:last-child{border-bottom:0}
.capmean b{font-size:12.5px;font-family:var(--mono,monospace);min-width:15ch}
.capmean i{font-size:13px;color:var(--mut);font-style:normal;flex:1 1 24ch}

@media (max-width:600px){
  .rolehold{margin-left:0;width:100%}
  .capgrid{grid-template-columns:1fr}
}

/* ── member badge tones ──────────────────────────────────────────────
   Lived in public/forum/index.html until the admin panel gained a badge
   editor. Two surfaces now render `pill badge-<tone>` — the forum and
   /admin/ — so the palette has to be somewhere both of them load.

   THIS LIST IS LOAD-BEARING. functions/api/_badges.js refuses a tone that is
   not in it, and tools/test-badges.mjs fails the release if the two drift in
   either direction: a tone offered by the editor with no rule here renders
   unstyled, and a rule here the editor does not offer is a colour nobody can
   pick. Adding one means adding it in both places.

   The colours mirror the Discord role colours so the two surfaces read as one
   community rather than two. */
  /* Badge tones mirror the Discord role colours so the two surfaces read as
     one community rather than two. */
.pill.badge-teal{color:#7fe4d2;border-color:rgba(26,188,156,.5)}
.pill.badge-lava{color:var(--lava-hi);border-color:rgba(255,122,61,.5)}
.pill.badge-azure{color:#9dc9ff;border-color:rgba(52,152,219,.5)}
.pill.badge-green{color:#a9eec2;border-color:rgba(46,204,113,.45)}
.pill.badge-pink{color:#f8a8c8;border-color:rgba(173,20,87,.55)}
.pill.badge-royal{color:#c9a6ff;border-color:rgba(113,54,138,.6);background:rgba(113,54,138,.16)}
.pill.badge-gold{color:#ffe1ae;border-color:rgba(255,182,72,.5)}
.pill.badge-rose{color:#ff9db8;border-color:rgba(233,30,99,.5)}
/* Accountant. A deeper emerald than badge-green (#a9eec2) and cooler than
   badge-teal (#7fe4d2), so the three do not read as one colour in a row of
   badges — and it matches the Discord role's #0b8457, which is the whole
   point of these tones. */
.pill.badge-emerald{color:#6fd6ac;border-color:rgba(11,132,87,.6);background:rgba(11,132,87,.14)}


/* ── buttons ──────────────────────────────────────────────────────────
   Three ranks and no ambiguity about which is which: filled violet for the
   one action a page wants, outlined for everything else, quiet for tertiary.
   Hover changes colour and elevation only — never size, never position, so
   nothing reflows under the pointer. */
.btn{
  font-family:var(--font-body);
  transition:background var(--duration-fast) var(--ease-out),
             border-color var(--duration-fast) var(--ease-out),
             color var(--duration-fast) var(--ease-out),
             box-shadow var(--duration-fast) var(--ease-out);
}
.btn-v{
  background:linear-gradient(180deg,var(--violet-primary),var(--violet-deep));
  border:1px solid var(--violet-primary);color:#fff;
  box-shadow:var(--shadow-sm);
}
.btn-v:hover{
  background:linear-gradient(180deg,var(--violet-orchid),var(--violet-primary));
  border-color:var(--violet-orchid);box-shadow:var(--shadow-md),var(--glow-medium);
}
.btn-o{border:1px solid var(--edge-strong);background:rgba(255,255,255,.02);color:var(--text-secondary)}
.btn-o:hover{border-color:var(--violet-primary);color:var(--text-primary);background:rgba(139,92,255,.10)}
/* A third tier, quieter than btn-o: an invitation rather than a call to
   action. The tour belongs here — findable by anyone who wants it, and never
   competing for attention with Download. */
.btn-g{border:1px solid transparent;background:none;color:var(--text-muted);
       display:inline-flex;align-items:center;gap:8px}
.btn-g:hover{color:var(--text-primary);border-color:var(--edge-strong);background:rgba(255,255,255,.02)}
.btn-g:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}
.tourdot{width:7px;height:7px;border-radius:50%;background:var(--violet-orchid);flex:none;
         box-shadow:0 0 0 0 rgba(165,109,255,.55);animation:tourpulse 2.6s ease-out infinite}
@keyframes tourpulse{
  0%   {box-shadow:0 0 0 0 rgba(165,109,255,.5)}
  70%  {box-shadow:0 0 0 9px rgba(165,109,255,0)}
  100% {box-shadow:0 0 0 0 rgba(165,109,255,0)}
}
/* A pulsing dot is a small attention magnet, and somebody who has asked not to
   be moved at should not get one. */
@media (prefers-reduced-motion: reduce){ .tourdot{animation:none} }
/* Inset, because .btn is clipped — see --focus-ring-inset. */
.btn:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}

/* THE OTHER CHAMFERED LINKS. .btn got an inset ring because clip-path clips
   both outline and outset box-shadow — measured, see --focus-ring-inset — and
   the same clip is on eleven classes. Nine of them are decorative or never
   focusable; these three are <a> elements a keyboard user tabs through, and
   each showed NOTHING on focus:

     .card   the documentation and support cards
     .mail   the mailto link on the support page
     .tag    the project tags on the home page

   Found by listing every clip-path'd class, then checking which are applied to
   focusable elements in the markup. A sighted keyboard user tabbing through
   /docs/ had no idea where they were. */
.card:focus-visible,
.mail:focus-visible,
.tag:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}
.btn[disabled],.btn:disabled{opacity:.5;cursor:not-allowed;box-shadow:none}
.btn[disabled]:hover,.btn:disabled:hover{background:inherit;border-color:var(--edge-base)}

/* ── surfaces ─────────────────────────────────────────────────────────
   Deepslate panels on an obsidian field. The border does the framing and a
   single shadow does the lift; there is no glow on a resting card, which is
   what stops fourteen pages of cards turning into a light show. */
.card,.panel,.feat,.proj,.relcard,.teamcard,.prodcard,.stat,.sup,.faq{
  background:linear-gradient(168deg,var(--surface-primary),var(--surface-obsidian));
  border:1px solid var(--edge-soft-2);
  box-shadow:var(--shadow-sm);
  transition:border-color var(--duration-base) var(--ease-out),
             box-shadow var(--duration-base) var(--ease-out),
             transform var(--duration-base) var(--ease-out);
}
.card:hover,.feat:hover,.proj:hover,.relcard:hover,.teamcard:hover,.prodcard:hover{
  border-color:var(--edge-strong);
  box-shadow:var(--shadow-md);
  transform:translateY(-2px);
}

/* Recessed things read as cut INTO the surface, not sitting on it. */
.fld,pre,code,.cmdrow,.tablewrap{
  background:var(--surface-obsidian);
  border-color:var(--edge-soft-2);
}
.fld:focus{
  outline:none;border-color:var(--violet-primary);
  background:rgba(139,92,255,.07);
  box-shadow:0 0 0 3px rgba(139,92,255,.18);
}

/* ── technical metadata ───────────────────────────────────────────────
   Monospace and cyan, everywhere and only here: versions, loaders, hashes,
   commands, file names. Consistency is what makes the colour mean
   "a machine produced this" rather than "this bit is blue". */
code,kbd,samp,.ver,.spec b,.cmdrow code,.mono{font-family:var(--font-mono)}
.ver{color:var(--technical-cyan)}

/* ── status and severity ──────────────────────────────────────────────
   Warnings must not be quiet. The brief is explicit that technical caveats
   stay legible instead of shrinking into low-contrast footnotes, so every
   state carries a tinted field and a full-strength left rule, not grey text. */
.note,.warnbox{
  border-left:3px solid var(--status-warning);
  background:rgba(255,182,72,.08);
  color:var(--text-primary);
  padding:var(--space-4) var(--space-5);
}
.note.info{border-left-color:var(--status-info);background:rgba(66,200,255,.08)}
.note.danger{border-left-color:var(--status-error);background:rgba(255,107,107,.09)}
.note.ok{border-left-color:var(--status-success);background:rgba(84,224,138,.08)}

.sev[data-sev="critical"],.sev.critical{color:var(--defect-critical);border-color:rgba(255,90,90,.5)}
.sev[data-sev="high"],.sev.high{color:var(--defect-high);border-color:rgba(255,106,42,.5)}
.sev[data-sev="medium"],.sev.medium{color:var(--defect-medium);border-color:rgba(255,182,72,.5)}
.sev[data-sev="low"],.sev.low{color:var(--defect-low);border-color:var(--edge-strong)}

/* ── typography rhythm ────────────────────────────────────────────────
   Display face and tighter leading for headings, comfortable measure for
   prose. Long-form text is capped at --width-prose so a paragraph never runs
   the full 1160px and becomes hard to track back from. */
h1,h2,h3,h4,.ph,.psech{font-family:var(--font-display);line-height:var(--leading-tight)}
h1,.ph{letter-spacing:-.015em}
p,li{max-width:var(--width-prose)}
.center p,.hero p,.psub{margin-left:auto;margin-right:auto}
.crumb,.kick{letter-spacing:var(--tracking-label)}

/* ── motion ───────────────────────────────────────────────────────────
   One switch. Anyone who has asked their system for less motion gets a site
   with none of it, including the ambience pulse and the card lift. */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;
  }
  .card:hover,.feat:hover,.proj:hover,.relcard:hover,.teamcard:hover,.prodcard:hover{transform:none}
}

/* ── the glow-off test ────────────────────────────────────────────────
   The brief requires the design to hold up with every glow disabled. This
   makes that testable rather than aspirational: add data-noglow to <html>
   and every decorative light goes, leaving borders, surfaces and type to
   carry the hierarchy on their own. */
html[data-noglow] *{
  box-shadow:none !important;
  filter:none !important;
  text-shadow:none !important;
}
html[data-noglow] :focus-visible{
  outline:2px solid var(--violet-orchid);outline-offset:3px;
}
/* Clipped controls keep the inset ring even here: with no glow the outset one
   is still clipped away, and "no decoration" must never mean "no focus". The
   !important is undoing the blanket box-shadow:none a few rules above, which
   is aimed at glow and would otherwise take the focus ring with it. */
html[data-noglow] .btn:focus-visible,
html[data-noglow] .dl-btn:focus-visible{
  outline:none;box-shadow:var(--focus-ring-inset) !important;
}

/* ── touch targets ────────────────────────────────────────────────────
   44px is the floor on coarse pointers. Several controls sat near 30px,
   which is a miss on a phone even when it looks fine on a desktop. */
@media(pointer:coarse){
  .btn,.btn-sm,.catchip,.roombtn,.ftab,.navdropbtn,.nav nav a{min-height:44px}
  .btn,.btn-sm{padding-top:var(--space-3);padding-bottom:var(--space-3)}
}

/* ── account chip ─────────────────────────────────────────────────────
   The signed-in member's own avatar, inline in the nav. Circular so it reads
   as a person against a bar of square voxel geometry, and ringed rather than
   glowing so it does not compete with the emblem two items to its left. */
.nav nav a.navcta.has-av{display:inline-flex;align-items:center;gap:8px}
.navav{
  width:22px;height:22px;border-radius:50%;object-fit:cover;flex:none;
  border:1px solid var(--edge-strong);background:var(--surface-obsidian);
}
.nav nav a.navcta.has-av:hover .navav{border-color:var(--violet-orchid)}
@media(max-width:1100px){
  /* Stacked nav: the avatar leads and the word still reads. */
  .nav nav a.navcta.has-av{justify-content:flex-start}
}

/* Monogram fallback. Most members have no picture, and the avatar route
   returns a bodyless 404 for them by design, so this is the common rendering
   rather than an error state — it should look deliberate. */
.navav-mono{
  display:inline-flex;align-items:center;justify-content:center;
  font-size:11px;font-weight:800;line-height:1;letter-spacing:0;
  color:var(--violet-crystal);
  background:linear-gradient(165deg,var(--violet-deep),var(--surface-obsidian));
  border-color:var(--violet-deep);
}


/* ════════════════════════════════════════════════════════════════════
   NEXUSCORE — the product surface.

   NexusCore owns CYAN within the studio palette; it does not get a palette of
   its own. That is the rule BRAND-GUIDELINES sets for products, and it is why
   the page reads as part of the studio rather than as a different site: same
   surfaces, same geometry, same type, one accent moved.

   Cyan is already the technical-metadata colour everywhere else, which makes
   it the natural product accent for a server-administration mod — versions,
   loaders, checksums and commands are most of what this page IS.
   ════════════════════════════════════════════════════════════════════ */

/* ── loader, version and status badges ────────────────────────────────
   One component, three meanings, distinguished by colour rather than by
   shape — so a scan down the page reads as a single vocabulary. */
.ncbadge{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--font-mono);font-size:var(--text-xs);
  letter-spacing:.08em;font-weight:700;white-space:nowrap;
  padding:5px 10px;border-radius:var(--radius-sm);
  border:1px solid var(--edge-strong);color:var(--text-secondary);
  background:var(--surface-obsidian);
}
.ncbadge.loader{border-color:rgba(66,200,255,.42);color:var(--technical-cyan)}
.ncbadge.version{border-color:rgba(66,200,255,.42);color:var(--technical-cyan)}
.ncbadge.java{border-color:var(--edge-strong);color:var(--text-secondary)}

/* Release and support state. Deliberately NOT the defect palette — an
   unsupported version is a fact, not an emergency. */
.ncstate{
  display:inline-flex;align-items:center;gap:6px;
  font-size:var(--text-xs);letter-spacing:var(--tracking-label);
  text-transform:uppercase;font-weight:800;
  padding:5px 10px;border-radius:var(--radius-sm);border:1px solid var(--edge-strong);
}
.ncstate.stable,.ncstate.released{color:var(--state-stable);border-color:rgba(84,224,138,.45);background:rgba(84,224,138,.08)}
.ncstate.development{color:var(--state-development);border-color:rgba(255,182,72,.45);background:rgba(255,182,72,.08)}
.ncstate.planned{color:var(--state-planned);border-color:var(--edge-strong)}
.ncstate.unsupported{color:var(--state-unsupported);border-color:rgba(255,107,107,.45);background:rgba(255,107,107,.07)}
.ncstate.unverified{color:var(--state-unverified);border-color:rgba(255,160,112,.45);background:rgba(255,160,112,.07)}
.ncstate::before{content:"";width:6px;height:6px;background:currentColor;border-radius:50%;flex:none}

/* ── downloads ────────────────────────────────────────────────────────
   A download is the most consequential control on the page, so it is the
   only place cyan is allowed to fill rather than outline. */
.dl-btn{
  border:1px solid rgba(66,200,255,.45);
  background:linear-gradient(180deg,rgba(66,200,255,.14),rgba(66,200,255,.05));
  color:var(--text-primary);
}
.dl-btn:hover{
  border-color:var(--technical-cyan);
  background:linear-gradient(180deg,rgba(66,200,255,.24),rgba(66,200,255,.10));
  box-shadow:0 0 22px rgba(66,200,255,.18);
}
.dl-btn:focus-visible{outline:none;box-shadow:var(--focus-ring-inset-cyan)}

/* Checksums are for comparing character by character, so they get monospace,
   full selectability and a surface that survives being stared at. */
.checksum,.dl-foot code{
  font-family:var(--font-mono);font-size:12px;
  color:var(--technical-cyan);background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-sm);
  padding:3px 7px;user-select:all;word-break:break-all;
}

/* ── defect rows ──────────────────────────────────────────────────────
   THE BRIEF IS EXPLICIT: known defects, unsupported versions and unfinished
   behaviour must NOT be softened into small low-contrast footnotes to make
   the project look tidier. They get a full-strength severity rule, body-sized
   text and the same weight as anything else on the page. */
.defect{
  display:grid;grid-template-columns:auto 1fr;gap:var(--space-3) var(--space-4);
  align-items:start;padding:var(--space-4) var(--space-5);
  border:1px solid var(--edge-soft-2);border-left-width:3px;
  border-radius:var(--radius-md);background:var(--surface-primary);
  margin-bottom:var(--space-3);
}
.defect.critical{border-left-color:var(--defect-critical)}
.defect.high{border-left-color:var(--defect-high)}
.defect.medium{border-left-color:var(--defect-medium)}
.defect.low{border-left-color:var(--defect-low)}
.defect .dtext{font-size:var(--text-base);color:var(--text-primary);line-height:var(--leading-snug);max-width:none}
.defect .dmeta{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-muted)}
@media(max-width:620px){.defect{grid-template-columns:1fr}}

/* ── the honest-status block ──────────────────────────────────────────
   The section that says what is unfinished and unverified. It is given the
   SAME visual weight as the feature grid, because a page that presents its
   strengths loudly and its limits quietly is not being transparent. */
.honest{
  border:1px solid rgba(255,160,112,.32);border-left:3px solid var(--state-unverified);
  background:linear-gradient(168deg,rgba(255,160,112,.06),var(--surface-obsidian));
  border-radius:var(--radius-md);padding:var(--space-6);
}
.honest h2,.honest h3{color:var(--text-primary)}
.honest p,.honest li{color:var(--text-secondary);max-width:72ch}

/* ── command explorer ─────────────────────────────────────────────────
   A long scannable table. Monospace for the command, sticky header so the
   columns stay named while scrolling, and horizontal scroll contained to the
   table rather than the page. */
.cmdrow code,.cmdrow .cmd{font-family:var(--font-mono);color:var(--technical-cyan);font-size:13px}
.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch;border-radius:var(--radius-md)}
.tablewrap table{min-width:520px;border-collapse:collapse;width:100%}
.tablewrap th{
  position:sticky;top:0;z-index:1;background:var(--surface-elevated);
  font-size:var(--text-xs);letter-spacing:var(--tracking-label);text-transform:uppercase;
  color:var(--text-muted);text-align:left;padding:var(--space-3) var(--space-4);
  border-bottom:1px solid var(--edge-base);
}
.tablewrap td{padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--divider);vertical-align:top}
.tablewrap tr:last-child td{border-bottom:none}

/* ── the product banner ───────────────────────────────────────────────
   It is artwork with its own frame and lighting; a border around it would be
   a second frame. It gets breathing room and nothing else. */
.nc-banner img,.nc-banner picture{
  width:100%;height:auto;border-radius:var(--radius-md);display:block;
}

/* A contained product lockup needs room to breathe and a surface that is not
   fighting the artwork's own dark field. */
.proj .shot.contain{background:var(--surface-obsidian)}
.proj .shot.contain img{padding:var(--space-5);object-fit:contain}
@media(max-width:700px){.proj .shot.contain img{padding:var(--space-4)}}


/* ════════════════════════════════════════════════════════════════════
   THE REMAINING PAGES — docs, forum, community, support, team, join,
   account. Written as shared component rules rather than seven bespoke
   stylesheets, because these pages are made of the same six or seven things
   and styling each page separately is how the panel drifted from the site in
   the first place.
   ════════════════════════════════════════════════════════════════════ */

/* ── documentation ────────────────────────────────────────────────────
   Deliberately quieter than the homepage. Somebody here is trying to read,
   not be impressed. */
.docs .card,.faq{background:var(--surface-primary);border-color:var(--edge-soft-2)}
.docs h2,.docs h3{margin-top:var(--space-10)}
.docs p,.docs li{max-width:var(--width-prose);color:var(--text-secondary)}
.docs li{margin-bottom:var(--space-2)}
/* Inline code is a noun inside a sentence; a block is a thing to copy. They
   should not look the same. */
:not(pre) > code{
  font-family:var(--font-mono);font-size:.92em;padding:2px 6px;
  color:var(--technical-cyan);background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-sm);
  word-break:break-word;
}
pre{
  background:var(--surface-obsidian);border:1px solid var(--edge-soft-2);
  border-left:3px solid var(--violet-deep);border-radius:var(--radius-md);
  padding:var(--space-4) var(--space-5);overflow-x:auto;
  -webkit-overflow-scrolling:touch;line-height:1.6;
}
pre code{background:none;border:0;padding:0;color:var(--text-primary);font-size:13px}
/* Numbered install steps read as a sequence rather than a list of sentences. */
.steps{counter-reset:step;list-style:none;padding:0}
.steps li{counter-increment:step;position:relative;padding-left:var(--space-10);margin-bottom:var(--space-5)}
.steps li::before{
  content:counter(step);position:absolute;left:0;top:0;
  width:26px;height:26px;display:grid;place-items:center;
  font-family:var(--font-mono);font-size:12px;font-weight:700;
  color:var(--violet-crystal);background:var(--surface-obsidian);
  border:1px solid var(--violet-deep);border-radius:var(--radius-sm);
}

/* ── forum ────────────────────────────────────────────────────────────
   A thread row is a link with a lot of metadata. The title has to win. */
.thread,.trow{
  display:block;padding:var(--space-4) var(--space-5);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-md);
  background:var(--surface-primary);margin-bottom:var(--space-3);
  text-decoration:none;transition:border-color var(--duration-fast) var(--ease-out),
                                  background var(--duration-fast) var(--ease-out);
}
.thread:hover,.trow:hover{border-color:var(--violet-primary);background:var(--surface-elevated)}
.thread .tt,.trow .tt{font-size:var(--text-md);color:var(--text-primary);font-weight:600;line-height:1.35}
.thread .tm,.trow .tm{font-size:var(--text-xs);color:var(--text-muted);margin-top:var(--space-2)}
/* A post is long-form. It gets prose measure and generous leading. */
.post .body,.post p{max-width:var(--width-prose);line-height:var(--leading-loose);color:var(--text-secondary)}
.post{border-bottom:1px solid var(--divider);padding:var(--space-6) 0}
.post:last-child{border-bottom:none}

/* ── community and support route cards ───────────────────────────────
   Each card is one route to a human. The distinguishing thing is WHICH
   route, so the label leads and the description supports it. */
/* NOT display:block. This rule used to set it, which silently undid the
   .sup{display:flex} above and produced two visible bugs at once on
   /support/: the email chip dropped below the text instead of sitting
   opposite it, and — because it stopped being a flex item, so was no longer
   blockified — its 15px vertical padding bled up over the paragraph and
   overlapped the last line. One override, two symptoms.
   `.route` was in this selector and matches nothing in any page; dropped. */
.sup{
  padding:var(--space-5);border-radius:var(--radius-md);
  border:1px solid var(--edge-soft-2);border-left:3px solid var(--edge-strong);
  background:var(--surface-primary);text-decoration:none;
}
.sup:hover{border-left-color:var(--violet-primary);background:var(--surface-elevated)}
.sup h3{margin:0 0 var(--space-2);font-size:var(--text-md);color:var(--text-primary)}
.sup p{margin:0;font-size:var(--text-sm);color:var(--text-secondary);line-height:var(--leading-snug)}
/* A security route is not a bug report. It says so before it is clicked. */
.sup.security{border-left-color:var(--status-warning)}

/* ── forms, one system everywhere ─────────────────────────────────────
   Sign-in, signup, applications, profile, email, password, support. */
label{display:block;font-size:var(--text-xs);letter-spacing:var(--tracking-label);
      text-transform:uppercase;color:var(--text-muted);font-weight:800;margin:0 0 7px}
.fld{
  width:100%;padding:12px 14px;font-family:inherit;font-size:var(--text-base);
  color:var(--text-primary);background:var(--surface-obsidian);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-md);
  margin-bottom:var(--space-4);
  transition:border-color var(--duration-fast) var(--ease-out),
             box-shadow var(--duration-fast) var(--ease-out);
}
.fld::placeholder{color:var(--text-faint)}
.fld:disabled{opacity:.55;cursor:not-allowed}
.fld[aria-invalid="true"]{border-color:var(--status-error);background:rgba(255,107,107,.06)}
/* A hint sits with its field, not adrift at the end of the form. */
.fieldhint{margin:-10px 0 var(--space-4);font-size:var(--text-xs);color:var(--text-muted)}

/* ── empty, loading and error states ──────────────────────────────────
   Three different things that used to look identical. "Nothing here yet" and
   "we could not find out" must never be the same message. */
.state{
  padding:var(--space-8) var(--space-5);text-align:center;
  border:1px dashed var(--edge-base);border-radius:var(--radius-md);
  color:var(--text-muted);font-size:var(--text-sm);
}
.state.error{border-style:solid;border-color:rgba(255,107,107,.4);
             background:rgba(255,107,107,.05);color:#FFD5D5}
.state.loading{border-style:solid;border-color:var(--edge-soft-2)}
.state.loading::after{
  content:"";display:inline-block;width:12px;height:12px;margin-left:9px;
  vertical-align:-2px;border:2px solid var(--edge-strong);
  border-top-color:var(--violet-orchid);border-radius:50%;
  animation:spin .8s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){.state.loading::after{animation:none}}

/* ── account ──────────────────────────────────────────────────────────
   Usability and trust over decoration: this is where somebody changes a
   password or an address, and cinematic surfaces do not help them. */
#dash .panel,#dash fieldset{background:var(--surface-primary);border-color:var(--edge-soft-2)}
#dash h2{font-size:1.05rem;margin-top:0}
.hint{font-size:var(--text-sm);color:var(--text-muted);line-height:var(--leading-snug)}

/* ── tables anywhere ──────────────────────────────────────────────────
   Contained scroll, so a wide table never pushes the page sideways. */
table{border-collapse:collapse;width:100%}
th{text-align:left;font-size:var(--text-xs);letter-spacing:var(--tracking-label);
   text-transform:uppercase;color:var(--text-muted);font-weight:800}
td,th{padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--divider)}

/* ── narrow screens ───────────────────────────────────────────────────
   The mobile layout should look intentional rather than merely reflowed. */
@media(max-width:640px){
  .wrap{padding-left:var(--space-4);padding-right:var(--space-4)}
  .askcard{width:calc(100vw - 24px);padding:var(--space-5)}
  .askrow{flex-direction:column}
  .askrow .btn{width:100%;justify-content:center}
  .steps li{padding-left:var(--space-8)}
  h1,.ph{font-size:clamp(1.7rem,8vw,2.3rem)}
}

/* ── the studio strip ─────────────────────────────────────────────────
   Three real destinations, sitting between the hero and the projects so the
   page reads as a studio with a community rather than as one mod's landing
   page. Quieter than a project card on purpose — it is orientation, not a
   product pitch. */
#studio{padding:var(--space-12) 0 0}
.studiogrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:var(--space-4)}
.studiocard{
  display:block;padding:var(--space-5) var(--space-6);text-decoration:none;
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-md);
  background:var(--surface-primary);
  transition:border-color var(--duration-base) var(--ease-out),
             background var(--duration-base) var(--ease-out);
}
.studiocard:hover{border-color:var(--violet-primary);background:var(--surface-elevated)}
.studiocard .sk{display:block;font-size:var(--text-xs);letter-spacing:var(--tracking-label);
                text-transform:uppercase;color:var(--violet-orchid);font-weight:800;margin-bottom:var(--space-2)}
.studiocard b{display:block;font-family:var(--font-display);font-size:var(--text-md);
              color:var(--text-primary);margin-bottom:var(--space-2)}
.studiocard p{margin:0;font-size:var(--text-sm);color:var(--text-secondary);
              line-height:var(--leading-snug);max-width:none}


/* ════════════════════════════════════════════════════════════════════
   PRESENCE — the small amount of life the interface is allowed.

   Everything here is bounded by three rules the brief sets and the palette
   depends on: no constant motion, nothing that shifts layout, and all of it
   gone under prefers-reduced-motion. The site still has to read correctly
   with data-noglow, so none of this carries meaning on its own.
   ════════════════════════════════════════════════════════════════════ */

/* ── staff badges glow, and only staff badges ─────────────────────────
   The forum badge catalogue is fifteen entries; most are community roles and
   a handful are actual studio staff. Lighting all fifteen would make the
   glow mean nothing, so it marks the distinction it is there to mark. The
   colour still comes from the badge's own tone — the glow adds emphasis, not
   a second colour scheme. */
.badge.staff,.badge[data-tone="royal"],.badge[data-tone="gold"],.badge[data-tone="rose"]{
  position:relative;
  box-shadow:0 0 12px -2px currentColor, inset 0 0 0 1px currentColor;
  border-color:currentColor;
}
.badge[data-tone="royal"]{animation:staffpulse 3.4s var(--ease-in-out) infinite}
@keyframes staffpulse{
  0%,100%{box-shadow:0 0 10px -3px currentColor, inset 0 0 0 1px currentColor}
  50%    {box-shadow:0 0 18px -1px currentColor, inset 0 0 0 1px currentColor}
}
/* The owner-tier badge earns a slow sheen. One element on the page, not all
   of them — that is the difference between a highlight and a light show. */
.badge[data-tone="royal"]::after{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:linear-gradient(105deg,transparent 42%,rgba(255,255,255,.30) 50%,transparent 58%);
  background-size:260% 100%;
  animation:sheen 5.5s var(--ease-in-out) infinite;
}
@keyframes sheen{0%,62%{background-position:190% 0}100%{background-position:-60% 0}}

/* ── arrival ──────────────────────────────────────────────────────────
   Sections settle in once, on first sight. `.rv` is already in the markup on
   the sections that want it, and site.js already observes it — this is the
   motion it was waiting for. Transform and opacity only, so nothing reflows
   and nothing costs layout. */
/* Reveal motion is defined once, up in the reveal section, gated on
   html.rv-ready so content never depends on it. */

/* ── feedback ─────────────────────────────────────────────────────────
   Buttons acknowledge a press. 60ms, no size change, no layout shift. */
.btn:active,.roombtn:active,.catchip:active{transform:translateY(1px)}

@media(prefers-reduced-motion:reduce){
  .badge[data-tone="royal"],.badge[data-tone="royal"]::after{animation:none}
  html.rv-ready .rv{opacity:1;transform:none;transition:none}
  .btn:active,.roombtn:active,.catchip:active{transform:none}
}
/* And under the glow-off switch, the staff badge keeps its border and loses
   its light — the distinction survives without the effect. */
html[data-noglow] .badge[data-tone="royal"]::after{display:none}

/* An earlier, complete .sfxbtn implementation lived here — 34px, its own
   --wave mask, its own hover/on states — and was shipped alongside the one
   further down without either noticing the other. The two fought: the later
   rule won size and colour, the earlier one still contributed margin-left and
   the .on background, so the pressed state got its border from one rule and
   its fill from the other. One component, one rule. The surviving definition
   is in the interface-sound section below and uses the shared icon set. */


/* ════════════════════════════════════════════════════════════════════
   ICONS — one set, masked from inline SVG.

   CSS masks rather than <img> or an icon font: the glyph takes `currentColor`,
   so an icon is always the colour of the thing it belongs to and can never
   drift from it. No network request, no font file, no FOIT, and nothing to
   keep in sync. The whole set below costs a few hundred bytes inside the
   stylesheet that was already being downloaded.

   THEY ARE DECORATION AND ARE MARKED AS SUCH. Every one is aria-hidden by
   construction — a ::before on an element whose text already says what it is.
   An icon that carries meaning on its own is an unlabelled control, which the
   brief bars and which a screen reader cannot announce.
   ════════════════════════════════════════════════════════════════════ */
:root{
  --ic-people:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 20v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2'/%3E%3Ccircle cx='9' cy='7' r='3.2'/%3E%3Cpath d='M22 20v-2a4 4 0 00-3-3.9M16 3.6a4 4 0 010 7.8'/%3E%3C/svg%3E");
  --ic-chat:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 11.5a8 8 0 01-8.5 8 9 9 0 01-3.9-.9L3 20l1.4-4.2A8 8 0 0112.5 3.5a8 8 0 018.5 8z'/%3E%3C/svg%3E");
  --ic-cube:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16V8l-9-5-9 5v8l9 5 9-5z'/%3E%3Cpath d='M3.3 7.5L12 12.5l8.7-5M12 22V12.5'/%3E%3C/svg%3E");
  --ic-bug:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='8' y='6' width='8' height='13' rx='4'/%3E%3Cpath d='M8 12H3M21 12h-5M8.5 7.5L6 5M15.5 7.5L18 5M8.5 17L6 19.5M15.5 17L18 19.5'/%3E%3C/svg%3E");
  --ic-shield:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 22s8-3.6 8-9.6V5.5L12 2.5 4 5.5v6.9C4 18.4 12 22 12 22z'/%3E%3C/svg%3E");
  --ic-book:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 4.5A2.5 2.5 0 016.5 2H20v18H6.5A2.5 2.5 0 004 22z'/%3E%3Cpath d='M4 17.5A2.5 2.5 0 016.5 15H20'/%3E%3C/svg%3E");
  --ic-spark:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2.5l2.3 6.2 6.2 2.3-6.2 2.3L12 19.5 9.7 13.3 3.5 11l6.2-2.3z'/%3E%3C/svg%3E");
  --ic-mail:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='4.5' width='19' height='15' rx='2'/%3E%3Cpath d='M3 6l9 6.5L21 6'/%3E%3C/svg%3E");
  --ic-bulb:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.2 17.5h5.6'/%3E%3Cpath d='M10 20.5h4'/%3E%3Cpath d='M12 2.8a6.2 6.2 0 00-3.6 11.2v1.5h7.2V14A6.2 6.2 0 0012 2.8z'/%3E%3C/svg%3E");
  --ic-ticket:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 9V6.6a1.1 1.1 0 011.1-1.1h15.8A1.1 1.1 0 0121 6.6V9a3 3 0 000 6v2.4a1.1 1.1 0 01-1.1 1.1H4.1A1.1 1.1 0 013 17.4V15a3 3 0 000-6z'/%3E%3Cpath d='M9.5 5.5v13'/%3E%3C/svg%3E");
  --ic-megaphone:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 10v4a1.5 1.5 0 001.5 1.5h1.8l7.7 4.8V3.7L6.8 8.5H5A1.5 1.5 0 003.5 10z'/%3E%3Cpath d='M17.8 8.8a4.5 4.5 0 010 6.4'/%3E%3Cpath d='M7 15.8V20a1 1 0 001 1h1.6a1 1 0 001-1v-2.3'/%3E%3C/svg%3E");
  --ic-bolt:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.6 2.3L4.4 13.6h6.1l-1.1 8.1 9.2-11.3h-6.1z'/%3E%3C/svg%3E");
  --ic-speaker:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9.4v5.2a1 1 0 001 1h2.6l4.9 3.8V4.6L7.6 8.4H5a1 1 0 00-1 1z'/%3E%3Cpath d='M16.2 9.1a4.4 4.4 0 010 5.8'/%3E%3Cpath d='M19 6.6a8 8 0 010 10.8'/%3E%3C/svg%3E");
  --ic-check:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.2 6.2L9.4 17 3.8 11.4'/%3E%3C/svg%3E");
  --ic-terminal:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='4' width='19' height='16' rx='2'/%3E%3Cpath d='M6.8 9.6l3 2.4-3 2.4'/%3E%3Cpath d='M12.6 14.8h4.8'/%3E%3C/svg%3E");
  --ic-key:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7.6' cy='15.4' r='4.1'/%3E%3Cpath d='M10.5 12.5L20.5 2.5'/%3E%3Cpath d='M17.6 5.4l2.6 2.6'/%3E%3Cpath d='M14.8 8.2l2.4 2.4'/%3E%3C/svg%3E");
  --ic-repo:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='6.5' cy='5.8' r='2.6'/%3E%3Ccircle cx='6.5' cy='18.2' r='2.6'/%3E%3Ccircle cx='17.5' cy='9' r='2.6'/%3E%3Cpath d='M17.5 11.6c0 3.6-3.4 4.6-6.4 4.9'/%3E%3Cpath d='M6.5 8.4v7.2'/%3E%3C/svg%3E");
  --ic-news:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 5.5h13v13H3.5z'/%3E%3Cpath d='M16.5 9.5h3a1 1 0 011 1v6a1.5 1.5 0 01-3 0V9.5z'/%3E%3Cpath d='M6.5 9h7M6.5 12h7M6.5 15h4.5'/%3E%3C/svg%3E");
  --ic-map:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 4.2L3.5 6.6v13.2L9 17.4l6 2.4 5.5-2.4V4.2L15 6.6z'/%3E%3Cpath d='M9 4.2v13.2M15 6.6v13.2'/%3E%3C/svg%3E");
  --ic-list:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8.6 6.5h11.9M8.6 12h11.9M8.6 17.5h11.9'/%3E%3Cpath d='M4 5.6v1.8M4 11.1v1.8M4 16.6v1.8'/%3E%3C/svg%3E");
  --ic-refresh:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.5 12a8.5 8.5 0 01-14.6 5.9'/%3E%3Cpath d='M3.5 12A8.5 8.5 0 0118.1 6.1'/%3E%3Cpath d='M18.5 2.5v4h-4'/%3E%3Cpath d='M5.5 21.5v-4h4'/%3E%3C/svg%3E");
  --ic-ruler:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 20.5h17L3.5 3.5z'/%3E%3Cpath d='M7.5 16.6v-3.2M11.4 16.6v-6.2M15.3 16.6v-2.4'/%3E%3C/svg%3E");
  --ic-flask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 3v6.2L4.6 17.8A2 2 0 006.3 21h11.4a2 2 0 001.7-3.2L14.5 9.2V3'/%3E%3Cpath d='M8 3h8'/%3E%3Cpath d='M7.4 15.4h9.2'/%3E%3C/svg%3E");
  --ic-ban:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M5.6 5.6l12.8 12.8'/%3E%3C/svg%3E");
  --ic-mute:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9.4v5.2a1 1 0 001 1h2.6l4.9 3.8V4.6L7.6 8.4H5a1 1 0 00-1 1z'/%3E%3Cpath d='M16.4 9.6l5.1 4.8M21.5 9.6l-5.1 4.8'/%3E%3C/svg%3E");
  --ic-bell:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 8.6a6 6 0 10-12 0c0 6-2.2 7.6-2.2 7.6h16.4S18 14.6 18 8.6z'/%3E%3Cpath d='M13.7 20.2a2 2 0 01-3.4 0'/%3E%3C/svg%3E");
  --ic-compass:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M15.6 8.4l-2.1 5.1-5.1 2.1 2.1-5.1z'/%3E%3C/svg%3E");
  --ic-scroll:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 4.5h11a2 2 0 012 2v11a2 2 0 002 2H8a2 2 0 01-2-2z'/%3E%3Cpath d='M6 4.5a2 2 0 00-2 2v2h2'/%3E%3Cpath d='M9.5 9h6M9.5 12.5h6M9.5 16h4'/%3E%3C/svg%3E");
  --ic-monitor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.5' y='3.5' width='19' height='13' rx='2'/%3E%3Cpath d='M8.5 20.5h7'/%3E%3Cpath d='M12 16.5v4'/%3E%3C/svg%3E");
}
.ic,[data-ic]::before{
  content:"";display:inline-block;width:1em;height:1em;flex:none;
  background:currentColor;vertical-align:-.13em;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:contain;mask-size:contain;
}
[data-ic="people"]::before{-webkit-mask-image:var(--ic-people);mask-image:var(--ic-people)}
[data-ic="chat"]::before{-webkit-mask-image:var(--ic-chat);mask-image:var(--ic-chat)}
[data-ic="cube"]::before{-webkit-mask-image:var(--ic-cube);mask-image:var(--ic-cube)}
[data-ic="bug"]::before{-webkit-mask-image:var(--ic-bug);mask-image:var(--ic-bug)}
[data-ic="shield"]::before{-webkit-mask-image:var(--ic-shield);mask-image:var(--ic-shield)}
[data-ic="book"]::before{-webkit-mask-image:var(--ic-book);mask-image:var(--ic-book)}
[data-ic="spark"]::before{-webkit-mask-image:var(--ic-spark);mask-image:var(--ic-spark)}
[data-ic="mail"]::before{-webkit-mask-image:var(--ic-mail);mask-image:var(--ic-mail)}
[data-ic="bulb"]::before{-webkit-mask-image:var(--ic-bulb);mask-image:var(--ic-bulb)}
[data-ic="ticket"]::before{-webkit-mask-image:var(--ic-ticket);mask-image:var(--ic-ticket)}
[data-ic="megaphone"]::before{-webkit-mask-image:var(--ic-megaphone);mask-image:var(--ic-megaphone)}
[data-ic="bolt"]::before{-webkit-mask-image:var(--ic-bolt);mask-image:var(--ic-bolt)}
[data-ic="speaker"]::before{-webkit-mask-image:var(--ic-speaker);mask-image:var(--ic-speaker)}
[data-ic="check"]::before{-webkit-mask-image:var(--ic-check);mask-image:var(--ic-check)}
[data-ic="terminal"]::before{-webkit-mask-image:var(--ic-terminal);mask-image:var(--ic-terminal)}
[data-ic="key"]::before{-webkit-mask-image:var(--ic-key);mask-image:var(--ic-key)}
[data-ic="repo"]::before{-webkit-mask-image:var(--ic-repo);mask-image:var(--ic-repo)}
[data-ic="news"]::before{-webkit-mask-image:var(--ic-news);mask-image:var(--ic-news)}
[data-ic="map"]::before{-webkit-mask-image:var(--ic-map);mask-image:var(--ic-map)}
[data-ic="list"]::before{-webkit-mask-image:var(--ic-list);mask-image:var(--ic-list)}
[data-ic="refresh"]::before{-webkit-mask-image:var(--ic-refresh);mask-image:var(--ic-refresh)}
[data-ic="ruler"]::before{-webkit-mask-image:var(--ic-ruler);mask-image:var(--ic-ruler)}
[data-ic="flask"]::before{-webkit-mask-image:var(--ic-flask);mask-image:var(--ic-flask)}
[data-ic="ban"]::before{-webkit-mask-image:var(--ic-ban);mask-image:var(--ic-ban)}
[data-ic="mute"]::before{-webkit-mask-image:var(--ic-mute);mask-image:var(--ic-mute)}
[data-ic="bell"]::before{-webkit-mask-image:var(--ic-bell);mask-image:var(--ic-bell)}
[data-ic="compass"]::before{-webkit-mask-image:var(--ic-compass);mask-image:var(--ic-compass)}
[data-ic="scroll"]::before{-webkit-mask-image:var(--ic-scroll);mask-image:var(--ic-scroll)}
[data-ic="monitor"]::before{-webkit-mask-image:var(--ic-monitor);mask-image:var(--ic-monitor)}

/* On a card the icon leads the label, at the label's own size and colour. */
.studiocard .sk{display:flex;align-items:center;gap:7px}
.studiocard .sk::before{font-size:1.15em}
.studiocard:hover .sk{color:var(--violet-crystal)}
.sup h3{display:flex;align-items:center;gap:9px}
.sup h3::before{color:var(--violet-orchid);font-size:1.05em}
.sup.security h3::before{color:var(--status-warning)}

/* The hexagon tiles previously held OS emoji, which render differently on every
   platform and cannot take a brand colour. The tile is unchanged — only the
   glyph inside is now a masked brand icon, so it inherits the palette and one
   consistent stroke weight. The heading beside it carries the meaning; these
   are decorative pseudo-elements and stay out of the accessibility tree. */
.hex[data-ic]{color:var(--violet-crystal)}
.hex[data-ic]::before{font-size:19px}
.feat .hex[data-ic]{color:var(--technical-cyan)}
.card:hover .hex[data-ic]{color:var(--ink)}

/* ─── visually hidden ───
   Available to screen readers, absent from the page. uisound.js was already
   emitting .vh and nothing defined it, so the toggle's label rendered as loose
   wrapping text in the header. */
.vh{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* display:none, once, for everybody.
   Eight pages each carried their own `.hidden{display:none}` inside a local
   <style> block, which is fine right up until one of those blocks is removed —
   at which point the page keeps using the class and nothing hides. The
   application form on /join/, /hire/ and the two apply pages is gated with it,
   so a missing rule does not degrade to a cosmetic problem: it shows a
   signed-out visitor a form the script has decided they may not use. */
.hidden{display:none !important}

/* ─── interface-sound toggle ───
   A quiet square in the nav. It states its state three ways — the glyph, the
   title, and aria-pressed — because a speaker icon alone is ambiguous about
   whether it means "sound is on" or "press to turn sound on". */
.sfxbtn{
  order:1;   /* after the nav on desktop; see the burger override below */
  width:38px;height:38px;flex:none;display:grid;place-items:center;margin-right:8px;
  background:transparent;border:1px solid var(--edge-soft-2);border-radius:var(--radius-md);
  color:var(--text-muted);cursor:pointer;
  transition:color var(--duration-fast) var(--ease-out),
             border-color var(--duration-fast) var(--ease-out),
             background var(--duration-fast) var(--ease-out);
}
.sfxbtn:hover{color:var(--violet-crystal);border-color:var(--violet-primary);background:rgba(139,92,255,.08)}
.sfxbtn:focus-visible{outline:none;border-color:var(--violet-primary);box-shadow:var(--focus-ring)}
.sfxbtn.on{color:var(--violet-orchid);border-color:var(--edge-base)}
.sfxicon{
  content:"";display:block;width:19px;height:19px;background:currentColor;
  -webkit-mask-image:var(--ic-mute);mask-image:var(--ic-mute);
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:contain;mask-size:contain;
}
.sfxbtn.on .sfxicon{-webkit-mask-image:var(--ic-speaker);mask-image:var(--ic-speaker)}
@media(pointer:coarse){.sfxbtn{width:44px;height:44px}}

/* ── presence on the team page ──────────────────────────────────────────
   A state, never an activity. What somebody is working on is written for
   colleagues and stays in the panel; a visitor gets "is this person around",
   which is the only part that helps them decide whether to wait for a reply.

   Rendered only for people who opted in — no chip at all is different from an
   Offline chip, and the difference is somebody's consent. */
.tstatus{
  display:inline-flex;align-items:center;gap:6px;vertical-align:middle;
  margin-left:10px;padding:3px 9px;
  font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;font-weight:800;
  color:var(--text-muted);
  border:1px solid var(--edge-soft-2);border-radius:var(--radius-pill);
  background:var(--surface-obsidian);
}
.tdot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--edge-strong)}
.tstatus.on{color:var(--status-success);border-color:rgba(84,224,138,.45);background:rgba(84,224,138,.10)}
.tstatus.on .tdot{background:var(--status-success);box-shadow:0 0 8px rgba(84,224,138,.75);
                  animation:tpulse 2.4s ease-in-out infinite}
@keyframes tpulse{0%,100%{opacity:1}50%{opacity:.45}}
@media(prefers-reduced-motion:reduce){.tstatus.on .tdot{animation:none}}

/* ─── Promotional slots ────────────────────────────────────────────────
   The studio's own promos on the home page, edited from the admin panel.
   No third-party network, so there is no iframe to size around and no
   consent banner to make room for — these are ordinary cards. */
#promos[hidden] { display: none; }

/* THE HTML `hidden` ATTRIBUTE MUST ACTUALLY HIDE, and a single-purpose rule
   for #promos was not enough. Any rule that sets `display` — .card{display:flex}
   is the one that bit — beats the browser's default `[hidden]{display:none}`
   on specificity, so an element shipped `hidden` renders anyway until a script
   removes it.
   That is exactly the failure the data-c-reveal note in site.js was written
   about: /community/ shipped two "Vote for us" cards VISIBLE, with dead
   href="#" buttons, for as long as it took /api/content to answer — and for
   ever if the request failed or JavaScript did not run. `!important` is right
   here and almost nowhere else: `hidden` is not a style, it is a statement
   that the element is not part of the page yet. */
[hidden] { display: none !important; }

.promogrid {
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.promo {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 18px;
  border: 1px solid var(--edge-base);
  border-radius: 14px;
  background: var(--surface-primary);
  color: inherit;
  text-decoration: none;
  transition: border-color .18s ease, transform .18s ease, background .18s ease;
}
/* Only a promo that GOES somewhere reacts to the pointer. The renderer emits a
   <div> rather than an <a> when there is no href, so this cannot light up a
   card that does nothing. */
a.promo:hover, a.promo:focus-visible {
  border-color: var(--violet-orchid);
  background: var(--surface-elevated);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .promo { transition: none; }
  a.promo:hover, a.promo:focus-visible { transform: none; }
}

.promoimg {
  width: 48px; height: 48px;
  flex: 0 0 48px;
  object-fit: contain;
  border-radius: 10px;
}

.promotext { min-width: 0; }
.promotext h3 {
  margin: 0 0 6px;
  font-size: 1rem;
  letter-spacing: .02em;
}
.promotext p {
  margin: 0;
  font-size: .9rem;
  line-height: 1.5;
  color: var(--text-secondary);
  /* Long words in a narrow card must not push the grid wide. */
  overflow-wrap: anywhere;
}
.promocta {
  display: inline-block;
  margin-top: 10px;
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--violet-orchid);
}

/* ─── paid placements ───────────────────────────────────────────────────
   A SPONSOR CARD MUST NOT BE MISTAKABLE FOR THE STUDIO'S OWN.

   Everything on this site that holds content — .card, .promo, .teamcard,
   .proj — shares one border, one surface and one hover lift. That consistency
   is the problem here: an unmarked paid card dropped into a .promogrid reads
   as something MWT Studios is telling you, which is the precise
   misrepresentation the disclosure exists to prevent. The label carries the
   legal duty; these rules carry the visual one, so the two agree.

   The differences are deliberately quiet — a dashed edge, a flatter surface,
   no lift on hover. Non-invasive was the requirement, and a slot that shouted
   would fail it just as badly as a popup. Quiet is not the same as hidden. */
.adslot{padding:0 0 var(--step)}
.adslot .promogrid{margin-top:14px}
.adkick{color:var(--dim)}          /* not --lava-hi: this band is not ours */

/* ---- a written section on an otherwise app-shaped page ----
   Static prose for pages whose real content is drawn by JavaScript or sits
   behind a sign-in, so that a crawler — and a visitor who is not signed in —
   gets something to read. Used by /products/ (27 server-rendered words around
   two JS-filled divs) and /hire/ (a heading and a gate panel). Styled as a reading column rather than
   the full band: 68ch is the measure the rest of the site's body copy uses, and
   a 1160px line of 14px text is not something anybody reads.

   --ink for the paragraphs, deliberately. --faint is for text a reader may skip
   and this is the page's actual content; the stylesheet already over-uses
   --faint for prose and this is not the place to add to that. */
.pagenote{max-width:68ch;margin:34px 0 40px}
.pagenote h2{
  font-size:clamp(19px,2.4vw,23px);
  line-height:1.25;
  margin:0 0 14px;
  color:var(--ink);
}
.pagenote h3{
  font-size:13px;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--lava-hi);
  margin:26px 0 8px;
}
.pagenote p{
  margin:0 0 14px;
  font-size:14.5px;
  line-height:1.75;
  color:var(--ink);
}
.pagenote a{color:var(--vio-hi)}

@media(max-width:640px){
  .pagenote{margin:26px 0 32px}
}

/* ---- ad unit formats ----
   Three kinds of unit now share this band, and they want different room. The
   class is set by site.js from the placement's format so the stylesheet never
   has to read Google's own data-ad-* attributes, which are theirs to rename.

   RESERVED HEIGHT, NOT A FIXED ONE. min-height stops the page reflowing when
   an ad arrives a second after the text; it does not cap the unit, because
   Google sizes these itself and a hard height on a responsive unit is how you
   get a scrollbar inside an iframe. The values are the shortest each format
   is worth showing at, not the tallest they get. */
.adunit{display:block}
.adunit-display{min-height:100px}

/* In-article sits in a measure of prose, so it is centred and narrowed to
   roughly the reading column rather than the band's full width. */
.adunit-article{min-height:120px;max-width:66ch;margin-inline:auto}

/* Multiplex is a grid of cards and looks cramped below about this. It is the
   one format that wants the whole band. */
.adunit-multiplex{min-height:280px}

/* An unfilled unit still occupies its reserved height, which on a page with no
   inventory is a band of nothing. Google sets this attribute once it has asked
   and been told there is no ad, which is exactly when the space stops being
   worth holding. */
ins.adsbygoogle[data-ad-status="unfilled"]{display:none!important}

/* ---- the ad blocker note ----
   Bottom-corner, not a modal, not an overlay, and it never covers content: the
   brief was that a blocked reader keeps the whole site, so this is a card that
   sits beside the page rather than in front of it. On a narrow screen it spans
   the width because a 380px card on a 360px phone is a horizontal scrollbar. */
.adnote{
  position:fixed;
  z-index:60;
  right:16px;
  bottom:16px;
  max-width:min(380px,calc(100vw - 32px));
  padding:16px 18px;
  /* --panel and --edge, which are the site's own aliases. An earlier version
     of this said var(--card) and var(--line); neither token exists, so the
     card rendered with a transparent background and no border — floating text
     over whatever it happened to be above. CSS does not warn about an unknown
     custom property, it just resolves to nothing, which is why this was found
     by reading the computed style in a browser rather than by looking at it. */
  background:var(--panel-hi);
  border:1px solid var(--edge);
  border-radius:12px;
  box-shadow:0 12px 32px rgba(0,0,0,.34);
  /* Starts down and transparent; .is-in lifts it. Both states are declared so
     the transition has somewhere to come from. */
  opacity:0;
  transform:translateY(12px);
  transition:opacity .28s ease,transform .28s ease;
}
.adnote.is-in{opacity:1;transform:none}
.adnote.is-going{opacity:0;transform:translateY(12px)}

.adnotetxt{
  margin:0 0 12px;
  font-size:13.5px;
  line-height:1.55;
  /* --ink, deliberately. This is body copy somebody is being asked to read and
     act on; --faint is for things a reader may skip, and the site already
     overuses it for prose it should not. */
  color:var(--ink);
}
.adnoterow{display:flex;justify-content:flex-end}

/* The chamfer on .btn clips an outline AND an outset shadow, so an inset ring
   is the only one that renders. Measured — see the note on --focus-ring-inset. */
.adnoteok:focus-visible{outline:none;box-shadow:var(--focus-ring-inset)}

@media (prefers-reduced-motion:reduce){
  .adnote{transition:none}
}

@media (max-width:480px){
  .adnote{right:12px;left:12px;bottom:12px;max-width:none}
}

.adcard{
  border-style:dashed;
  background:transparent;
}
/* No translateY. The lift is the site's "this is worth clicking" gesture and
   it is not on loan to an advertiser; the border and surface still respond so
   the card is plainly interactive. */
a.adcard:hover,a.adcard:focus-visible{
  border-color:var(--vio);
  background:var(--surface-primary);
  transform:none;
}

/* THE DISCLOSURE. Small, but never smaller or fainter than the body text it
   sits above — var(--dim) is the floor, and the FTC's prominence factor is the
   reason there is a floor at all rather than a judgement call. Block display
   so it occupies its own line above the title instead of being read as part
   of it. */
.adtag{
  display:block;
  margin:0 0 8px;
  font-size:10.5px;
  letter-spacing:.28em;
  text-transform:uppercase;
  font-weight:700;
  color:var(--dim);
}

.adnote{
  margin:14px 0 0;
  font-size:.8rem;
  line-height:1.55;
  color:var(--dim);
}

@media(prefers-reduced-motion:reduce){
  a.adcard:hover,a.adcard:focus-visible{transform:none}
}

/* ─── consent banner ────────────────────────────────────────────────────
   A STRIP, NOT A MODAL. It does not cover the page, does not block scrolling,
   and does not trap focus — the site is fully readable with it open, and a
   visitor who ignores it forever loses nothing but an advert. The pattern this
   deliberately avoids is the full-screen interstitial that holds content
   hostage, which is the reason these things are hated and, increasingly, the
   reason regulators look at them.

   ONLY EVER RENDERED where consent is actually required, and only when ads are
   switched on. With ads off — the shipped state — this CSS styles nothing. */
.consentbar{
  position:fixed;left:0;right:0;bottom:0;z-index:60;
  background:var(--surface-elevated,#17131F);
  border-top:1px solid var(--edge-base,#2A2637);
  box-shadow:0 -8px 32px rgba(0,0,0,.45);
  padding:14px 18px;
  /* Clear of an iPhone's home indicator. */
  padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));
}
.consentwrap{
  max-width:1100px;margin:0 auto;
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
}
.consenttext{
  margin:0;flex:1 1 340px;min-width:0;
  font-size:13px;line-height:1.6;color:var(--text-secondary,#B8B0C7);
}
.consentlink{
  font-size:12.5px;color:var(--violet-orchid,#A56DFF);
  text-decoration:underline;text-underline-offset:2px;white-space:nowrap;
}
.consentbtns{display:flex;gap:10px;flex:0 0 auto}
.consentbtn{
  font:inherit;font-size:13px;font-weight:600;letter-spacing:.02em;
  padding:9px 18px;border-radius:999px;cursor:pointer;
  border:1px solid var(--edge-base,#2A2637);
  background:transparent;color:var(--text-secondary,#B8B0C7);
  transition:border-color .15s ease,background .15s ease;
}
/* EQUAL WEIGHT, on purpose. Making the refusal quieter than the acceptance is
   the dark pattern these rules exist to stop, and it is not worth an ad. */
.consentbtn:hover,.consentbtn:focus-visible{border-color:var(--violet-orchid,#A56DFF)}
.consentbtn.is-yes{
  background:var(--violet-orchid,#A56DFF);border-color:var(--violet-orchid,#A56DFF);
  color:#140f1d;
}
@media(max-width:620px){
  .consentwrap{gap:12px}
  .consentbtns{width:100%}
  .consentbtn{flex:1 1 auto}
}
@media(prefers-reduced-motion:reduce){
  .consentbtn{transition:none}
}

/* ─── release health line ───
   One editable sentence at the top of Honest Status, for the hour after
   something breaks — before a changelog exists and before the generated defect
   table can be rebuilt.

   :empty IS THE MECHANISM, not a tidy-up. The element ships in the markup and
   is filled by site.js only when there is something to say, so with nothing
   wrong it occupies no space and there is no flash of an empty band before the
   script runs. No JavaScript is needed for the normal case. */
.healthline:empty{display:none}
/* Same precedent: a caption that exists only when there is something to say.
   Empty is the default, and an empty element must not leave a gap. */
.filmnote:empty{display:none}
.healthline{
  margin:0 0 20px;padding:12px 16px;
  border:1px solid var(--edge-base);border-left-width:3px;border-radius:10px;
  background:var(--surface-primary);
  font-size:13.5px;line-height:1.65;color:var(--text-secondary);
}
/* Colour is the ONLY thing the level changes. The sentence carries the meaning;
   a badge that disagrees with its own text is worse than no badge. */
.healthline.is-warn{border-left-color:var(--status-warning,#f0b45a)}
.healthline.is-down{border-left-color:var(--status-error,#FF6B6B)}

/* ── THE SMALLEST LABELS, ON THE SMALLEST SCREENS ──────────────────────
   The label system runs small on purpose — uppercase, heavily tracked, 9.5 to
   10.5px — and at desktop reading distance that reads as a label. Measured at
   375px it is a phone at arm's length, and 9.5px uppercase is the size people
   pinch to read: product price and status chips, team status and roles, the
   shared-inbox label, forum pills, kickers and crumbs. Raised to an 11px floor
   BELOW 640px ONLY — the desktop design is untouched. Last in the file on
   purpose, so it outranks each label's own rule without extra specificity. */
@media(max-width:640px){
  .kick,.crumb,.badge,.sev,.pstatus,.pprice,.pcat,.adtag,.tstatus,.tsharedlab,
  .teamcard .trole,.stat span,.fgrid h4,.strip,
  .pwrole,.navgrouph,.ovpanelh,.crole,.cmsg .when,.appform label,.roomform label,
  .grpform label,.pkind,.partnerkind,.chatroomshead{font-size:11px}
}

/* PRODUCT NAMES GET THE WHOLE LINE ON A PHONE. The name and its price and
   status chips shared one row that never wraps; measured at 375px the chips
   took 123-169px and left the name 88-110px, so "NexusCore Plugin (NCP)" set
   in three lines beside them. Below 640px the name takes the full line and the
   chips sit left-aligned underneath. */
@media(max-width:640px){
  .prodcard .ptop{flex-wrap:wrap;row-gap:8px}
  .prodcard .ptop > h2{flex:1 0 100%}
  .prodcard .ptop > .pprice:first-of-type,
  .prodcard .ptop > .pstatus:first-of-type{margin-left:0}
}

/* CHECKBOXES AND RADIOS ON A TOUCH SCREEN. The browser default is about 13px,
   and measured at phone width the handbook's acknowledgement boxes were 13x13
   and the announcement toggle 16x18, with no label around them to widen the
   target. 24px on a coarse pointer meets WCAG 2.5.8; a mouse keeps the default.
   The panel sizes two kinds itself (.switch 18px, .askcheck 17px) at this same
   specificity and after this file, so it repeats the rule for those in its own
   <style> block — 11 of its 37 boxes were still 18px on touch until it did. */
@media(pointer:coarse){input[type=checkbox],input[type=radio]{width:24px;height:24px}}
