/* ═══════════════════════════════════════════════════════════════════════
   AWi — the shared sheet.

   SELF-HOSTED BY NECESSITY, not preference. The Content-Security-Policy
   this app sends has no third-party origins at all (lib/http.php), which
   means fonts.googleapis.com is unreachable from a served page. The design
   files under site/ still link Google Fonts because they are standalone
   artifacts; everything the server renders loads its faces from here.

   ⚠ THESE FACES ARE THE DESIGN. Not decoration on it. Fraunces at weight
   300 with its SOFT and WONK axes is what makes a heading look drawn
   rather than typeset, and the first deploy that went to the box had none
   of them — so every screen fell back to Georgia and Arial and looked like
   a competent template instead of the thing that was approved. Four files,
   295KB, cached for a year. Cheapest visual return in the project.

   Variable axes, and why each file was chosen:
     Fraunces      full  — opsz + wght + SOFT + WONK, all four are used
     Newsreader    wght  — opsz dropped; at 18px it is invisible and costs 72KB
     Archivo       wght  — wdth dropped; it moves two lines of label type
     Martian Mono  wght  — the only axis the design asks for

   Order: faces, tokens, reset, furniture, forms, then the decorations.
   ═══════════════════════════════════════════════════════════════════════ */

/* `swap` on every face: text paints immediately in the fallback and
   re-renders when the face lands. `block` would give a flash of nothing,
   which on a slow connection reads as a broken page rather than a loading
   one. */
@font-face{
  font-family:"Fraunces";font-style:normal;font-display:swap;
  font-weight:100 900;font-variation-settings:normal;
  src:url("fonts/fraunces.woff2") format("woff2-variations");
}
@font-face{
  font-family:"Newsreader";font-style:normal;font-display:swap;
  font-weight:200 800;
  src:url("fonts/newsreader.woff2") format("woff2-variations");
}
@font-face{
  font-family:"Newsreader";font-style:italic;font-display:swap;
  font-weight:200 800;
  src:url("fonts/newsreader-italic.woff2") format("woff2-variations");
}
@font-face{
  font-family:"Archivo";font-style:normal;font-display:swap;
  font-weight:100 900;
  src:url("fonts/archivo.woff2") format("woff2-variations");
}
@font-face{
  font-family:"Martian Mono";font-style:normal;font-display:swap;
  font-weight:100 800;
  src:url("fonts/martian-mono.woff2") format("woff2-variations");
}

:root{
  /* The Institute's palette. Copper on near-black, with patina for "good"
     and rust for "wrong" — deliberately not green/red, which read as a
     dashboard rather than an instrument. */
  --void:#05060A; --plate:#0A0C11; --rise:#101319; --edge:#191D26; --edge-lit:#252B37;
  --cu:#B4642A; --cu-live:#E0873C; --cu-hot:#FFC98F;
  --patina:#4A9384; --rust:#9E4B33;
  --ink:#E8E6E1; --ink-2:#9A9A96; --ink-3:#6B6B68; --ink-4:#43433F;
  --ease:cubic-bezier(.2,.8,.2,1); --in:cubic-bezier(.16,1,.3,1);

  --serif:"Fraunces","Newsreader",Georgia,"Times New Roman",serif;
  --body:"Newsreader",Georgia,"Times New Roman",serif;
  --sans:"Archivo",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --mono:"Martian Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}

*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{min-height:100%;background:var(--void);color:var(--ink);
  font-family:var(--body);font-size:18px;line-height:1.62;font-weight:400;
  -webkit-font-smoothing:antialiased}
img{max-width:100%;height:auto}
a{color:var(--cu-live);text-decoration:none;
  border-bottom:1px solid rgba(180,100,42,.42);transition:color 160ms var(--ease),border-color 160ms var(--ease)}
a:hover{color:var(--cu-hot);border-color:var(--cu-live)}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums slashed-zero}

/* Keyboard users get a visible ring; pointer users never see it. Removing
   :focus outright is the single most common accessibility regression in a
   dark theme, because the ring is invisible against the ground anyway and
   somebody "cleans it up". */
:focus-visible{outline:2px solid var(--cu-live);outline-offset:3px;border-radius:3px}

/* ── the standing vignette ─────────────────────────────────────────── */
.vig{position:fixed;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(122% 92% at 26% 38%,transparent 28%,rgba(0,0,0,.66) 100%)}

/* ── masthead ──────────────────────────────────────────────────────── */
.mast{position:relative;z-index:3;display:flex;align-items:center;gap:14px;
  padding:18px clamp(18px,4vw,44px);border-bottom:1px solid var(--edge);
  background:rgba(8,10,14,.6);backdrop-filter:blur(14px) saturate(160%)}
.mast .brand{font-family:var(--sans);font-weight:700;font-size:14.5px;letter-spacing:.01em;
  color:var(--ink);border:0}
.mast .brand i{color:var(--cu-live);font-style:normal}
.mast .sp{flex:1}
.mast nav{display:flex;gap:clamp(10px,2vw,22px);align-items:center}
.mast nav a{font-family:var(--sans);font-size:12.5px;color:var(--ink-3);border-bottom-color:transparent}
.mast nav a:hover{color:var(--cu-live);border-bottom-color:var(--cu)}
.mast nav a[aria-current="page"]{color:var(--ink)}
@media(max-width:620px){.mast nav .opt{display:none}}

/* ── page frame ────────────────────────────────────────────────────── */
.wrap{position:relative;z-index:2;width:100%;max-width:1180px;margin:0 auto;
  padding:clamp(28px,6vw,64px) clamp(18px,4vw,44px) 80px}
.narrow{max-width:560px}

h1{font-family:var(--serif);font-weight:300;letter-spacing:-.04em;line-height:.98;
  font-size:clamp(2.3rem,1rem+3.6vw,4.1rem);text-wrap:balance;
  font-variation-settings:"SOFT" 0,"WONK" 1}
h1 em{font-style:normal;color:var(--cu-live)}
h2{font-family:var(--serif);font-weight:400;letter-spacing:-.028em;line-height:1.08;
  font-size:clamp(1.6rem,1rem+1.7vw,2.2rem);text-wrap:balance}
.lede{color:var(--ink-3);margin-top:16px;max-width:46ch;text-wrap:pretty}
.eyebrow{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--cu);margin-bottom:14px}

/* ── flash ─────────────────────────────────────────────────────────── */
.flash{display:grid;gap:9px;margin-bottom:26px}
.flash p{font-family:var(--sans);font-size:13.5px;line-height:1.6;
  padding:12px 15px;border-radius:9px;border:1px solid var(--edge-lit);
  background:rgba(16,19,25,.72);color:var(--ink-2);
  border-left-width:2px;animation:slip 380ms var(--in) both}
.flash p.ok{border-left-color:var(--patina);color:#BFD8D2}
.flash p.warn{border-left-color:var(--cu);color:#E3C69E}
.flash p.error{border-left-color:var(--rust);color:#E2B3A6}
@keyframes slip{from{opacity:0;transform:translateY(-7px)}}

/* ── the card that every gate screen sits in ───────────────────────── */
.card{position:relative;overflow:hidden;border:1px solid var(--edge);border-radius:14px;
  padding:clamp(24px,4vw,38px);background:rgba(10,12,17,.66);
  backdrop-filter:blur(16px) saturate(165%);animation:rise 460ms var(--in) both}
.card::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(147deg,rgba(255,255,255,.05),transparent 46%)}
.card>*{position:relative}
.card .sub{font-family:var(--sans);font-size:13.5px;color:var(--ink-3);
  margin-top:10px;max-width:46ch;line-height:1.62}
@keyframes rise{from{opacity:0;transform:translateY(15px)}}

/* ── fields ────────────────────────────────────────────────────────── */
.f{margin-top:20px}
.f>label{display:flex;align-items:baseline;gap:8px;
  font-family:var(--sans);font-size:10.5px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-4);transition:color 200ms var(--ease)}
.f>label .hint{margin-left:auto;font-family:var(--mono);font-size:9px;
  letter-spacing:.04em;text-transform:none;font-weight:400;color:var(--ink-4)}
.f.ok>label{color:var(--patina)}
.f.bad>label{color:var(--rust)}
.f .box{display:flex;align-items:center;gap:10px;margin-top:7px;padding:0 13px;
  border:1px solid var(--edge-lit);border-radius:10px;background:rgba(16,19,25,.72);
  transition:border-color 200ms var(--ease),box-shadow 200ms var(--ease)}
.f .box:focus-within{border-color:var(--cu);box-shadow:0 0 0 3px rgba(180,100,42,.13)}
.f.ok .box{border-color:rgba(74,147,132,.5)}
.f.bad .box{border-color:rgba(158,75,51,.62)}
.f .box .at{font-family:var(--mono);font-size:14px;color:var(--ink-4)}
.f input{flex:1;min-width:0;padding:13px 0;border:none;background:none;
  font-family:var(--sans);font-size:15px;color:var(--ink)}
.f input:focus{outline:none}
.f input::placeholder{color:var(--ink-4)}
.f .peek{font-family:var(--mono);font-size:9px;color:var(--ink-4);background:none;
  border:1px solid var(--edge);border-radius:5px;padding:4px 7px;cursor:pointer;
  transition:color 160ms var(--ease),border-color 160ms var(--ease)}
.f .peek:hover{color:var(--cu-live);border-color:var(--cu)}
.f .msg{font-family:var(--mono);font-size:9.5px;line-height:1.7;letter-spacing:.02em;
  margin-top:6px;min-height:14px;color:var(--ink-4)}
.f.ok .msg{color:var(--patina)}
.f.bad .msg{color:var(--rust)}

/* Off-screen, NOT display:none — a bot that reads computed style skips a
   hidden field, and then the honeypot catches nobody. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* Five hairlines and one honest sentence, not a rainbow bar. */
.str{display:flex;gap:4px;margin-top:9px}
.str i{flex:1;height:2px;border-radius:1px;background:var(--edge-lit);
  transition:background 320ms var(--ease)}
.str i.l1{background:var(--rust)} .str i.l2{background:#8A5A2B}
.str i.l3{background:var(--cu)}  .str i.l4{background:var(--cu-live)}
.str i.l5{background:var(--patina)}
.verdict{font-family:var(--mono);font-size:9.5px;color:var(--ink-3);margin-top:7px}

.consent{display:flex;gap:11px;align-items:flex-start;margin-top:22px;
  padding-top:17px;border-top:1px solid var(--edge)}
.consent input[type=checkbox]{width:19px;height:19px;flex-shrink:0;margin-top:3px;
  accent-color:var(--cu);cursor:pointer}
.consent p{font-family:var(--sans);font-size:12.5px;color:var(--ink-3);line-height:1.6}
.consent .ver{display:block;font-family:var(--mono);font-size:9px;color:var(--ink-4);margin-top:5px}

/* The four documents, on their own line under the sentence. Printed with
   their versions because "you agreed to the Terms" means nothing without
   saying which Terms. */
.consent .docs{display:block;margin-top:7px;font-size:12.5px;line-height:1.9}
.consent .docs a{color:var(--ink-2);border-bottom-color:rgba(180,100,42,.3)}
.consent .docs a:hover{color:var(--cu-live)}
.consent .docs .v{font-family:var(--mono);font-style:normal;font-size:9px;
  color:var(--ink-4);letter-spacing:.04em}

/* The select wears the same box as every input. The native arrow is replaced
   with one drawn in the border colour — a system-coloured triangle is the one
   thing on this form that would look borrowed. */
.f .box select{flex:1;min-width:0;padding:13px 0;border:none;background:none;
  font-family:var(--sans);font-size:15px;color:var(--ink);cursor:pointer;
  appearance:none;-webkit-appearance:none}
.f .box select:focus{outline:none}
.f .box select option{background:var(--plate);color:var(--ink)}
.f .box:has(select)::after{content:"";width:7px;height:7px;flex-shrink:0;
  border-right:1.5px solid var(--ink-4);border-bottom:1.5px solid var(--ink-4);
  transform:translateY(-2px) rotate(45deg);transition:border-color 200ms var(--ease)}
.f .box:has(select):focus-within::after{border-color:var(--cu-live)}

.btn{font-family:var(--sans);font-size:14px;font-weight:600;padding:13px 20px;
  border-radius:10px;border:1px solid var(--edge-lit);background:rgba(16,19,25,.7);
  color:var(--ink);cursor:pointer;
  transition:border-color 180ms var(--ease),background 180ms var(--ease),
             color 180ms var(--ease),opacity 180ms var(--ease)}
.btn:hover{border-color:var(--cu);background:rgba(180,100,42,.14)}
.btn.primary{width:100%;background:var(--cu);border-color:var(--cu-live);color:#150B02}
.btn.primary:hover{background:var(--cu-live)}
.btn.primary:disabled{opacity:.34;cursor:not-allowed;background:var(--cu)}
.go{margin-top:26px}
.alt{font-family:var(--sans);font-size:13px;color:var(--ink-3);margin-top:18px;text-align:center}

/* ── foot ──────────────────────────────────────────────────────────── */
.foot{position:relative;z-index:2;border-top:1px solid var(--edge);
  padding:26px clamp(18px,4vw,44px);display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center;
  font-family:var(--sans);font-size:11.5px;color:var(--ink-4)}
.foot a{color:var(--ink-3);border-bottom-color:transparent}
.foot a:hover{color:var(--cu-live)}
.foot .sp{flex:1}

/* ── the right-click plate ─────────────────────────────────────────── */
.rcx{position:fixed;inset:0;z-index:9999;display:grid;place-items:center;
  background:rgba(3,4,7,.72);backdrop-filter:blur(6px);
  opacity:0;pointer-events:none;transition:opacity 240ms var(--ease)}
.rcx.on{opacity:1;pointer-events:auto}
.rcx .plate{position:relative;padding:30px 46px;border-radius:13px;
  border:1px solid var(--edge-lit);background:rgba(10,12,17,.94);
  box-shadow:0 26px 70px rgba(0,0,0,.62);
  transform:translateY(12px) scale(.97);transition:transform 300ms var(--in)}
.rcx.on .plate{transform:none}
.rcx .plate p{font-family:var(--serif);font-weight:300;font-size:clamp(1.1rem,1rem+.9vw,1.6rem);
  letter-spacing:-.02em;color:var(--ink);white-space:nowrap}
.rcx .plate p i{font-style:normal;color:var(--cu-live)}
.rcx .x{position:absolute;top:8px;right:10px;width:26px;height:26px;line-height:24px;
  text-align:center;border-radius:6px;border:1px solid var(--edge);background:none;
  color:var(--ink-4);font-size:15px;cursor:pointer;
  transition:color 160ms var(--ease),border-color 160ms var(--ease)}
.rcx .x:hover{color:var(--cu-live);border-color:var(--cu)}
/* The 6-second wick, drawn rather than counted at the user. */
.rcx .wick{position:absolute;left:0;bottom:0;height:2px;width:100%;
  background:var(--cu);transform-origin:left;border-radius:0 0 0 13px}
.rcx.on .wick{animation:burn 6s linear forwards}
@keyframes burn{to{transform:scaleX(0)}}

@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:1ms!important;animation-iteration-count:1!important;
    transition-duration:1ms!important}
}

/* ═══════════════════════════════════════════════════════════════════════
   THE BROADSHEET
   ═══════════════════════════════════════════════════════════════════════ */

.narrow-wide{max-width:760px}
.note{font-family:var(--sans);font-size:13px;line-height:1.6;color:var(--ink-3);
  margin-top:20px;padding:12px 15px;border-left:2px solid var(--edge-lit);
  background:rgba(16,19,25,.5);border-radius:0 8px 8px 0}
.note.promo{border-left-color:var(--patina);color:#A9C6BF}
/* The beta banner on the AWi page. Copper rather than the patina green:
   this is a "we are working on it" and green reads as reassurance, which
   is not what the sentence says. Muted on purpose — it sits above the
   composer on every visit and a loud banner stops being read by the
   second one. */
.note.beta{border-left-color:var(--cu);color:#C6A98B}

.tag{font-family:var(--mono);font-style:normal;font-size:8.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-4);border:1px solid var(--edge-lit);
  border-radius:3px;padding:2px 5px;margin-right:7px;vertical-align:2px;white-space:nowrap}
.tag.promo{color:var(--patina);border-color:rgba(74,147,132,.4)}

/* ── the group / room index ────────────────────────────────────────── */
.groups{display:grid;gap:34px;margin-top:44px}
@media(min-width:940px){.groups{grid-template-columns:1fr 1fr;gap:34px 40px}}
.grp>header h2{font-size:clamp(1.3rem,1rem+.9vw,1.7rem)}
.grp>header h2 a{color:var(--ink);border-bottom:none}
.grp>header h2 a:hover{color:var(--cu-live)}
.grp>header p{font-family:var(--sans);font-size:12.5px;color:var(--ink-4);margin-top:5px}

.rooms{list-style:none;margin-top:14px;border-top:1px solid var(--edge)}
.rooms.tight{margin-top:26px}
.rooms li{border-bottom:1px solid var(--edge)}
.rooms a{display:grid;grid-template-columns:1fr auto;gap:2px 14px;align-items:baseline;
  padding:12px 4px;border-bottom:none;
  /* The row is the target, so the hover has to move the row and not the
     text — a link that only highlights its own label reads as unclickable
     everywhere else in the row. */
  transition:background 180ms var(--ease),padding-left 180ms var(--ease)}
.rooms a:hover{background:rgba(180,100,42,.07);padding-left:11px}
.rooms .rn{font-family:var(--sans);font-weight:600;font-size:14.5px;color:var(--ink)}
.rooms a:hover .rn{color:var(--cu-live)}
.rooms .rd{grid-column:1;font-family:var(--sans);font-size:12px;color:var(--ink-4);
  line-height:1.5}
.rooms .rc{grid-column:2;grid-row:1/3;align-self:center;font-size:11px;color:var(--ink-4)}

/* ── thread listing ────────────────────────────────────────────────── */
.threads{list-style:none;margin-top:36px;border-top:1px solid var(--edge)}
.threads li{border-bottom:1px solid var(--edge)}
.threads li.pin{background:linear-gradient(90deg,rgba(180,100,42,.06),transparent 60%)}
.threads a{display:block;padding:18px 4px;border-bottom:none;
  transition:background 180ms var(--ease),padding-left 180ms var(--ease)}
.threads a:hover{background:rgba(180,100,42,.06);padding-left:11px}
.threads h3{font-family:var(--serif);font-weight:400;font-size:1.28rem;line-height:1.25;
  letter-spacing:-.02em;color:var(--ink);text-wrap:balance}
.threads a:hover h3{color:var(--cu-hot)}
.threads .ex{font-family:var(--sans);font-size:13px;color:var(--ink-3);margin-top:6px;
  line-height:1.55;max-width:70ch}
.meta{font-size:10px;letter-spacing:.03em;color:var(--ink-4);margin-top:9px}

.pages{display:flex;gap:5px;margin-top:30px;font-size:11px;flex-wrap:wrap}
.pages a,.pages span{padding:6px 11px;border:1px solid var(--edge);border-radius:6px;
  color:var(--ink-3)}
.pages a:hover{border-color:var(--cu);color:var(--cu-live)}
.pages span[aria-current]{border-color:var(--cu);color:var(--cu-hot);
  background:rgba(180,100,42,.12)}

/* ── a post and its replies ────────────────────────────────────────── */
.post{margin-top:26px;padding-bottom:26px;border-bottom:1px solid var(--edge)}
.post h1{font-size:clamp(1.9rem,1rem+2.4vw,3rem);font-weight:300}
.post .acts{display:flex;gap:10px;align-items:center;margin-top:22px}
.btn.sm{font-size:12px;padding:8px 14px}
.btn.danger:hover{border-color:var(--rust);background:rgba(158,75,51,.16);color:#E2B3A6}
.btn.armed{border-color:var(--rust);background:rgba(158,75,51,.22);color:#F0CFC5}

/* The rendered body. Every selector here styles output from
   Markdown::render() and nothing else — which is why the tag list is
   closed and short: those are the only elements the renderer emits. */
.body{margin-top:24px;font-size:18.5px;line-height:1.68;color:#D7D5D0}
.body>*+*{margin-top:17px}
.body h3,.body h4,.body h5{font-family:var(--serif);font-weight:500;color:var(--ink);
  letter-spacing:-.02em;line-height:1.25;margin-top:30px}
.body h3{font-size:1.42rem} .body h4{font-size:1.2rem} .body h5{font-size:1.05rem}
.body strong{color:var(--ink);font-weight:600}
.body em{font-style:italic}
.body del{color:var(--ink-4)}
.body ul,.body ol{padding-left:1.35em}
.body li+li{margin-top:7px}
.body blockquote{padding:3px 0 3px 18px;border-left:2px solid var(--cu);color:var(--ink-2);
  font-style:italic}
.body code{font-family:var(--mono);font-size:.82em;background:rgba(16,19,25,.9);
  border:1px solid var(--edge);border-radius:4px;padding:2px 5px;color:var(--cu-hot)}
.body pre{background:rgba(8,10,14,.9);border:1px solid var(--edge);border-radius:9px;
  padding:15px 17px;overflow-x:auto}
.body pre code{background:none;border:none;padding:0;color:#C8C5D4;font-size:13px;
  line-height:1.6}
.body hr{border:none;border-top:1px solid var(--edge);margin:26px 0}
.body a{word-break:break-word}
.body.gone{color:var(--ink-4);font-style:italic}

.replies{margin-top:40px}
.replies h2{font-family:var(--sans);font-weight:700;font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-4)}
.cmt{padding:22px 0;border-bottom:1px solid var(--edge)}
.cmt .meta{margin-top:0}
.cmt .body{margin-top:10px;font-size:16.5px}
/* Jumping to a reply has to be visible, or the jump looks like it failed. */
.cmt:target{background:rgba(180,100,42,.07);border-radius:8px;
  padding-left:14px;padding-right:14px;animation:landed 1.6s var(--ease)}
@keyframes landed{from{background:rgba(180,100,42,.2)}}

/* Rule 15 controls. They sit quiet until the comment is hovered or something
   inside it has focus — the fifteen-minute window belongs to one person and
   should not decorate everybody else's reading. focus-within keeps that
   promise for the keyboard, where hover never fires. */
.cmt .acts{margin-top:10px;opacity:0;transition:opacity .18s var(--ease)}
.cmt:hover .acts,.cmt:focus-within .acts,.cmt:target .acts{opacity:1}
@media (hover:none){.cmt .acts{opacity:1}}

.cmt-edit{margin-top:12px;padding:14px;border:1px solid var(--edge);border-radius:10px;
  background:var(--rise)}
.cmt-edit .go{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:10px}
.cmt-edit .msg{margin:0;color:var(--ink-3)}

/* Present to a screen reader, absent to the eye. Never display:none — that
   takes the label out of the accessibility tree along with the pixels. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

/* ── compose ───────────────────────────────────────────────────────── */
.compose{margin-top:34px}
.compose h2{font-family:var(--serif);font-weight:400;font-size:1.5rem;letter-spacing:-.02em}
.f .box.area{padding:11px 13px;align-items:stretch}
.f textarea{flex:1;min-width:0;resize:vertical;border:none;background:none;padding:0;
  font-family:var(--sans);font-size:15px;line-height:1.6;color:var(--ink)}
.f textarea:focus{outline:none}

/* ── polls ─────────────────────────────────────────────────────────── */
.poll{margin-top:32px}
.poll h2{font-family:var(--serif);font-weight:400;font-size:1.4rem;letter-spacing:-.02em}
.opts{list-style:none;margin-top:18px;display:grid;gap:9px}
.opts label,.opts.results li{display:grid;grid-template-columns:auto 1fr auto;gap:11px;
  align-items:center;padding:11px 13px;border:1px solid var(--edge-lit);border-radius:9px;
  background:rgba(16,19,25,.6);cursor:pointer;
  transition:border-color 180ms var(--ease),background 180ms var(--ease)}
.opts.results li{grid-template-columns:1fr auto;cursor:default}
.opts label:hover{border-color:var(--cu);background:rgba(180,100,42,.09)}
.opts input[type=radio]{accent-color:var(--cu);width:16px;height:16px}
.opts .ol{font-family:var(--sans);font-size:14px;color:var(--ink)}
.opts .on{font-size:11px;color:var(--cu-live);min-width:38px;text-align:right}
/* The bar sits UNDER the row rather than beside it, so a long option label
   never squeezes the bar down to nothing on a phone. */
.opts .obar{grid-column:1/-1;grid-row:2;height:3px;border-radius:2px;
  background:var(--edge-lit);overflow:hidden}
.opts .obar i{display:block;height:100%;background:var(--cu);border-radius:2px;
  transition:width 620ms var(--in)}
.poll .msg{margin-top:12px;font-size:10px;color:var(--ink-4)}
.poll form>.btn{margin-top:16px}

/* ═══════════════════════════════════════════════════════════════════════
   UTILITIES — and the reason they exist rather than inline styles

   ⚠ A CSP NONCE DOES NOT COVER `style="…"` ATTRIBUTES. Nonces apply to
   <style> ELEMENTS and to <script>; inline style ATTRIBUTES are governed by
   style-src-attr, which falls back to style-src, and there a nonce without
   'unsafe-inline' blocks them outright. So every `style="…"` in a view was
   being silently dropped by the browser — no console error the server ever
   sees, no failing test, just layout that quietly did not apply.

   The fix is these classes rather than loosening the policy. Anything
   genuinely per-instance (a poll bar's width) goes in a <style nonce="…">
   block, which the nonce DOES cover.
   ═══════════════════════════════════════════════════════════════════════ */

.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.row.pad{margin-top:34px}
.btn.auto{width:auto}
/* Links styled as buttons carry the underline every other link has. */
a.btn{border-bottom:none;display:inline-block;text-align:center}
a.btn.block{display:block;width:100%}
.stack-sm{margin-top:16px}
.stack-md{margin-top:30px}
.stack-lg{margin-top:36px}
.flush-b{padding-bottom:0}
.consent.plain{border-top:none;padding-top:14px}
strong.lit{color:var(--ink-2)}
.stat{margin-top:44px;font-size:10px;letter-spacing:.12em;color:var(--ink-4)}

/* ═══════════════════════════════════════════════════════════════════════
   THE SWITCHBOARD
   ═══════════════════════════════════════════════════════════════════════ */

.reqs,.convs{margin-top:40px}
.reqs h2,.convs h2{font-family:var(--sans);font-weight:700;font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink-4)}
.req{display:grid;gap:10px;padding:16px 0;border-bottom:1px solid var(--edge)}
.req:first-of-type{border-top:1px solid var(--edge);margin-top:14px}
.req .who{font-family:var(--sans);font-size:15px;color:var(--ink)}
.req .who a{color:var(--cu-live)}
.req .who .meta{display:block;margin-top:4px}
.acts{display:flex;gap:8px;flex-wrap:wrap;align-items:center}

/* ── the voice panel ───────────────────────────────────────────────
   Sits at the foot of the conversation rail. It is furniture, not content:
   quieter than the conversation list above it, separated by a rule rather
   than a heading, and it never grows tall enough to push the list out of
   view — the whole point of putting it here is that the conversations stay
   where they were. */
.voicebox{margin-top:auto;padding-top:16px;border-top:1px solid var(--edge);
  font-family:var(--sans);flex:0 0 auto}
.voicebox .eyebrow{margin-bottom:10px}
.voicebox .vrow{display:flex;align-items:center;gap:9px;cursor:pointer;
  font-size:12.5px;color:var(--ink-2);padding:5px 0;
  transition:color 160ms var(--ease)}
.voicebox .vrow:hover{color:var(--ink)}
.voicebox input[type=checkbox]{flex:0 0 auto;accent-color:var(--cu-live);
  width:14px;height:14px;cursor:pointer}
.voicebox .vwhen{margin-top:6px;
  animation:vfade 260ms var(--in) both}
@keyframes vfade{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.voicebox .vwhen{animation:none}}
.voicebox .vfield{display:block;margin:9px 0 12px}
.voicebox .vlab{display:flex;justify-content:space-between;align-items:baseline;
  font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-4);
  margin-bottom:6px}
.voicebox .vlab b{font-family:var(--mono);font-weight:400;letter-spacing:0;
  text-transform:none;color:var(--cu-live)}
.voicebox select{width:100%;padding:7px 9px;cursor:pointer;
  font-family:var(--sans);font-size:12.5px;color:var(--ink);
  background:rgba(16,19,25,.9);border:1px solid var(--edge-lit);border-radius:7px;
  transition:border-color 160ms var(--ease)}
.voicebox select:hover,.voicebox select:focus{border-color:var(--cu);outline:none}
.voicebox input[type=range]{width:100%;accent-color:var(--cu-live);cursor:pointer}
/* ⚠ NOT PINNED TO THE BOTTOM WHEN THE RAIL IS A STRIP. Under 980px the rail
   collapses to a 132px band that scrolls; `margin-top:auto` inside it would
   push the panel against a floor that is only a few pixels down, landing it
   on top of the conversation list. It flows after the list instead, and is
   reached the way everything else in there is. */
@media(max-width:979px){
  .voicebox{margin-top:14px}
}

.convlist{list-style:none;margin-top:14px;border-top:1px solid var(--edge)}
.convlist li{border-bottom:1px solid var(--edge)}
.convlist a{display:grid;grid-template-columns:1fr auto;gap:3px 14px;padding:14px 4px;
  border-bottom:none;transition:background 180ms var(--ease),padding-left 180ms var(--ease)}
.convlist a:hover{background:rgba(180,100,42,.06);padding-left:11px}
.convlist .h{font-family:var(--sans);font-weight:600;font-size:14.5px;color:var(--ink);
  display:flex;align-items:center;gap:8px}
.convlist .pv{grid-column:1;font-family:var(--sans);font-size:12.5px;color:var(--ink-4);
  line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60ch}
.convlist .ts{grid-column:2;grid-row:1;font-size:10px;color:var(--ink-4)}
.convlist li.un .h{color:var(--cu-hot)}
.convlist li.un .pv{color:var(--ink-2)}
.dot{font-style:normal;font-size:9.5px;min-width:18px;height:18px;line-height:18px;
  text-align:center;border-radius:9px;background:var(--cu);color:#150B02;padding:0 5px}

/* ── a conversation ────────────────────────────────────────────────── */
.msgs{list-style:none;margin-top:26px;display:grid;gap:12px;
  max-height:min(62vh,620px);overflow-y:auto;padding:4px 2px;
  scrollbar-width:thin;scrollbar-color:var(--edge-lit) transparent}
.msgs::-webkit-scrollbar{width:9px}
.msgs::-webkit-scrollbar-thumb{background:var(--edge-lit);border-radius:5px}
.msgs li{display:flex;animation:said 340ms var(--in) both}
.msgs li.mine{justify-content:flex-end}
@keyframes said{from{opacity:0;transform:translateY(9px)}}
.bub{max-width:min(76%,52ch);padding:12px 15px;border-radius:13px;
  border:1px solid var(--edge-lit);background:rgba(16,19,25,.72)}
.msgs li.mine .bub{border-color:rgba(180,100,42,.42);background:rgba(180,100,42,.11);
  border-bottom-right-radius:4px}
.msgs li.theirs .bub{border-bottom-left-radius:4px}
.bub .body{margin-top:0;font-size:15.5px;line-height:1.6;color:#DAD8D3}
.bub .body>*+*{margin-top:11px}
.bub .ts{font-size:9.5px;color:var(--ink-4);margin-top:7px}
.msgs li.mine .ts{text-align:right}

/* ═══════════════════════════════════════════════════════════════════════
   THE INSTRUMENT
   ═══════════════════════════════════════════════════════════════════════ */

/* ══ THE INSTRUMENT ══════════════════════════════════════════════════
   ⚠ THIS IS A LOCKED SHELL, NOT A DOCUMENT.

   It used to be ordinary page flow: the turn list grew, the composer sat
   underneath it, and the whole document scrolled. After twenty exchanges
   that is a mile of page, the box you type into is somewhere far below
   the fold, and every new answer pushes it further away. You cannot read
   and write at the same time, which is the only thing a chat has to do.

   So the page does not scroll here. The masthead stays, the composer
   stays, and ONE pane moves — the turns. Everything below is in service
   of that. */

/* dvh, not vh: on mobile the browser chrome collapses and 100vh is taller
   than the visible window, which puts the composer under the address bar
   — the single most common way a chat UI feels broken on a phone. */
/* ⚠ main.awi, NOT .awi — ELEMENT-QUALIFIED ON PURPOSE.
   A conversation turn is <li class="awi">, so a bare `.awi` height rule
   matched every answer AWi gave and made each one exactly as tall as the
   window. Measured: 799px rows against a 53px question. A shell class and a
   content class shared a name, and CSS has no idea they mean different
   things — only the element does. */
main.awi{
  --mast: 61px;                       /* header, measured not guessed (see JS) */
  display:grid; gap:0;
  height:calc(100dvh - var(--mast));
  min-height:520px;
  align-items:stretch;
}
@media(min-width:980px){
  main.awi{grid-template-columns:246px minmax(0,1fr); gap:0}
}

/* The page itself must not scroll while the Instrument is open. Without
   this you get TWO scrollbars and the outer one steals the wheel the
   moment the inner pane reaches an end. */
body.locked{overflow:hidden}
body.locked .foot{display:none}

/* The wrap's generous page padding is wrong for an app shell — it would
   eat a third of the height in whitespace. */
.wrap.awi-wrap{max-width:1320px;padding:0 clamp(0px,2vw,26px)}

/* ── conversations ─────────────────────────────────────────────────── */
.past{
  display:flex;flex-direction:column;min-height:0;
  padding:22px 22px 18px 4px;
  border-right:1px solid var(--edge);
}
@media(max-width:979px){
  .past{border-right:none;border-bottom:1px solid var(--edge);
        padding:14px 4px 12px;max-height:132px}
}
.past .eyebrow{margin-bottom:12px;flex:0 0 auto}
.past .hint{font-family:var(--sans);font-size:12.5px;color:var(--ink-4);line-height:1.6}

/* New conversation is a CONTROL, not the first row of the list. It was an
   <li> styled like every other entry, so the one thing you press most often
   looked exactly like the forty things you press rarely. */
.newconv{
  flex:0 0 auto;display:flex;align-items:center;gap:9px;
  margin:0 6px 14px 0;padding:10px 12px;
  border:1px solid var(--edge);border-radius:9px;border-bottom:1px solid var(--edge);
  background:var(--rise);color:var(--ink-2);
  font-family:var(--sans);font-size:13px;font-weight:500;
  transition:border-color 160ms var(--ease),color 160ms var(--ease),background 160ms var(--ease)}
.newconv .plus{font-size:15px;line-height:1;color:var(--cu);transition:color 160ms var(--ease)}
.newconv:hover{border-color:var(--cu);color:var(--ink);background:rgba(180,100,42,.08)}
.newconv:hover .plus{color:var(--cu-hot)}
.newconv.on{border-color:rgba(180,100,42,.55);color:var(--cu-hot);background:rgba(180,100,42,.11)}

/* ⚠ THE SCROLLER IS THE FLEX CHILD; THE LISTS INSIDE IT ARE NOT.
   This was one <ul> with `display:grid; flex:1 1 auto` — a grid told to fill
   the column, whose default `align-content:stretch` then spread five short
   rows down the whole height. Every entry sat in about 140px of nothing and
   the sidebar read as broken.
   It is the SAME defect that squashed the message pane earlier in this
   build, in a different element, and it is why `align-content:start` is
   spelled out below rather than left to the default. */
.convscroll{
  flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:6px;
  scrollbar-width:thin;scrollbar-color:var(--edge-lit) transparent}
.convscroll::-webkit-scrollbar{width:8px}
.convscroll::-webkit-scrollbar-thumb{background:var(--edge-lit);border-radius:4px}

/* The drawer labels. Sixty untitled fragments in one flat column is a filing
   cabinet with no drawers. */
.cgroup{
  position:sticky;top:0;z-index:1;
  margin:14px 0 5px;padding:4px 0 5px;
  background:linear-gradient(var(--void) 70%,transparent);
  font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.18em;
  text-transform:uppercase;color:var(--ink-4)}
.cgroup:first-child{margin-top:0}

/* ⚠ minmax(0,1fr), NOT 1fr. A grid item's default `min-width:auto` means it
   refuses to shrink below its content — so `text-overflow:ellipsis` on the
   title never fired and long titles ran under the divider and were clipped by
   the container instead. The ellipsis was there the whole time; the column
   was simply never narrower than the text. */
.past ul{list-style:none;display:grid;grid-template-columns:minmax(0,1fr);
  align-content:start;gap:2px;margin:0;padding:0}
.past li{min-width:0}
/* ⚠ TWO LINES, NOT TWO COLUMNS. The first version put the title and the
   metadata side by side, and the metadata — a time, a turn count, sometimes a
   pin — took a third of a 340px rail. Every title truncated to "Draft the
   annou…", which is the one thing the list exists to show. The title now owns
   the full width and the meta sits under it, quieter. */
.past li a{
  display:block;padding:8px 11px 9px;border-radius:8px;border-bottom:none;
  font-family:var(--sans);font-size:13px;line-height:1.35;color:var(--ink-3);
  transition:background 160ms var(--ease),color 160ms var(--ease)}
.past li a .ct{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.past li a .cm{
  display:flex;align-items:baseline;gap:5px;margin-top:3px;white-space:nowrap;
  font-family:var(--mono);font-size:9.5px;color:var(--ink-4);
  opacity:.8;transition:opacity 160ms var(--ease),color 160ms var(--ease)}
/* ⚠ NOT `.dot`. That class is a filled unread badge in the switchboard, and
   inheriting it turned a typographic middot into a copper disc. A shared
   stylesheet makes a generic class name a collision waiting to happen. */
.past li a .cm .sep{font-style:normal;opacity:.45}
.past li a .cm .pin{font-style:normal;color:var(--cu);margin-left:auto}
.past li a:hover{background:rgba(180,100,42,.09);color:var(--cu-live)}
.past li a:hover .cm{opacity:1;color:var(--ink-3)}
/* The current conversation gets a copper spine — a shape, not just a tint,
   so it survives being screenshotted in greyscale. */
.past li.on a{position:relative;background:rgba(180,100,42,.13);color:var(--cu-hot)}
.past li.on a::before{
  content:"";position:absolute;left:-6px;top:6px;bottom:6px;width:2px;
  background:var(--cu-live);border-radius:0 2px 2px 0;
  box-shadow:0 0 10px rgba(224,135,60,.55)}
.past li.on a .cm{color:var(--cu);opacity:1}

@media(max-width:979px){
  .convscroll{overflow-x:auto;overflow-y:hidden}
  .cgroup{display:none}
  .past ul{grid-auto-flow:column;grid-auto-columns:minmax(160px,1fr);gap:8px}
  .past li a{grid-template-columns:1fr}
  .past li a .cm{display:none}
  .newconv{margin-bottom:8px}
}

/* ── the column: head / gauge fixed, turns scroll, composer pinned ─── */
.talk{
  display:flex;flex-direction:column;min-height:0;   /* min-height:0 is what
    actually lets the middle child scroll instead of stretching the parent */
  padding:0 clamp(4px,2vw,30px);
}

.thead{display:flex;align-items:flex-end;gap:16px;justify-content:space-between;
  flex:0 0 auto;padding:20px 0 0}
.thead h1{font-size:clamp(1.4rem,1rem+1.1vw,2rem);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60vw}

.gauge{position:relative;margin-top:16px;padding-top:9px;flex:0 0 auto}
.gauge i{display:block;height:2px;border-radius:1px;background:var(--cu);
  transition:width 700ms var(--in)}
.gauge::before{content:"";position:absolute;left:0;right:0;top:0;height:2px;
  border-radius:1px;background:var(--edge-lit)}
.gauge span{display:block;margin-top:7px;font-size:9.5px;letter-spacing:.05em;
  color:var(--ink-4);transition:color 300ms var(--ease)}
/* Running out is worth seeing before it happens, not at the moment it stops. */
/* Unmetered: a full bar in the calm colour, not an empty one. */
.gauge.free i{width:100%;background:linear-gradient(90deg,var(--cu),var(--patina))}
.gauge.free span{color:var(--ink-3)}
.gauge.low i{background:var(--rust)}
.gauge.low span{color:var(--rust)}

/* ⚠ THE SCROLL PANE. The only thing on this page that moves.
   overscroll-behavior:contain stops the wheel chaining to the document at
   either end — without it, reaching the top of the conversation starts
   scrolling the page behind it and the whole illusion collapses. */
.turnwrap{position:relative;flex:1 1 auto;min-height:0;margin-top:14px}
.turns{
  list-style:none;display:grid;gap:22px;
  /* ⚠ align-content:start IS LOAD-BEARING.
     A grid with height:100% distributes its rows across that height — so
     twenty messages SHRANK to fit 360px instead of overflowing, and the
     pane never scrolled at all. Measured: scrollHeight === clientHeight
     with twenty turns in it. `start` makes each row take its natural
     height and lets the container overflow, which is the entire job. */
  align-content:start;
  height:100%;overflow-y:auto;overscroll-behavior:contain;
  padding:18px 14px 22px 2px;
  scrollbar-width:thin;scrollbar-color:var(--edge-lit) transparent;
}
.turns::-webkit-scrollbar{width:10px}
.turns::-webkit-scrollbar-track{background:transparent}
.turns::-webkit-scrollbar-thumb{background:var(--edge-lit);border-radius:99px;
  border:3px solid var(--plate);background-clip:padding-box}
.turns::-webkit-scrollbar-thumb:hover{background:#39414f;background-clip:padding-box;
  border:3px solid var(--plate)}

/* Content passes UNDER the edges rather than stopping dead at them — the
   difference between a pane and a box with things cut off in it. */
.turnwrap::before,.turnwrap::after{content:"";position:absolute;left:0;right:10px;
  height:34px;pointer-events:none;z-index:2;opacity:0;transition:opacity 220ms var(--ease)}
.turnwrap::before{top:0;background:linear-gradient(180deg,var(--void),transparent)}
.turnwrap::after{bottom:0;background:linear-gradient(0deg,var(--void),transparent)}
.turnwrap.has-above::before{opacity:1}
.turnwrap.has-below::after{opacity:1}

/* ⚠ `> li`, NOT `li`. THIS IS A DESCENDANT-SELECTOR BUG THAT SHIPPED.
   `.turns li` matches every list item inside the thread — INCLUDING the ones
   in AWi's own answers, because AWi writes bulleted lists. So every bullet in
   every answer became a flex container: its marker disappeared, its
   turn-in animation ran, and — the tell that finally gave it away in a
   screenshot — `<strong>The mandate</strong> narrowed` rendered as
   "The mandatenarrowed", because flex makes the element and the text node
   beside it two separate flex items with nothing between them.

   The HTML was right the whole time. Markdown::render() emits the space; the
   stylesheet was throwing it away. The child combinator says what was always
   meant: these rules are about TURNS, and a turn is a direct child. */
.turns>li{display:flex;animation:turn-in 420ms var(--in) both}
@keyframes turn-in{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.turns>li{animation:none}}

.turns>li.said{justify-content:flex-end}
.turns>li.said .body{max-width:min(78%,54ch);padding:12px 16px;border-radius:14px;
  border:1px solid rgba(180,100,42,.4);background:rgba(180,100,42,.1);
  border-bottom-right-radius:4px;font-size:16px;color:#E4E1DC}
.turns>li.awi .body{max-width:74ch;font-size:17.5px}
.turns>li.awi::before{content:"";flex:0 0 3px;align-self:stretch;margin-right:16px;
  border-radius:2px;background:linear-gradient(180deg,var(--cu),transparent)}
.turns>li.failed::before{background:linear-gradient(180deg,var(--rust),transparent)}
.turns>li.failed .body{color:#E2B3A6;font-size:15px}
.turns>li.opening{display:block;color:var(--ink-3);font-family:var(--sans);font-size:14px;
  line-height:1.65;max-width:52ch;margin:auto 0}
.turns>li.opening .hint{margin-top:9px;font-size:12px;color:var(--ink-4)}
.turns .body>*+*{margin-top:14px}

/* ── AWi out loud ──────────────────────────────────────────────────
   A play control that has to survive sitting next to every answer on the
   page without ever competing with one.

   Three decisions:

   · IT RECEDES UNTIL WANTED. At rest it is a dim outline at 45%; the turn
     being hovered, focused, or currently speaking brings it up. Forty
     answers each with a solid copper button is a page of buttons with some
     prose between them.
   · THE STATES ARE DRAWN, NOT LOADED. Play, loading and stop are one
     element and a pseudo-element — no icon font, no sprite, no request, and
     nothing to go missing behind a CDN.
   · PLAYING IS OBVIOUS FROM ACROSS THE ROOM. A ring breathes around it, so
     "which one is talking" is answerable at a glance in a long thread —
     which is the question somebody actually has while hunting for the stop. */
/* ⚠ ANCHORED UNDER THE ANSWER, NOT BESIDE IT. As a flex sibling of .body it
   landed at the top-right corner of the body's BOX — which, for a short
   answer or one that ends in a code block, is a bare circle floating in open
   space with nothing to say what it belongs to. A screenshot made that
   obvious immediately.

   Absolute, at the foot of the turn and aligned with the text's own left
   edge, it reads as an action ON this answer, which is what it is. The turn
   pays 32px of bottom padding for it; the alternative was a control nobody
   could parse. */
.turns>li.awi{position:relative; padding-left:2px; padding-bottom:32px}
.turns>li.awi .say{
  position:absolute; left:19px; bottom:0;
  width:30px; height:30px; border-radius:50%; padding:0; cursor:pointer;
  background:transparent;
  border:1px solid var(--edge-lit); color:var(--ink-3);
  opacity:.45; transform:scale(.94);
  transition:opacity 200ms var(--ease), transform 200ms var(--in),
             border-color 200ms var(--ease), color 200ms var(--ease);
}
.turns>li.awi:hover .say,
.turns>li.awi .say:focus-visible,
.turns>li.awi .say[data-state="playing"],
.turns>li.awi .say[data-state="loading"]{opacity:1; transform:scale(1)}
.turns>li.awi .say:hover{border-color:var(--cu); color:var(--cu-hot)}

/* rest · a play triangle, optically centred (a glyph on its own centroid
   reads as sitting too far left, hence the extra pixel) */
.turns>li.awi .say[data-state="rest"]::before{
  content:""; position:absolute; top:50%; left:50%;
  transform:translate(-42%,-50%);
  border-style:solid; border-width:5px 0 5px 8px;
  border-color:transparent transparent transparent currentColor;
}

/* loading · one arc, turning. Deliberately not a full spinner: the wait is
   usually under a second and a busy animation makes it feel longer. */
.turns>li.awi .say[data-state="loading"]{border-color:var(--cu)}
.turns>li.awi .say[data-state="loading"]::before{
  content:""; position:absolute; inset:4px; border-radius:50%;
  border:2px solid transparent; border-top-color:var(--cu-live);
  animation:say-spin 700ms linear infinite;
}
@keyframes say-spin{to{transform:rotate(360deg)}}

/* playing · two bars, and a ring that breathes */
.turns>li.awi .say[data-state="playing"]{border-color:var(--cu-live); color:var(--cu-hot)}
.turns>li.awi .say[data-state="playing"]::before{
  content:""; position:absolute; top:50%; left:50%;
  transform:translate(-50%,-50%);
  width:9px; height:10px;
  border-left:3px solid currentColor; border-right:3px solid currentColor;
}
.turns>li.awi .say[data-state="playing"]::after{
  content:""; position:absolute; inset:-4px; border-radius:50%;
  border:1px solid var(--cu); animation:say-ring 1.6s var(--ease) infinite;
  pointer-events:none;
}
/* the one line the voice is allowed to say for itself
   Sits under the answer it belongs to, indented to the button's own column
   so it reads as that control speaking rather than as the page announcing
   something. Muted, no icon, no border — an explanation, not an alarm. */
.turns>li.awi .saynote{
  margin:10px 0 0; padding:0 0 0 19px;
  font-size:.82rem; line-height:1.5; color:var(--ink-3);
  opacity:0; animation:say-note-in 420ms var(--ease) forwards;
}
@keyframes say-note-in{
  from{opacity:0; transform:translateY(-3px)}
  to  {opacity:.78; transform:none}
}
@media (prefers-reduced-motion:reduce){
  .turns>li.awi .saynote{animation:none; opacity:.78}
}

@keyframes say-ring{
  0%  {opacity:.55; transform:scale(.86)}
  70% {opacity:0;   transform:scale(1.28)}
  100%{opacity:0;   transform:scale(1.28)}
}

@media(prefers-reduced-motion:reduce){
  .turns>li.awi .say[data-state="loading"]::before{animation-duration:1.6s}
  .turns>li.awi .say[data-state="playing"]::after{animation:none; opacity:.5}
}

/* Narrow screens: the control keeps its target size but stops stealing
   width from the answer, which is the thing people came for. */
@media(max-width:640px){
  .turns>li.awi .say{left:14px; opacity:.7; transform:none}
}

/* ── jump to latest ────────────────────────────────────────────────
   Appears only when the reader has scrolled away from the bottom. The
   alternative — yanking them back down every time an answer lands — is
   the thing that makes a chat unreadable while it is talking. */
.tolatest{position:absolute;left:50%;bottom:14px;transform:translate(-50%,14px);
  display:flex;align-items:center;gap:8px;z-index:3;
  padding:8px 15px;border-radius:99px;cursor:pointer;
  font-family:var(--sans);font-size:12px;color:var(--ink);
  background:rgba(18,21,28,.92);border:1px solid var(--edge-lit);
  backdrop-filter:blur(10px);box-shadow:0 8px 24px rgba(0,0,0,.5);
  opacity:0;pointer-events:none;transition:opacity 220ms var(--ease),transform 220ms var(--in)}
.tolatest.on{opacity:1;pointer-events:auto;transform:translate(-50%,0)}
.tolatest:hover{border-color:var(--cu);color:var(--cu-hot)}
.tolatest b{font-family:var(--mono);font-size:10px;font-weight:400;color:var(--cu-live)}
.tolatest svg{width:12px;height:12px;flex:0 0 12px}

/* Three dots, not a spinner: a spinner says "loading", these say "writing". */
.dots{display:flex;gap:5px;align-items:center;height:18px}
.dots i{width:5px;height:5px;border-radius:50%;background:var(--cu);
  animation:think 1.25s var(--ease) infinite}
.dots i:nth-child(2){animation-delay:.16s}
.dots i:nth-child(3){animation-delay:.32s}
@keyframes think{0%,60%,100%{opacity:.22;transform:translateY(0)}
                 30%{opacity:1;transform:translateY(-4px)}}

/* ── the composer, pinned ──────────────────────────────────────────── */
/* ── rank badges ────────────────────────────────────────────────────
   Seven marks, distinguished by SHAPE first and colour second — colour
   alone is not a distinction for a reader who cannot tell copper from
   green, and "who is staff here" has to be unambiguous.

   The label is drawn as text and only revealed on hover/focus. A row of
   words beside every name would shout; a row of small marks reads as
   texture until you look at one. */
.rank{display:inline-flex;align-items:center;gap:0;vertical-align:-2px;
  margin-left:6px;color:var(--ink-4);transition:color 180ms var(--ease)}
.rank svg{width:13px;height:13px;flex:0 0 13px}
.rank i{font-family:var(--mono);font-style:normal;font-size:9px;letter-spacing:.06em;
  text-transform:uppercase;max-width:0;overflow:hidden;white-space:nowrap;opacity:0;
  transition:max-width 260ms var(--in),opacity 200ms var(--ease),margin 260ms var(--in)}
.rank:hover i,.meta:hover .rank i{max-width:110px;opacity:1;margin-left:5px}

/* The ladder, climbing out of the page's own greys into copper.
   Colour SECONDS the shape here; it never carries the meaning alone. */
.rank.guest     {color:#3A3A37}
.rank.member    {color:var(--ink-4)}
.rank.paid      {color:var(--patina)}
.rank.vip       {color:#7FBFAE}
.rank.special   {color:#9FD6C3}
.rank.editor    {color:var(--cu)}
.rank.moderator {color:#6FA8D6}
.rank.staff     {color:var(--ink-2)}
.rank.asst_admin{color:var(--cu)}
.rank.admin     {color:var(--cu-live)}
.rank.owner     {color:var(--cu-live);filter:drop-shadow(0 0 4px rgba(224,135,60,.3))}
.rank.founder   {color:var(--cu-hot);filter:drop-shadow(0 0 6px rgba(255,201,143,.42))}

/* ── the composer ───────────────────────────────────────────────────
   ONE control, not two. The send button used to sit in a separate row on
   the opposite side of the page from the box you type in — forty
   centimetres apart, sharing no edge and no styling, for a single action.
   It now lives inside the field, where your hand already is. */
.askbox{flex:0 0 auto;padding:10px 0 16px;position:relative}
.askbox .box.area.asker{
  display:flex;align-items:flex-end;gap:10px;
  padding:9px 9px 9px 16px;border-radius:16px;
  background:linear-gradient(180deg,rgba(20,24,31,.9),rgba(13,16,22,.9));
  border:1px solid var(--edge-lit);
  box-shadow:0 10px 34px rgba(0,0,0,.34);
  transition:border-color 220ms var(--ease),box-shadow 220ms var(--ease)}
.askbox .box.area.asker:focus-within{
  border-color:rgba(180,100,42,.6);
  box-shadow:0 0 0 3px rgba(180,100,42,.13),0 14px 40px rgba(0,0,0,.42)}
/* ⚠ The reset that strips a textarea's chrome is scoped to `.f textarea`,
   and this composer is not inside a .f wrapper — so it was rendering with
   the browser's default border and grey field, a raw form control sitting
   inside a piece of glass. Reset it here rather than adding a wrapper div
   whose only job would be to inherit a rule. */
.askbox textarea{
  flex:1;min-width:0;min-height:26px;max-height:240px;
  border:none;background:none;outline:none;resize:none;
  padding:8px 0;line-height:1.5;
  font-family:var(--sans);font-size:15.5px;color:var(--ink);
  caret-color:var(--cu-live)}
.askbox textarea::placeholder{color:var(--ink-4)}

/* A circle, because the action is one thing. Copper when it will do
   something, inert when the field is empty — a button that is always lit
   and sometimes does nothing teaches people to distrust it. */
.sendbtn{flex:0 0 40px;width:40px;height:40px;border-radius:50%;
  display:grid;place-items:center;cursor:pointer;
  border:1px solid transparent;background:var(--cu);color:#0A0C11;
  transition:background 200ms var(--ease),transform 160ms var(--in),opacity 200ms var(--ease)}
.sendbtn svg{width:17px;height:17px}
.sendbtn:hover:not(:disabled){background:var(--cu-live);transform:translateX(1px)}
.sendbtn:active:not(:disabled){transform:scale(.94)}
.sendbtn:disabled{background:none;border-color:var(--edge-lit);color:var(--ink-4);
  cursor:default}

.askfoot{display:flex;align-items:center;justify-content:space-between;gap:14px;
  margin-top:9px;padding:0 4px}
.askfoot .hint{font-size:9.5px;color:var(--ink-4)}
.askfoot .hint.near{color:var(--cu-live)}

/* While it is thinking, the field breathes rather than freezing. */
.askbox.busy .box.area.asker{border-color:rgba(180,100,42,.5)}
.askbox.busy .sendbtn{opacity:.5}
@media(prefers-reduced-motion:no-preference){
  .askbox.busy .box.area.asker{animation:awaiting 2.2s var(--ease) infinite}
}
@keyframes awaiting{
  0%,100%{box-shadow:0 0 0 0 rgba(180,100,42,.16),0 10px 34px rgba(0,0,0,.34)}
  50%    {box-shadow:0 0 0 4px rgba(180,100,42,.10),0 10px 34px rgba(0,0,0,.34)}}

/* ═══════════════════════════════════════════════════════════════════════
   THE STAGE — the aperture, and the pages built around it

   The shader is a fixed full-viewport layer at z-index 0. Everything else
   floats above it. `.no-gl` is set by assets/stage.js when the machine
   cannot or should not run WebGL2, and the 2D canvas takes over: the page
   must never be a flat black rectangle, because dark-and-empty reads as
   broken rather than as dark.
   ═══════════════════════════════════════════════════════════════════════ */

#gl,#fallback{position:fixed;inset:0;z-index:0;display:block;pointer-events:none}
#fallback{display:none}
.no-gl #gl{display:none}
.no-gl #fallback{display:block}
/* With the shader painting the ground, the flat vignette would double the
   darkening and flatten the very thing it is sitting on. */
.has-gl .vig{opacity:.45}
.staged .mast{background:rgba(8,10,14,.42)}

/* ── the display face, with its axes ──────────────────────────────────
   Fraunces is a variable face and the design uses four of its axes. SOFT 0
   keeps the terminals crisp; WONK 1 turns on the alternate letterforms that
   make a heading look drawn rather than typeset. Without these it is just
   another serif, which is exactly what the first deploy shipped. */
h1,h2,.display{font-variation-settings:"opsz" 144,"SOFT" 0,"WONK" 1}
h1 em,h2 em,.display em{font-variation-settings:"opsz" 144,"SOFT" 100,"WONK" 1}
body{font-optical-sizing:auto}

/* ── the boot: everything RESOLVES through blur, it does not fade ──────
   A fade is a page appearing. This is an image coming into focus, which is
   what an instrument does when it is switched on. Driven by --r, set per
   element with a stagger. */
.res{--r:0;
  opacity:var(--r);
  filter:blur(calc((1 - var(--r)) * 7px)) contrast(calc(1 + (1 - var(--r)) * 2.4));
  transform:translateY(calc((1 - var(--r)) * 14px));
  transition:opacity 900ms var(--in),filter 900ms var(--in),transform 900ms var(--in);
  will-change:opacity,filter,transform}
@media(prefers-reduced-motion:reduce){.res{--r:1!important;filter:none;transform:none}}

/* ── hero ─────────────────────────────────────────────────────────── */
.stage{position:relative;z-index:2}
.hero{min-height:78svh;display:grid;place-items:center;text-align:center;position:relative;
  padding:clamp(40px,9vh,110px) 0}
.hero .inner{max-width:900px}
.hero h1{font-size:clamp(3rem,1rem+8.4vw,8.6rem);font-weight:430;line-height:.92;
  letter-spacing:-.028em;
  /* screen blend so the aperture's light comes THROUGH the letterforms
     rather than being covered by them */
  mix-blend-mode:screen}
.hero .sub{font-size:clamp(1rem,.85rem+.5vw,1.28rem);color:var(--ink-2);
  margin:26px auto 0;max-width:52ch}
.readout{margin-top:32px;font-size:11px;color:var(--ink-3);letter-spacing:.08em}
.readout b{color:var(--cu-live);font-weight:400}

.scrollcue{position:absolute;bottom:22px;left:50%;transform:translateX(-50%);
  font-family:var(--sans);font-size:9.5px;text-transform:uppercase;letter-spacing:.2em;
  color:var(--ink-4);display:flex;flex-direction:column;align-items:center;gap:8px}
.scrollcue i{display:block;width:1px;height:40px;
  background:linear-gradient(var(--cu),transparent);animation:drop 2.4s var(--ease) infinite}
@keyframes drop{0%{transform:scaleY(0);transform-origin:top}
  45%{transform:scaleY(1);transform-origin:top}
  55%{transform:scaleY(1);transform-origin:bottom}
  100%{transform:scaleY(0);transform-origin:bottom}}

/* ── refracting glass ─────────────────────────────────────────────────
   An SVG displacement map, not a blur. backdrop-filter alone smears what
   is behind a panel; a displacement map BENDS it, which is what glass
   does. The filter is declared inline on the page that uses it. */
.pane{position:relative;border-radius:16px;overflow:hidden;
  background:rgba(10,12,17,.55);border:1px solid rgba(255,255,255,.075);
  backdrop-filter:blur(11px) saturate(175%) brightness(1.05) url(#refract);
  -webkit-backdrop-filter:blur(11px) saturate(175%) brightness(1.05);
  box-shadow:0 24px 60px -24px rgba(0,0,0,.85),inset 0 1px 0 rgba(255,255,255,.05)}
/* One key light, 15° upper-left. EVERY surface on the site obeys the same
   source — that consistency is most of why it reads as built rather than
   assembled. */
.pane::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(147deg,rgba(255,255,255,.085),rgba(255,255,255,.012) 40%,transparent 68%)}
/* The glint crosses ONCE, from the light's direction. Twice is a gimmick. */
.pane::after{content:"";position:absolute;top:-60%;left:-80%;width:55%;height:220%;
  background:linear-gradient(100deg,transparent,rgba(255,201,143,.16),transparent);
  transform:rotate(18deg) translateX(-40%);opacity:0;pointer-events:none}
.pane:hover::after{animation:glint 760ms var(--ease) 1}
@keyframes glint{0%{opacity:0;transform:rotate(18deg) translateX(-40%)}
  22%{opacity:1}100%{opacity:0;transform:rotate(18deg) translateX(340%)}}
.pane>*{position:relative;z-index:2}
.pane.reads{background:rgba(10,12,17,.86)}

/* ── the index — an institute publishes an index, not feature cards ── */
.idx{border-top:1px solid var(--edge);margin-top:34px}
.idxrow{display:grid;grid-template-columns:52px 1fr auto;gap:18px;align-items:baseline;
  padding:20px 6px;border-bottom:1px solid var(--edge);border-radius:0;
  transition:background 320ms var(--ease),padding-left 320ms var(--ease)}
.idxrow:hover{background:linear-gradient(90deg,rgba(180,100,42,.07),transparent 60%);
  padding-left:18px}
.idxrow .n{font-family:var(--mono);font-size:11px;color:var(--ink-4)}
.idxrow .t{font-family:var(--serif);font-size:clamp(1.5rem,1rem+2.4vw,3rem);font-weight:400;
  letter-spacing:-.03em;line-height:1;color:var(--ink);
  transition:color 320ms var(--ease)}
.idxrow:hover .t{color:var(--cu-live)}
.idxrow .m{font-family:var(--sans);font-size:12px;color:var(--ink-3);text-align:right}
@media(max-width:640px){.idxrow{grid-template-columns:38px 1fr}.idxrow .m{display:none}}
a.idxrow{border-bottom:1px solid var(--edge)}

.floor section{padding:clamp(56px,10vh,120px) 0}
.floor .lede{margin-bottom:8px}
.panebody{padding:clamp(26px,4vw,44px)}
.floor .row{margin-top:30px;justify-content:center}
.floor #idx .row,.floor .pane .row{justify-content:flex-start}

/* ═══════════════════════════════════════════════════════════════════════
   THE THRESHOLD — a doorway, not a form on a page

   The gate is two halves: on the left the aperture with a line of type
   sitting inside its opening, on the right a glass panel holding the act.
   The first deploy shipped only the panel, which is the half that does the
   work and none of the half that says where you are. Below 960px the stage
   folds away entirely — on a phone the form IS the page.
   ═══════════════════════════════════════════════════════════════════════ */

.gate{position:relative;z-index:2;display:grid;grid-template-columns:1fr;
  min-height:calc(100svh - 66px)}
@media(min-width:960px){
  .gate{grid-template-columns:minmax(0,1fr) minmax(430px,520px)}
}

.gate .say{display:none;position:relative;flex-direction:column;justify-content:center;
  padding:0 clamp(28px,5vw,76px)}
@media(min-width:960px){.gate .say{display:flex}}
/* The copy needs its own ground or the shader eats it. A gradient sweeping
   from the left, so the type sits in shadow and the aperture stays lit. */
.gate .say::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:linear-gradient(100deg,var(--void) 4%,rgba(5,6,10,.86) 26%,
             rgba(5,6,10,.2) 46%,transparent 62%)}
.gate .say>*{position:relative;z-index:1}
.gate .say h2{font-family:var(--serif);font-weight:300;
  font-size:clamp(2.4rem,1rem+3.9vw,4.4rem);line-height:.94;letter-spacing:-.042em;
  max-width:14ch;text-wrap:balance}
.gate .say h2 em{font-style:normal;color:var(--cu-live)}
.gate .say p{font-size:18px;color:var(--ink-3);margin-top:20px;max-width:32ch;
  text-wrap:pretty}
.gate .say .st{margin-top:26px;font-family:var(--mono);font-size:10px;color:var(--ink-4);
  letter-spacing:.1em;line-height:2.2}
.gate .say .st b{color:var(--cu);font-weight:400}
.gate .say .st i{font-style:normal;color:var(--patina)}

/* The panel. Glass, because this is chrome around an act. */
.gate .panel{position:relative;display:flex;flex-direction:column;justify-content:center;
  padding:clamp(26px,4vw,44px) clamp(20px,4vw,40px) 60px;
  background:rgba(8,10,14,.62);backdrop-filter:blur(16px) saturate(165%);
  border-left:1px solid var(--edge)}
@media(max-width:959px){.gate .panel{border-left:none;background:transparent;
  backdrop-filter:none;padding-top:34px}}
.gate .panel::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:0;
  background:linear-gradient(147deg,rgba(255,255,255,.05),transparent 46%)}
.gate .panel>*{position:relative;z-index:1;width:100%;max-width:520px;margin:0 auto}
/* Inside the panel the card furniture would be a box in a box. */
.gate .panel .card{background:none;border:none;backdrop-filter:none;padding:0;
  animation:none}
.gate .panel .card::before{display:none}
/* On a wide screen the stage says the headline, so the panel repeating it is
   the same sentence twice. Below 960px the stage is gone and the panel's
   own headline is the only one there is — so it is hidden by width, not
   deleted. */
@media(min-width:960px){.gate .panel .card>h1{display:none}}
@media(max-width:959px){.gate .panel .card>h1{margin-top:4px}}

/* ═══════════════════════════════════════════════════════════════════════
   THE STANDING ORDERS

   These are the only pages on the site where a stranger decides whether to
   trust us, so they are set for READING: one narrow column, generous
   leading, and a contents list that stays put. Nothing moves, nothing
   floats over the text, and there is no call to action anywhere on them.
   ═══════════════════════════════════════════════════════════════════════ */

.orders{display:grid;gap:34px;align-items:start}
@media(min-width:1000px){.orders{grid-template-columns:230px minmax(0,1fr);gap:56px}}

.toc{position:sticky;top:24px}
@media(max-width:999px){.toc{position:static}}
.toc .docs{list-style:none;display:grid;gap:1px;margin-top:12px}
.toc .docs a{display:block;padding:8px 11px;border-radius:8px;border-bottom:none;
  font-family:var(--sans);font-size:13.5px;color:var(--ink-3);
  transition:background 160ms var(--ease),color 160ms var(--ease)}
.toc .docs a:hover{background:rgba(180,100,42,.09);color:var(--cu-live)}
.toc .docs li.on a{background:rgba(180,100,42,.13);color:var(--cu-hot);font-weight:600}
.toc .secs{list-style:none;margin-top:12px;border-left:1px solid var(--edge);
  max-height:52vh;overflow-y:auto;scrollbar-width:thin;
  scrollbar-color:var(--edge-lit) transparent}
.toc .secs a{display:block;padding:5px 0 5px 13px;border-bottom:none;
  font-family:var(--sans);font-size:11.5px;line-height:1.45;color:var(--ink-4);
  border-left:1px solid transparent;margin-left:-1px;
  transition:color 160ms var(--ease),border-color 160ms var(--ease)}
.toc .secs a:hover{color:var(--cu-live);border-left-color:var(--cu)}
.toc .secs .l4 a{padding-left:24px}
.toc .secs .l5 a{padding-left:34px}

.order{max-width:74ch}
.order>header{padding-bottom:24px;border-bottom:1px solid var(--edge)}
.order h1{font-size:clamp(2rem,1rem+2.6vw,3.2rem);font-weight:300}
.order>header .meta{margin-top:12px}
.order .note.ok{border-left-color:var(--patina);color:#BFD8D2}

/* The document body. Longer measure and looser leading than a post — these
   are read start to finish, not scanned. */
.doc{margin-top:30px;font-size:18px;line-height:1.75}
.doc>*+*{margin-top:18px}
.doc h3{font-size:1.34rem;margin-top:44px;padding-top:6px;color:var(--ink)}
.doc h4{font-size:1.1rem;margin-top:32px}
/* Anchored headings get a quiet marker on hover, so a section can be linked
   to without a permalink icon shouting on every line. */
.doc h3,.doc h4,.doc h5{position:relative}
.doc h3::before,.doc h4::before{content:"§";position:absolute;left:-1.1em;
  color:var(--ink-4);opacity:0;font-family:var(--mono);font-size:.6em;top:.45em;
  transition:opacity 200ms var(--ease)}
.doc h3:hover::before,.doc h4:hover::before{opacity:1}
.doc h3:target,.doc h4:target{color:var(--cu-hot)}
.doc table{width:100%;border-collapse:collapse;font-family:var(--sans);font-size:14px;
  display:block;overflow-x:auto}
.doc th,.doc td{text-align:left;padding:9px 14px 9px 0;border-bottom:1px solid var(--edge);
  vertical-align:top}
.doc th{font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-4);
  font-weight:700}
.doc blockquote{color:var(--ink-2)}

.revs{margin-top:56px;padding-top:26px;border-top:1px solid var(--edge)}
.revs .hint{font-family:var(--sans);font-size:12.5px;color:var(--ink-4);line-height:1.6;
  margin-top:8px;max-width:60ch}
.revs ul{list-style:none;margin-top:16px;display:grid;gap:7px}
.revs li{display:flex;gap:12px;align-items:baseline;font-family:var(--sans);font-size:13px}
.revs li .mono{font-size:10px;color:var(--ink-4)}


/* ═══ THE IMPERSONATION BAR ═══════════════════════════════════════════
   Rendered by views/partials/ghostbar.php on EVERY layout, so it is
   styled in the shared sheet rather than the console's. It is meant to
   be impossible to miss: that is the feature, not an accident of
   colour choice.
   ═══════════════════════════════════════════════════════════════════ */
/* Fixed, full width, and it PUSHES content down rather than covering it:
   a banner that overlaps the page is a banner people scroll past. */
.ghostbar{
  position:fixed; inset:0 0 auto 0; z-index:40;
  display:flex; align-items:center; gap:.75rem;
  padding:.6rem 1.1rem;
  background:linear-gradient(90deg,#2A1408,#3A1B09 60%,#2A1408);
  border-bottom:1px solid var(--cu);
  font:500 .84rem/1.3 var(--sans); letter-spacing:.01em;
  box-shadow:0 1px 0 rgba(224,135,60,.25), 0 12px 40px -12px #000;
}
.ghostbar b{color:var(--cu-hot); font-weight:600;}
.ghostbar form{margin:0 0 0 auto;}
.ghostbar .dot{
  width:.55rem; height:.55rem; border-radius:50%;
  background:var(--cu-live); flex:0 0 auto;
  animation:pulse 1.9s var(--ease) infinite;
}
@keyframes pulse{
  0%,100%{opacity:1; box-shadow:0 0 0 0 rgba(224,135,60,.55);}
  50%    {opacity:.7; box-shadow:0 0 0 .45rem rgba(224,135,60,0);}
}

body.masked{padding-top:2.6rem;}

/* ═══ ROOMS ═══════════════════════════════════════════════════════════
   A conversation with more than two people in it (migration 014). Two
   things need saying visually and only two:
     · WHO said it, because sides of the page no longer answer that
     · that a SYSTEM LINE is not something anybody said
   ═══════════════════════════════════════════════════════════════════ */
.msgs .from{
  font-family:var(--sans); font-size:11px; font-weight:600; letter-spacing:.06em;
  text-transform:uppercase; color:var(--cu); margin-bottom:5px;
}
/* Centred, unattributed, no bubble. "Silicon entered this conversation" in
   a bubble reads as something Silicon SAID, which is the one reading it
   must never have. */
.msgs .sysline{
  list-style:none; text-align:center; margin:18px 0;
}
.msgs .sysline p{
  display:inline-block; padding:5px 14px;
  border:1px solid var(--edge); border-radius:999px;
  font-family:var(--sans); font-size:12px; color:var(--ink-3);
  background:var(--rise);
}
.convlist .tag{
  font-family:var(--sans); font-size:10.5px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--ink-4); margin-left:8px;
}

/* AWi paused. Where the composer was — deliberately plain and centred, so it
   reads as the instrument resting rather than as an error the member caused. */
.resting{
  text-align:center; margin:22px auto 6px; max-width:44ch;
  font-family:var(--body); font-size:15px; color:var(--ink-3);
}


/* ══════════════════════════════════════════════════════════════════════
   TOUCH · what a thumb needs that a mouse does not
   ══════════════════════════════════════════════════════════════════════
   ⚠ GATED ON THE POINTER, NOT ON THE WIDTH, and that distinction is the
   whole reason this block is safe to add. A width query would rewrite the
   design on a narrow desktop window — somebody dragging a browser to half
   a screen would get phone spacing on a mouse-driven machine, and the
   design L signed off would be one drag away from changing. `hover:none`
   and `pointer:coarse` together mean a finger, on any size of screen: a
   phone, a tablet, and a touchscreen laptop that also has a trackpad
   correctly stays fine-pointer.

   ⚠ AND IT IS ADDITIVE. Nothing here restyles anything — it raises floors.
   Every rule is a min-height, a min-width or a font-size that only ever
   goes up. The desktop rendering is byte-identical.

   Measured by bin/test_responsive.js at 320, 360, 390, 768, 1024 and 1440
   in real Chromium, because CSS breakpoints existing is evidence of
   intent and no evidence at all of behaviour. */
@media (hover:none) and (pointer:coarse){

  /* 24px is WCAG 2.2 AA (2.5.8) and it is a floor, not a target. The
     masthead brand measured 122×23 — one pixel short, which is exactly the
     kind of miss that only a measurement finds. */
  .mast .brand,
  .mast nav a,
  .foot a{
    display:inline-flex; align-items:center;
    min-height:24px;
  }
  /* The footer links sit in a row, so vertical padding would push the
     footer taller for nothing; the height comes from the flex box above
     and the horizontal room from here. */
  .foot a{padding:0 2px}
  .mast nav a{padding:0 3px}

  /* ⚠ 16px ON EVERY FIELD, AND IT IS NOT ABOUT LEGIBILITY.
     iOS Safari ZOOMS THE PAGE when a field with a font smaller than 16px
     takes focus, and it does not zoom back out. Tapping the search box on
     the Directory would leave the whole site enlarged and scrolled
     sideways, with no obvious way back — which reads as the site breaking
     at the exact moment somebody first tries to use it. This is the single
     most-felt line in the block. */
  input, select, textarea{font-size:16px}

  /* ⚠ AND A SELECT MUST NOT SET THE PAGE'S WIDTH. A <select> sizes itself
     to its WIDEST OPTION, so raising the font to 16px above made the
     Contact form's subject list wider than the phone and the whole page
     scrolled sideways — a fix for one measurement creating a worse one.
     Caught by re-running the same probe rather than by assuming the change
     was free. */
  select{width:100%; max-width:100%; box-sizing:border-box}

  /* A 19px select is a coin toss with a thumb. 44px is Apple's own
     guidance and comfortably clears the 24px floor. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  select,
  button,
  .btn{min-height:44px}
  textarea{min-height:88px}

  /* The small monospace labels are a real part of the look and they stay
     small — 12px rather than 9.5px is still a caption and is still
     readable at arm's length on a phone. */
  .readout, .letter, .hd, .mh, .msg, .hint,
  label, .f label, .s, .d,
  /* Restated with the same prefix the base rules use, because `.f .msg`
     and a bare `.eyebrow` are two different specificities and only one of
     them loses to a single class. Every one of these was measured
     failing, not guessed at. */
  /* `.gate .say .st` is only visible at 960px and up, which on an iPad in
     landscape is a touch device — so a rule written for a desktop side
     panel lands on a tablet. Every one of these was found by measuring at
     the six sizes, not by reading the stylesheet. */
  .f .msg, .eyebrow, .poll .msg, .gate .say .st{font-size:12px}

  /* ⚠ THE HINT WRAPS INSTEAD OF BEING PUSHED OFF THE EDGE.
     `.f>label` is a flex row with the hint on `margin-left:auto`, which is
     right on a laptop and impossible on a phone — "or what you would like
     to be called" simply ran past the right edge and was cut. Wrapping the
     label puts it on its own line, where it is a caption rather than a
     casualty. */
  .f>label{flex-wrap:wrap; row-gap:3px}
  .f>label .hint{margin-left:0; flex:1 0 100%}
}
