/*! ascii/ui kit 1.3.1 | MIT | (c) 2026 Fede Kotek */
/* ascii-ui.css
   Components in an ASCII skin. Frames, fills and shadows are strings of
   characters; weight comes from how dense the character is.

   Use: link this file and ascii-ui.js, then paste the html of any component
   from https://ascii.fedekotek.design (open Code on it). Theme it by overriding the
   tokens below on :root, after this file.

   The file is split into blocks. Each block starts with a line like
     /* ==== button: .btn ==== *\/
   The site's Code tab reads those lines to print the block a component needs,
   so keep them if you edit this file.

   MIT license. Copyright (c) 2026 Fede Kotek. The full text is in LICENSE.txt
   next to this file, and in README.md. */

/* Geist Mono, a Latin-1 subset, from fonts/ next to this file: no request to
   anyone else. An installed Geist Mono wins; without either, the system
   monospace. Copy fonts/ along with this file, or drop this rule and bring
   your own. Geist Mono is SIL OFL 1.1, Copyright 2024 The Geist Project
   Authors; the license is fonts/OFL.txt. */
@font-face{
  font-family:"Geist Mono";font-style:normal;font-weight:100 900;font-display:swap;
  src:local("Geist Mono"),url("fonts/geist-mono-latin.woff2") format("woff2");
}

/* ==== tokens: :root ==== */
/* Ten colors. Magenta (--hot) acts, cyan (--cy) is focus and nothing else,
   lime (--ok) confirms, yellow (--warn) warns. --r is one row: every height is
   a number of rows and every width a number of characters (ch). */
:root{
  --bg:#ecebe4; --ink:#111110; --muted:#5c5b55;
  --hot:#c91468; --pink:#e8478f; --cy:#0a7287; --ok:#3f6b00; --warn:#8a5a00;
  --deep:#2b2bd1; --violet:#6a45d9;
  --t0:#111110; --t1:#111110; --t2:#c91468; --t3:#2b2bd1;   /* title rows */
  --accent:var(--cy);    /* focus */
  --danger:var(--warn);  /* errors and destructive actions */
  --r:21px;              /* one row: 14px type on a 21px line */
  --font:"Geist Mono",ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --scan:rgba(17,17,16,.06);   /* the crt option's scanlines, one line in four */
  color-scheme:light;
}
/* Motion. Every animation in the kit takes its time and its steps from these.
   Motion is steps, never eased: a step is a whole character or a whole row.
   Set a duration to 0s to switch that motion off; reduced motion switches off
   all of it. --aui-toast is the shortest a toast stays: longer words stay
   longer (ascii-ui.js reads it) */
:root{
  --aui-quick:.16s;              /* a switch flips, a tooltip types in, a panel wipes open */
  --aui-base:.24s;               /* a toast types in, an alert dialog nudges */
  --aui-slow:.28s;               /* a sheet comes up */
  --aui-toast:3.6s;              /* a toast of a few words stays this long */
  --aui-ease-flip:steps(2);      /* the switch thumb, one character a step */
  --aui-ease-type:steps(12);     /* the tooltip and the toast, typed in from the left */
  --aui-ease-wipe:steps(4);      /* a panel, a row a step */
  --aui-ease-rise:steps(7);      /* the sheet, from the bottom */
  --aui-ease-jolt:steps(1,end);  /* the nudge: no in-betweens */
}
/* dark follows the system unless the page says data-theme="light" */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:#0a0612; --ink:#f3eef7; --muted:#a79db5;
    --hot:#ff3d9a; --pink:#ffb3d9; --cy:#35e6f0; --ok:#c8f02c; --warn:#ffd23f;
    --deep:#4a3dff; --violet:#9b7bea;
    --t0:#f3eef7; --t1:#ffb3d9; --t2:#ff3d9a; --t3:#9b7bea;
    --scan:rgba(0,0,0,.28);
    color-scheme:dark;
  }
}
/* force a theme with <html data-theme="dark"> or <html data-theme="light"> */
:root[data-theme="dark"]{
  --bg:#0a0612; --ink:#f3eef7; --muted:#a79db5;
  --hot:#ff3d9a; --pink:#ffb3d9; --cy:#35e6f0; --ok:#c8f02c; --warn:#ffd23f;
  --deep:#4a3dff; --violet:#9b7bea;
  --t0:#f3eef7; --t1:#ffb3d9; --t2:#ff3d9a; --t3:#9b7bea;
  --scan:rgba(0,0,0,.28);
  color-scheme:dark;
}

/* ==== tones: .tone-heavy .tone-dense .tone-mid .tone-light .tone-shade .tone-faint .tone-danger .tone-error ==== */
/* The border strings. --h-* runs along the top and bottom, --s-* is one side,
   --v-* is a wall (one side per row). ascii-ui.js can rebuild them with other
   characters: ASCIIUI.tones({"@":"#"}).
   Limits: a frame is 400 characters wide (about 3,400px at 14px type) and
   a wall 200 rows tall (4,200px). Past that the string ends and the rim
   stops short. */
:root{
  --k8:"@"; --k6:"#"; --k1:".";
  --h-heavy:"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@";
  --s-heavy:"@@";
  --v-heavy:"@@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A @@\A ";
  --h-dense:"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%";
  --s-dense:"%%";
  --v-dense:"%%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A %%\A ";
  --h-mid:"################################################################################################################################################################################################################################################################################################################################################################################################################";
  --s-mid:"##";
  --v-mid:"##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ##\A ";
  --h-light:"================================================================================================================================================================================================================================================================================================================================================================================================================";
  --s-light:"::";
  --v-light:"::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ";
  --h-shade:"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::";
  --s-shade:"::";
  --v-shade:"::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ::\A ";
  --h-faint:"- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ";
  --s-faint:": ";
  --v-faint:": \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A : \A ";
  --h-danger:"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////";
  --s-danger:"//";
  --v-danger:"//\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A //\A ";
  --h-error:"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
  --s-error:"!!";
  --v-error:"!!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A !!\A ";
}
.tone-heavy{--h:var(--h-heavy);--s:var(--s-heavy);--v:var(--v-heavy)}
.tone-dense{--h:var(--h-dense);--s:var(--s-dense);--v:var(--v-dense)}
.tone-mid{--h:var(--h-mid);--s:var(--s-mid);--v:var(--v-mid)}
.tone-light{--h:var(--h-light);--s:var(--s-light);--v:var(--v-light)}
.tone-shade{--h:var(--h-shade);--s:var(--s-shade);--v:var(--v-shade)}
.tone-faint{--h:var(--h-faint);--s:var(--s-faint);--v:var(--v-faint)}
.tone-danger{--h:var(--h-danger);--s:var(--s-danger);--v:var(--v-danger)}
.tone-error{--h:var(--h-error);--s:var(--s-error);--v:var(--v-error)}

/* ==== base: body .muted .vh .row .stack .demo .group fieldset ==== */
*,*::before,*::after{box-sizing:border-box}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--font);font-size:14px;line-height:var(--r);font-weight:300;
  font-variant-ligatures:none;font-feature-settings:"liga" 0,"calt" 0;
  -webkit-text-size-adjust:100%;
}
h1,h2,h3,p,ul,ol,dl,dd,pre,figure{margin:0;font:inherit}
::selection{background:var(--hot);color:var(--bg)}
[hidden]{display:none !important}
/* links are ink with a magenta underline: blue is focus, so no browser blue */
a{color:var(--ink);text-decoration-color:var(--hot);text-underline-offset:3px}
a:focus-visible{outline:0;background:var(--accent);color:var(--bg)}
.muted{color:var(--muted);font-weight:400}   /* grey reads thinner, so it gets 400 */
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--r) 2ch}
.stack{display:grid;gap:var(--r);justify-items:start}
/* a stack in a stack is a box around a component and its parts: it takes the
   width, so a chart or a table in it can too (what is in it still starts left) */
.stack > .stack{justify-self:stretch;min-width:0}
/* a page made from the Code tab says so when this file did not load; here it goes */
.aui-nokit{display:none !important}
.demo{margin-top:var(--r)}
.demo:first-child{margin-top:0}
.status{min-height:var(--r)}
.group{max-width:48ch}
.group + .group{margin-top:var(--r)}
fieldset{border:0;padding:0;margin:0;min-width:0}
legend{padding:0;margin-bottom:var(--r)}
/* motion is steps(), never eased. Reduced motion switches all of it off */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none !important;transition:none !important}
}
/* Scanlines are off: they stripe every slab, print as grey and are not
   characters. <html class="crt"> brings them back, over the page and over an
   open dialog (the top layer is above the page's own layer) */
:root.crt body::before{
  content:"";position:fixed;inset:0;pointer-events:none;z-index:250;
  background:repeating-linear-gradient(to bottom,transparent 0 3px,var(--scan) 3px 4px);
}
:root.crt dialog[open]::after{
  content:"";position:absolute;inset:0;z-index:5;pointer-events:none;background-attachment:fixed;
  background-image:repeating-linear-gradient(to bottom,transparent 0 3px,var(--scan,rgba(0,0,0,.12)) 3px 4px);
}

/* ==== frame: .frame .mid ==== */
/* A frame is a string on top and bottom (--h) and a string on each side (--s).
   The characters are paint: the alt text after the slash is empty, so screen
   readers skip them. The line before is for browsers without alt text. */
.frame{position:relative;padding:var(--r) 0}
.frame::before,.frame::after{
  content:var(--h);content:var(--h) / "";
  position:absolute;left:0;right:0;height:var(--r);
  overflow:hidden;white-space:nowrap;font-weight:700;pointer-events:none;
  color:var(--frame-color,var(--ink));
}
.frame::before{top:0}
.frame::after{bottom:0;content:var(--hb,var(--h));content:var(--hb,var(--h)) / ""}
.mid{display:flex}
.mid::before,.mid::after{
  content:var(--s);content:var(--s) / "";
  flex:none;white-space:pre;font-weight:700;
  color:var(--frame-color,var(--ink));
}
.mid::after{content:var(--sr,var(--s));content:var(--sr,var(--s)) / ""}

/* ==== button: .btn ==== */
/* The rim says how loud: @ primary, = default, / danger. */
.btn{
  display:inline-block;font:inherit;color:var(--ink);background:none;border:0;border-radius:0;
  margin:0;padding-left:0;padding-right:0;cursor:pointer;text-transform:uppercase;white-space:pre;
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  outline:2px solid transparent;outline-offset:-2px;
}
.btn .mid{display:inline-flex}
.btn .label{padding:0 1ch}
.btn-primary{--frame-color:var(--hot)}
.btn-primary .label{background:var(--hot);color:var(--bg)}
.btn-danger{--frame-color:var(--danger)}
.btn-danger .label{color:var(--danger)}
@media (hover:hover) and (pointer:fine){
  .btn:not(:disabled):hover{--h:var(--h-mid);--s:var(--s-mid)}
  .btn-primary:not(:disabled):hover{--h:var(--h-dense);--s:var(--s-dense)}
  .btn-danger:not(:disabled):hover{--h:var(--h-danger);--s:var(--s-danger)}
  .btn-danger:not(:disabled):hover .label{background:var(--danger);color:var(--bg)}
}
.btn:not(:disabled):active{background:var(--ink);--frame-color:var(--bg)}
.btn:not(:disabled):active .label{background:var(--ink);color:var(--bg)}
.btn-primary:not(:disabled):active .label{background:var(--bg);color:var(--ink)}
.btn:focus-visible{--h:var(--h-heavy);--s:var(--s-heavy);--frame-color:var(--accent)}
/* the primary rim is @ already, so a focus that only recolours it is a few
   pixels: its slab takes the focus color too */
.btn-primary:focus-visible .label{background:var(--accent);color:var(--bg)}
.btn:disabled{--h:var(--h-faint);--s:var(--s-faint);--frame-color:var(--muted);color:var(--muted);cursor:not-allowed}
.btn-primary:disabled .label{background:var(--muted);color:var(--bg)}
.btn-danger:disabled .label{color:var(--muted)}   /* a disabled danger button is gray too, not yellow */

/* ==== field: .field .field-label .error .count ==== */
/* Input, select and textarea share one frame. Focus turns it heavy and cyan,
   .invalid turns it into a wall of !. */
.field-label{display:block;font-weight:400}
.field{cursor:text}
.field:has(select){cursor:pointer}
.field .mid{gap:1ch}
.field .prompt{font-weight:700;color:var(--hot)}
.field input,.field select{
  font:inherit;color:var(--ink);background:none;border:0;border-radius:0;outline:0;
  padding:0;margin:0;flex:1;min-width:0;height:var(--r);caret-color:var(--hot);
}
.field select{appearance:none;-webkit-appearance:none;cursor:pointer}
/* a search field's clear x: the text's color, magenta under a pointer, never the browser's blue */
.field input[type="search"]::-webkit-search-cancel-button{
  -webkit-appearance:none;appearance:none;width:1ch;height:1em;margin:0;cursor:pointer;background:var(--muted);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2 2l6 6M8 2l-6 6' stroke='%23000' stroke-width='1.6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2 2l6 6M8 2l-6 6' stroke='%23000' stroke-width='1.6'/%3E%3C/svg%3E") center/contain no-repeat;
}
.field input[type="search"]::-webkit-search-cancel-button:hover{background:var(--hot)}
.field select option{background:var(--bg);color:var(--ink)}
.field:focus-within{--h:var(--h-heavy);--s:var(--s-heavy);--v:var(--v-heavy);--frame-color:var(--accent)}
.field.invalid{--h:var(--h-error);--s:var(--s-error);--v:var(--v-error);--frame-color:var(--danger)}
.field.invalid .prompt{color:var(--danger)}
/* wrong and focused: the wall of ! stays, in the focus color, so you can see where you are */
.field.invalid:focus-within{--frame-color:var(--accent)}
/* disabled reads the way a disabled button does: a faint rim, gray text */
.field:has(:disabled){--h:var(--h-faint);--s:var(--s-faint);--v:var(--v-faint);--frame-color:var(--muted);cursor:not-allowed}
.field:has(:disabled) :disabled,.field:has(:disabled) .prompt{color:var(--muted);cursor:not-allowed}
.error{color:var(--danger)}
.error:empty{display:none}
/* textarea: the same frame, walls instead of sides */
.field.area .body{padding:0 3ch}
.field.area .body::before,.field.area .body::after{color:var(--frame-color,var(--ink))}
.field.area textarea{
  display:block;width:100%;font:inherit;color:var(--ink);background:none;border:0;border-radius:0;
  outline:0;resize:none;padding:0;margin:0;caret-color:var(--hot);
}
.count{color:var(--muted);text-align:right}
.count.full{color:var(--warn);font-weight:700}

/* ==== check: .check .switch-track ==== */
/* Checkbox, radio and switch. The input is real and invisible, the glyph is paint. */
.check{
  position:relative;display:inline-flex;gap:1ch;cursor:pointer;
  padding:12px 0;margin:-12px 0;-webkit-tap-highlight-color:transparent;
}
.check input{position:absolute;opacity:0;width:1px;height:1px;margin:0;left:0;top:12px}
.check .glyph{white-space:pre;font-weight:700}
.check input:checked + .glyph{color:var(--ok)}
.check input:focus-visible + .glyph{background:var(--accent);color:var(--bg)}
.check input[type="checkbox"]:not([role]) + .glyph::before{content:"[ ]"}
.check input[type="checkbox"]:not([role]):checked + .glyph::before{content:"[" var(--k8) "]"}
.check input[type="radio"] + .glyph::before{content:"( )"}
.check input[type="radio"]:checked + .glyph::before{content:"(" var(--k8) ")"}
.switch-track{position:relative;display:inline-block;width:4ch;text-align:left}
.switch-track::after{content:var(--k1) var(--k1) var(--k1) var(--k1);position:absolute;left:0;top:0;color:var(--muted)}
.switch-track::before{
  content:var(--k8) var(--k8);position:relative;z-index:1;display:inline-block;background:var(--bg);
  transition:transform var(--aui-quick) var(--aui-ease-flip);
}
.check input:checked + .glyph .switch-track::before{transform:translateX(2ch)}
.check input:focus-visible + .glyph .switch-track::before{background:var(--accent)}
.check input:focus-visible + .glyph .switch-track::after{color:var(--bg)}

/* ==== chart: .chart .spark ==== */
/* data-aui="chart" around a <table>. Without the script the table is what
   shows. With it, ascii-ui.js draws the characters in a .plot above it, one
   row of var(--r) a line, and keeps the table for screen readers only.
   data-type="spark" on a .spark is a word-sized trend. */
.chart{display:block;position:relative;max-width:100%;justify-self:stretch;min-width:0}
.chart:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.chart .plot{display:block;white-space:pre;overflow:hidden;font-weight:700;cursor:crosshair;user-select:none;-webkit-user-select:none}
.chart table{border-collapse:collapse}
.chart caption{text-align:left;font-weight:700}
.chart th,.chart td{padding:0 2ch 0 0;text-align:left;font-weight:inherit}
.chart td{text-align:right}
.chart.drawn{overflow:hidden}   /* the table below is as wide as its words: kept out of the page width */
.chart.drawn table{position:absolute;left:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.spark{font-weight:700;color:var(--hot);white-space:pre}

/* ==== bar: .bar .slider .slider-track .progress ==== */
/* Slider and progress are one halftone bar of characters. ascii-ui.js draws
   it (data-aui="slider", data-aui="progress"); a real range input sits on top. */
.bar{font-weight:700;white-space:pre;display:block}
.slider{display:flex;flex-wrap:wrap;gap:0 2ch;align-items:flex-end}
.slider label{flex-basis:100%}
.slider-track{position:relative;display:inline-block;min-width:24ch;min-height:var(--r)}
.slider-track input{
  position:absolute;inset:-12px 0;width:100%;height:calc(100% + 24px);
  margin:0;opacity:0;cursor:pointer;
}
.slider-track:has(input:focus-visible){color:var(--accent)}
.slider-track:has(input:focus-visible) .bar span{color:var(--accent) !important}
.progress{display:flex;gap:2ch;align-items:flex-end}
.progress .bar{min-width:24ch;min-height:var(--r)}
.progress .pct{white-space:pre}

/* ==== datatable: [data-aui="datatable"] .dt-tools .dt-count .dt-sort ==== */
/* A .tbl that sorts, filters, picks rows and pages. data-aui="datatable" on
   the box around it. The sort mark is paint: aria-sort on the th says it. */
[data-aui="datatable"]{display:grid;gap:var(--r);min-width:0;justify-self:stretch}
/* the count sits on the field's middle row, the one you type on */
.dt-tools{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--r) 2ch}
.dt-tools .group{flex:1 1 24ch}
.dt-count{white-space:nowrap;margin-bottom:var(--r)}
.dt-sort{
  font:inherit;font-weight:inherit;color:inherit;text-transform:inherit;text-align:inherit;background:none;border:0;border-radius:0;
  margin:-12px -1ch;padding:12px 1ch;cursor:pointer;outline:0;white-space:nowrap;-webkit-tap-highlight-color:transparent;
}
/* the mark is on the sorted column only; the rest keep its two places blank,
   so a sort never moves a column. A pointer over one shows where it goes */
.dt-sort::after{content:"  ";content:"  " / "";white-space:pre}
[aria-sort="ascending"] > .dt-sort::after{content:" ^";content:" ^" / ""}
[aria-sort="descending"] > .dt-sort::after{content:" v";content:" v" / ""}
.dt-sort:focus-visible{background:var(--accent);color:var(--bg)}
@media (hover:hover) and (pointer:fine){[aria-sort="none"] > .dt-sort:hover::after{content:" ^";content:" ^" / "";opacity:.6}}
/* a column of numbers lines up on the right, so a sort by value shows (the kit marks it) */
.tbl .dt-num{text-align:right}
/* the checkbox column: the glyph takes the row's color, so it reads on a slab */
.dt-pick{width:5ch;-webkit-user-select:none;user-select:none}
.dt-pick .check .glyph,.dt-pick .check input:checked + .glyph{color:inherit}
.dt-pick .check input:focus-visible + .glyph{background:var(--accent);color:var(--bg)}
/* some rows picked: the script marks the label (.dt-mixed), since an :indeterminate rule is checked against every box on the page and made pasting a thousand radios twice as slow */
.check.dt-mixed .glyph::before{content:"[-]";content:"[-]" / ""}
/* a picked row: its box is a slab that says [@], the row is bold on a
   darker tint, and a badge keeps its color */
.tbl tbody tr.dt-on{font-weight:700}
.tbl tbody tr.dt-on > .dt-pick{background:var(--ink);color:var(--bg)}
/* stripes count the rows that show, not the ones a filter or a page hid */
[data-aui="datatable"] .tbl tbody tr:nth-child(even){background:none}
[data-aui="datatable"] .tbl tbody tr:nth-child(even of :not([hidden],.dt-skel,.dt-empty)){background:color-mix(in srgb,var(--ink) 7%,transparent)}
[data-aui="datatable"] .tbl tbody tr.dt-on:nth-child(n){background:color-mix(in srgb,var(--ink) 16%,transparent)}
.dt-empty td{color:var(--muted);white-space:normal}
.dt-skel td{color:var(--muted);font-weight:700;overflow:hidden}
/* no rows match: a way back to all of them */
.dt-clear{
  font:inherit;color:var(--ink);background:none;border:0;border-radius:0;padding:12px 0;margin:-12px 0 -12px 1ch;
  cursor:pointer;outline:0;text-decoration:underline;text-decoration-color:var(--hot);text-underline-offset:3px;
}
.dt-clear:focus-visible{background:var(--accent);color:var(--bg)}
/* wider than its box: it scrolls inside it, and says so under the table */
[data-aui="datatable"] .tablewrap{position:relative}
[data-aui="datatable"] .tablewrap[data-wide]::after{
  content:"<< scrolls sideways >>";content:"<< scrolls sideways >>" / "";
  display:block;position:sticky;left:0;height:var(--r);color:var(--muted);white-space:nowrap;
}

/* ==== tabs: .tablist .tab .tabpanel ==== */
/* The active tab is a slab. data-aui="tabs" on the tablist wires the arrows. */
.tablist{display:flex;flex-wrap:wrap;position:relative;padding-bottom:var(--r)}
.tablist::after{
  content:var(--h-light);content:var(--h-light) / "";
  position:absolute;left:0;right:0;bottom:0;height:var(--r);
  overflow:hidden;white-space:nowrap;font-weight:700;color:var(--violet);
}
.tab{
  font:inherit;color:var(--muted);background:none;border:0;border-radius:0;margin:0;
  padding:12px 2ch;cursor:pointer;text-transform:uppercase;
  outline:2px solid transparent;outline-offset:-2px;-webkit-tap-highlight-color:transparent;
}
.tab[aria-selected="true"]{background:var(--ink);color:var(--bg)}
@media (hover:hover) and (pointer:fine){.tab[aria-selected="false"]:hover{color:var(--ink)}}
.tab:focus-visible{background:var(--accent);color:var(--bg)}
.tabpanel{padding:var(--r) 0;outline:0;position:relative}
/* a focused panel gets a wall of @ down its left edge, in the focus color, in
   the column its content leaves free */
.tabpanel:focus-visible::before{
  content:var(--v-heavy);content:var(--v-heavy) / "";position:absolute;left:0;top:var(--r);bottom:var(--r);width:1ch;
  overflow:hidden;white-space:pre;font-weight:700;color:var(--accent);pointer-events:none;
}
.tabpanel pre{overflow-x:auto;white-space:pre;padding-left:2ch}
.tabpanel .row{padding-left:2ch}

/* ==== card: .card .lift .bar-title .body ==== */
/* Title on a bar of @, walls of the tone's character, and a shadow of colons
   from .lift, the wrapper. */
.lift{position:relative;padding:0 2ch var(--r) 0;max-width:50ch}
.lift::before{
  content:var(--v-shade);content:var(--v-shade) / "";
  position:absolute;right:0;top:var(--r);bottom:var(--r);width:2ch;
  overflow:hidden;white-space:pre;font-weight:700;color:var(--deep);
}
.lift::after{
  content:var(--h-shade);content:var(--h-shade) / "";
  position:absolute;left:2ch;right:0;bottom:0;height:var(--r);
  overflow:hidden;white-space:nowrap;font-weight:700;color:var(--deep);
}
.card{background:var(--bg)}
.card::before{content:var(--h-heavy);content:var(--h-heavy) / ""}
.bar-title{
  position:absolute;top:0;left:2ch;z-index:1;padding:0 1ch;margin:0;
  background:var(--hot);color:var(--bg);text-transform:uppercase;font:inherit;
  max-width:calc(100% - 4ch);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.body{position:relative;padding:var(--r) 4ch}
.body::before,.body::after{
  content:var(--v);content:var(--v) / "";
  position:absolute;top:0;bottom:0;width:2ch;
  overflow:hidden;white-space:pre;font-weight:700;color:var(--frame-color,inherit);
}
.body::before{left:0}
.body::after{right:0}
.body .row{margin-top:var(--r)}
.card dl{display:grid;grid-template-columns:9ch 1fr}
.card dt{color:var(--muted)}

/* ==== dialog: dialog ==== */
/* A native <dialog> holding a card. data-aui-open="id" on a button opens it,
   data-aui-close inside closes it, and so do Escape and the page around it. */
dialog{
  font:inherit;color:var(--ink);background:none;border:0;padding:0;overflow:visible;
  width:calc(var(--dcols,36)*1ch);max-width:calc(100vw - 2ch);
}
dialog .lift{max-width:none}
dialog .row{justify-content:flex-end;column-gap:1ch}
dialog::backdrop{
  background-color:rgba(236,235,228,.9);
  background-color:color-mix(in srgb,var(--bg,Canvas) 92%,transparent);
}
/* The veil behind a dialog is characters on the grid: a period every other
   column on every row, in faint magenta. One row string (160 periods, about
   2,700px), doubled up to 128 rows, drawn from the window's corner and
   clipped to it. A modal dialog is in the top layer, so it draws its own */
dialog{
  --veil-r:". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \A ";
  --veil-4:var(--veil-r) var(--veil-r) var(--veil-r) var(--veil-r);
  --veil-16:var(--veil-4) var(--veil-4) var(--veil-4) var(--veil-4);
  --veil-64:var(--veil-16) var(--veil-16) var(--veil-16) var(--veil-16);
}
dialog[open]::before{
  content:var(--veil-64) var(--veil-64);content:var(--veil-64) var(--veil-64) / "";
  position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden;
  white-space:pre;font-weight:700;line-height:var(--r);
  color:rgba(201,20,104,.45);color:color-mix(in srgb,var(--hot) 45%,transparent);
}
body:has(dialog[open]){overflow:hidden}   /* the page behind stays put */
/* the way out, in the title row: [x] */
.sheet-x{
  position:absolute;top:calc(var(--r)*-0.5);right:2ch;z-index:4;
  font:inherit;font-weight:700;background:var(--bg);border:0;border-radius:0;
  color:var(--ink);padding:12px 1ch;line-height:var(--r);cursor:pointer;outline:0;
}
.sheet-x:focus-visible{background:var(--accent);color:var(--bg)}
@media (hover:hover) and (pointer:fine){.sheet-x:hover{color:var(--hot)}}

/* ==== sheet: .sheet ==== */
/* A dialog that comes up from the bottom in seven steps. */
dialog.sheet{margin:auto 0 0 0;width:100%;max-width:100%;max-height:82vh;max-height:82dvh;flex-direction:column}
dialog.sheet[open]{display:flex}
dialog.sheet > .lift,dialog.sheet .card{min-height:0;display:flex;flex-direction:column;flex:1}
dialog.sheet .lift{padding:0;max-width:none}
dialog.sheet .lift::before,dialog.sheet .lift::after{display:none}
dialog.sheet[open] .lift{animation:aui-sheetin var(--aui-slow) var(--aui-ease-rise) both}
@keyframes aui-sheetin{from{transform:translateY(100%)}to{transform:none}}
dialog.sheet .card{padding-bottom:calc(var(--r) + env(safe-area-inset-bottom,0px))}
dialog.sheet .card::before,dialog.sheet .card::after{background:var(--bg)}
dialog.sheet .body{max-width:80ch;width:100%;margin:0 auto;min-height:0;flex:1;overflow-y:auto;overscroll-behavior:contain}
dialog.sheet .sheet-x{right:max(2ch,calc(50% - 40ch))}
/* On a touch screen a sheet goes down the way it came up: drag it by its
   title, or from the top of what it holds, and let go past a quarter of its
   height or with a flick. It follows the finger a row at a time. The grip is
   the row of = above the title */
@media (pointer:coarse){
  dialog.sheet > .lift{touch-action:pan-x pinch-zoom}
  dialog.sheet > .lift::before{
    display:block;content:"========";content:"========" / "";position:absolute;left:2ch;right:auto;top:calc(var(--r)*-1);bottom:auto;
    width:8ch;height:var(--r);overflow:hidden;white-space:pre;font-weight:700;color:var(--muted);background:var(--bg);
  }
  dialog.sheet .body{touch-action:pan-y pinch-zoom}
}

/* ==== alert: .alert ==== */
/* A card with a hazard rim. .info is violet: cyan is focus and nothing else. */
.alert{--frame-color:var(--warn);max-width:50ch}
.alert .card::before{content:var(--h);content:var(--h) / ""}
.alert .bar-title{background:var(--warn)}
.alert.info{--frame-color:var(--violet)}
.alert.info .bar-title{background:var(--violet)}

/* ==== badge: .badge ==== */
.badge{padding:0 1ch;text-transform:uppercase;font-weight:700;background:var(--ink);color:var(--bg);white-space:nowrap}
.b-ok{background:var(--ok)}
.b-warn{background:var(--warn)}
.b-hot{background:var(--hot)}
.b-out{background:none;color:var(--ink);padding:0}
.b-out::before{content:"[";color:var(--muted)}
.b-out::after{content:"]";color:var(--muted)}

/* ==== avatar: .avatar ==== */
.avatar{
  display:inline-grid;place-items:center;width:8ch;height:calc(var(--r)*3);flex:none;overflow:hidden;
  background:var(--ink);color:var(--bg);font-weight:700;
}
.avatar.sm{width:5ch;height:calc(var(--r)*2)}
.avatar.hot{background:var(--hot)}
.avatar.ok{background:var(--ok)}
.avatar img,.avatar canvas{width:100%;height:100%;display:block;object-fit:cover}

/* ==== crumbs: .crumbs ==== */
.crumbs{list-style:none;display:flex;flex-wrap:wrap;padding:0;margin:0}
.crumbs li + li::before{content:"/";content:"/" / "";color:var(--hot);font-weight:700;padding:0 1ch}
.crumbs a{color:var(--muted);text-decoration:none;outline:0;padding:12px 0;margin:-12px 0;display:inline-block}
.crumbs a:focus-visible{background:var(--accent);color:var(--bg)}
@media (hover:hover){.crumbs a:hover{color:var(--ink);text-decoration:underline}}
.crumbs [aria-current] span{background:var(--ink);color:var(--bg);padding:0 1ch;font-weight:700}

/* ==== ibtn: .ibtn ==== */
/* A character in brackets that is a button: [<] [3] [>] */
.ibtn{font:inherit;font-weight:700;background:none;border:0;border-radius:0;color:var(--ink);padding:12px 1ch;margin:-12px 0;cursor:pointer;outline:0}
.ibtn::before{content:"[";content:"[" / "";color:var(--muted)}
.ibtn::after{content:"]";content:"]" / "";color:var(--muted)}
.ibtn:focus-visible{background:var(--accent);color:var(--bg)}
.ibtn:disabled,.ibtn[aria-disabled="true"]{color:var(--muted);cursor:not-allowed}
a.ibtn{display:inline-block;text-decoration:none}   /* data-href pagination draws links */
.ibtn[aria-current="page"]{background:var(--ink);color:var(--bg);background-clip:content-box}
/* focus beats the current page: the slab turns blue, brackets and all */
.ibtn[aria-current="page"]:focus-visible{background:var(--accent);color:var(--bg)}
.ibtn:focus-visible::before,.ibtn:focus-visible::after{color:inherit}

/* ==== calendar: .cal ==== */
/* data-aui="calendar" fills it with a month of buttons. Today is magenta, the pick is a slab. */
.cal{display:inline-block}
.cal-head{display:flex;justify-content:space-between;align-items:center;text-transform:uppercase;font-weight:700}
.cal-grid{display:grid;grid-template-columns:repeat(7,4ch);margin-top:var(--r)}
.cal-grid span{color:var(--muted);text-align:center}
.cal-grid button{font:inherit;background:none;border:0;border-radius:0;color:var(--ink);padding:12px 0;cursor:pointer;outline:0;text-align:center}
.cal-grid button.today{color:var(--hot);font-weight:700}
.cal-grid button:disabled{color:var(--muted);font-weight:300;cursor:not-allowed}   /* outside data-min and data-max */
.cal-grid button[aria-pressed="true"]{background:var(--ink);color:var(--bg);font-weight:700}
.cal-grid button:focus-visible{background:var(--accent);color:var(--bg)}

/* ==== pagination: [data-aui="pagination"] ==== */
/* data-aui="pagination" on a <nav> draws [<] [1] .. [3] .. [9] [>] */
[data-aui="pagination"]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--r) 0}
[data-aui="pagination"] .muted{padding:0 1ch}

/* ==== dropdown: .pop .menu ==== */
/* data-aui="dropdown" on .pop: the button opens the [role=menu], arrows move,
   Escape and Tab close. */
.pop{position:relative;display:inline-block}
.menu{position:absolute;left:0;top:calc(var(--r)*3);z-index:30;width:28ch;max-width:calc(100vw - 6ch);background:var(--bg)}
.menu [role="menuitem"]{
  display:flex;justify-content:space-between;width:100%;font:inherit;background:none;border:0;border-radius:0;
  color:var(--ink);padding:calc(var(--r)/2) 0;margin:0;text-align:left;cursor:pointer;outline:0;   /* two whole rows, a thumb tall */
}
.menu [role="menuitem"]:disabled,.menu [role="menuitem"][aria-disabled="true"]{color:var(--muted);cursor:not-allowed}
.menu [role="menuitem"]:focus-visible{background:var(--ink);color:var(--bg)}
.menu [role="menuitem"].danger{color:var(--warn)}
.menu [role="menuitem"].danger:focus-visible{background:var(--warn);color:var(--bg)}
@media (hover:hover) and (pointer:fine){
  .menu [role="menuitem"]:not(:disabled):not([aria-disabled="true"]):hover{background:var(--ink);color:var(--bg)}
  .menu [role="menuitem"].danger:not(:disabled):not([aria-disabled="true"]):hover{background:var(--warn);color:var(--bg)}
}
.menu .sepd{margin:0}
.menu kbd{color:inherit;font-weight:300}

/* ==== tooltip: .tip ==== */
/* Hover, focus or tap. It types itself in. data-aui="tooltip" on .pop adds the
   tap and lets Escape dismiss it. */
.tip{
  position:absolute;left:0;bottom:calc(var(--r)*3 + 6px);z-index:30;padding:0 1ch;
  max-width:calc(100vw - 4ch);white-space:nowrap;font-weight:700;pointer-events:none;
  background:var(--ink);color:var(--bg);
  clip-path:inset(0 100% 0 0);transition:clip-path var(--aui-quick) var(--aui-ease-type);
}
.tip::after{content:"v";content:"v" / "";position:absolute;left:2ch;top:var(--r);color:var(--ink);line-height:12px}
.pop:focus-within .tip,.pop.on .tip{clip-path:inset(0 -4px -14px -4px)}
@media (hover:hover) and (pointer:fine){.pop:hover .tip{clip-path:inset(0 -4px -14px -4px)}}
.pop.off .tip{clip-path:inset(0 100% 0 0) !important}   /* Escape */
/* a tap leaves focus behind, so on touch the tap timer shows it, below the
   button; a keyboard on a touch screen shows it too (a tap sets no :focus-visible) */
@media (pointer:coarse){
  .tip{bottom:auto;top:calc(var(--r)*3 + 6px);white-space:normal}
  .tip::after{content:"^";content:"^" / "";top:-12px}
  .pop:focus-within .tip{clip-path:inset(0 100% 0 0)}
  .pop.on .tip,.pop:has(:focus-visible) .tip{clip-path:inset(-14px -4px 0 -4px)}
}

/* ==== pane: .pane ==== */
/* A panel that floats next to what opened it: the popover, the combobox list
   and the context menu. ascii-ui.js lifts it into the top layer where the
   browser has the Popover API, so no box that scrolls or clips holds it back,
   and places it on the grid: under its button, or above it when there is no
   room below. It comes in with a wipe in four steps, from its button's side. */
.pane{position:absolute;left:0;top:0;z-index:30;width:calc(var(--pcols,36)*1ch);max-width:calc(100vw - 2ch);background:var(--bg);color:var(--ink);text-align:left}
.pane[popover]{inset:auto;margin:0;padding:0;border:0;overflow:visible;background:var(--bg);color:var(--ink)}
.pane .lift{max-width:none}
.pane .row{justify-content:flex-end;column-gap:1ch}
.pane.open > .lift{animation:aui-wipe var(--aui-quick) var(--aui-ease-wipe) both}
.pane.open.up > .lift{animation-name:aui-wipe-up}
@keyframes aui-wipe{from{clip-path:inset(0 0 100% 0)}to{clip-path:inset(0)}}
@keyframes aui-wipe-up{from{clip-path:inset(100% 0 0 0)}to{clip-path:inset(0)}}

/* ==== popover: [data-aui="popover"] ==== */
/* data-aui="popover" on a .pop: the button opens the .pane next to it. While
   it is open the button's rim is # (held open), unless the focus is on it. */
[data-aui="popover"] > .btn[aria-expanded="true"]:not(:focus-visible){--h:var(--h-mid);--s:var(--s-mid)}

/* ==== combobox: .combo .opts ==== */
/* data-aui="combobox": a field that narrows a list as you type. The list is a
   .pane as wide as the field. The pick is an ink slab, the option the arrows
   are on is the focus color, a disabled one is gray. Two rows each, a thumb
   tall, six in view before it scrolls. */
.combo{position:relative}
.combo .prompt{cursor:pointer}
.combo .pane .body{padding:var(--r) 3ch}
.opts{position:relative;max-height:calc(var(--r)*12);overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}
.opts [role="option"]{
  display:flex;justify-content:space-between;gap:2ch;padding:calc(var(--r)/2) 1ch;white-space:nowrap;overflow:hidden;
  cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;
}
.opts [role="option"][aria-selected="true"]{background:var(--ink);color:var(--bg);font-weight:700}
.opts [role="option"][data-active]{background:var(--accent);color:var(--bg)}
.opts [role="option"][aria-disabled="true"]{color:var(--muted);cursor:not-allowed}
.opts-none{color:var(--muted);padding:0 1ch}
/* nothing matches: the pane shrinks to the message, half a row each side */
.combo .pane .body:has(> .opts-none:not([hidden])){padding-block:calc(var(--r)/2)}

/* ==== contextmenu: .ctx ==== */
/* data-aui="contextmenu" on .ctx: a right-click, a long press or Shift F10
   inside it opens its .menu where you are. The row it acts on wears a violet
   bar while the menu is open. On touch the press is the menu's, not the
   browser's, so it does not select the text under the finger. */
.ctx{position:relative}
.ctx > .menu{top:0;width:28ch;max-width:calc(100vw - 2ch)}
.ctx [data-ctx]{box-shadow:inset 1ch 0 0 var(--violet)}
/* while the menu is open only its row is marked: the zebra steps back */
.ctx:has(> .menu.open) tbody tr:not([data-ctx]){background:none}
@media (pointer:coarse){.ctx{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}}

/* ==== alertdialog: [role="alertdialog"] ==== */
/* A dialog that asks before something that cannot be undone. A tap on the
   page around it is not an answer: it nudges one character each way and the
   focus goes back to the safe button. Inside an .alert rim, the backdrop dots
   turn yellow. */
dialog[role="alertdialog"].nudge > .lift{animation:aui-nudge var(--aui-base) var(--aui-ease-jolt) both}
@keyframes aui-nudge{0%{transform:translateX(-1ch)}33%{transform:translateX(1ch)}66%{transform:translateX(-1ch)}100%{transform:none}}
dialog[role="alertdialog"] .body > p + .group{margin-top:var(--r)}
dialog[role="alertdialog"]:has(> .alert)[open]::before{color:rgba(138,90,0,.5);color:color-mix(in srgb,var(--warn) 50%,transparent)}

/* ==== otp: .otp ==== */
/* Six brackets. data-aui="otp" advances, goes back on Backspace and takes a paste. */
.otp{display:flex;flex-wrap:wrap;gap:1ch}
.otp span{display:inline-flex;font-weight:700}
.otp span::before{content:"[";content:"[" / "";color:var(--muted)}
.otp span::after{content:"]";content:"]" / "";color:var(--muted)}
.otp input{
  font:inherit;font-weight:700;width:2ch;text-align:center;background:none;border:0;border-radius:0;
  color:var(--ink);padding:12px 0;margin:-12px 0;outline:0;caret-color:var(--hot);
}
.otp input::placeholder{color:var(--muted)}
.otp span:focus-within::before,.otp span:focus-within::after{color:var(--accent)}
.otp.good span::before,.otp.good span::after,.otp.good input{color:var(--ok)}
/* a character that is not a digit: the brackets turn into the error wall, !
   in the warning color, so it does not rest on color alone. The status line
   says "Digits only." */
.otp.invalid span::before,.otp.invalid span::after,.otp.invalid input{color:var(--danger)}
.otp.invalid span::before,.otp.invalid span::after{content:"!";content:"!" / ""}

/* ==== icon: .icon ==== */
/* Icons drawn in characters, no font of their own and no svg. Pure css.
     <span class="icon" data-icon="search" role="img" aria-label="Search"></span>
   Inline: one row tall, one to three characters wide. It sits in a line of
   text or inside a button's .label. Add .icon-lg for the large one: three
   rows tall, five or six characters wide, for empty states and tiles.
   Next to words that say the same, it is decoration: aria-hidden="true" and no
   label. The characters are paint (empty alt text), so a screen reader says
   the aria-label or nothing. They take the color of the text; .hot, .ok,
   .warn and .violet give them a role color. The names are in README.md */
.icon{
  --i:"";--il:"";
  display:inline-block;white-space:pre;font-weight:700;font-style:normal;
  line-height:var(--r);text-transform:none;letter-spacing:0;
}
.icon::before{content:var(--i);content:var(--i) / ""}
.icon-lg{vertical-align:top}
.icon-lg::before{content:var(--il);content:var(--il) / "";display:block}
.icon.hot{color:var(--hot)}
.icon.ok{color:var(--ok)}
.icon.warn{color:var(--warn)}
.icon.violet{color:var(--violet)}
/* the set: --i is the inline one, --il the large one, rows split by \A */
.icon[data-icon="search"]{--i:"o\\";--il:" .-. \A (   )\A  `-'\\"}
.icon[data-icon="close"]{--i:"[x]";--il:"\\   /\A   X  \A /   \\"}
.icon[data-icon="menu"]{--i:"[=]";--il:"=====\A =====\A ====="}
.icon[data-icon="plus"]{--i:"[+]";--il:"  |  \A --+--\A   |  "}
.icon[data-icon="minus"]{--i:"[-]";--il:"     \A -----\A      "}
.icon[data-icon="check"]{--i:"v/";--il:"    /\A \\  / \A  \\/  "}
.icon[data-icon="warning"]{--i:"/!\\";--il:"  ^  \A  /!\\ \A /___\\"}
.icon[data-icon="error"]{--i:"(!)";--il:" .-. \A ( ! )\A  '-' "}
.icon[data-icon="info"]{--i:"(i)";--il:" .-. \A ( i )\A  '-' "}
.icon[data-icon="help"]{--i:"(?)";--il:" .-. \A ( ? )\A  '-' "}
.icon[data-icon="arrow-up"]{--i:"/|\\";--il:"  ^  \A  /|\\ \A   |  "}
.icon[data-icon="arrow-down"]{--i:"\\|/";--il:"  |  \A  \\|/ \A   v  "}
.icon[data-icon="arrow-left"]{--i:"<-";--il:" /   \A <----\A  \\   "}
.icon[data-icon="arrow-right"]{--i:"->";--il:"   \\ \A ---->\A    / "}
.icon[data-icon="chevron-up"]{--i:"^";--il:"     \A   ^  \A  / \\ "}
.icon[data-icon="chevron-down"]{--i:"v";--il:" \\ / \A   v  \A      "}
.icon[data-icon="chevron-left"]{--i:"<";--il:"   / \A   <  \A    \\ "}
.icon[data-icon="chevron-right"]{--i:">";--il:" \\   \A   >  \A  /   "}
.icon[data-icon="external"]{--i:"->]";--il:".-- ^\A | / |\A '---'"}
.icon[data-icon="copy"]{--i:"|[]";--il:".--. \A |.--.\A '|__|"}
.icon[data-icon="download"]{--i:"_v_";--il:"  |  \A  \\|/ \A |_v_|"}
.icon[data-icon="upload"]{--i:"_^_";--il:"  ^  \A  /|\\ \A |_|_|"}
.icon[data-icon="edit"]{--i:"_/";--il:"   /7\A   // \A <_/  "}
.icon[data-icon="delete"]{--i:"|_|";--il:"_.-._\A |:::|\A |___|"}
.icon[data-icon="settings"]{--i:"-|-";--il:"--|--\A |----\A ---|-"}
.icon[data-icon="user"]{--i:"/o\\";--il:" .-. \A  '-' \A /___\\"}
.icon[data-icon="home"]{--i:"[^]";--il:"  ^  \A  / \\ \A |_n_|"}
.icon[data-icon="bell"]{--i:"/.\\";--il:" .-. \A  | | \A /_o_\\"}
.icon[data-icon="lock"]{--i:"[o]";--il:" .-. \A  | | \A [_o_]"}
.icon[data-icon="unlock"]{--i:"[o/";--il:" .-. \A  |   \A [_o_]"}
.icon[data-icon="eye"]{--i:"<o>";--il:" .-. \A <(o)>\A  '-' "}
.icon[data-icon="eye-off"]{--i:"<\\>";--il:"\\.-. \A <(\\)>\A  '-'\\"}
.icon[data-icon="calendar"]{--i:"[:]";--il:".T-T.\A |===|\A |:::|"}
.icon[data-icon="clock"]{--i:"(')";--il:" .-. \A ( |_)\A  '-' "}
.icon[data-icon="filter"]{--i:"\\-/";--il:"=====\A  \\ / \A   |  "}
.icon[data-icon="sort"]{--i:"^v";--il:" ^ | \A  | | \A  | v "}
.icon[data-icon="refresh"]{--i:"(->";--il:" .-> \A (   )\A  '-' "}
.icon[data-icon="more"]{--i:"...";--il:"     \A o o o\A      "}
.icon[data-icon="star"]{--i:"(*)";--il:"__/\\__\A \\    /\A /_/\\_\\"}

/* ==== kbd: kbd .kbds ==== */
kbd{font:inherit;font-weight:700;color:var(--ink)}
kbd::before{content:"[";content:"[" / ""}   /* paint: a screen reader says the key, not the brackets */
kbd::after{content:"]";content:"]" / ""}
.kbds{display:grid;gap:var(--r);grid-template-columns:repeat(auto-fill,minmax(16ch,1fr))}

/* ==== divider: .sepd .sepl ==== */
/* Four weights of nothing, and a rule with a word in it. */
.sepd{height:var(--r);overflow:hidden;white-space:nowrap;font-weight:700;margin:var(--r) 0 0}
.sepd::before{content:var(--h-light);content:var(--h-light) / ""}
.sepd.heavy::before{content:var(--h-heavy);content:var(--h-heavy) / "";color:var(--hot)}
.sepd.faint::before{content:var(--h-faint);content:var(--h-faint) / "";color:var(--muted)}
.sepd.shade::before{content:var(--h-shade);content:var(--h-shade) / "";color:var(--violet)}
.sepl{display:flex;gap:1ch;color:var(--muted);margin-top:var(--r);white-space:nowrap;overflow:hidden}
.sepl::before,.sepl::after{content:var(--h-faint);content:var(--h-faint) / "";flex:1;overflow:hidden;min-width:0}

/* ==== spinner: .spins [data-aui="spinner"] ==== */
/* data-aui="spinner" data-kind="classic|ramp|bounce|dots|fill" on any <b> or <span>. */
[data-aui="spinner"]{display:inline-block;min-width:8ch;margin-right:1ch;color:var(--hot);white-space:pre;font-weight:700}
.spins{display:grid;gap:var(--r) 2ch;grid-template-columns:repeat(auto-fill,minmax(14ch,1fr))}

/* ==== segment: .tgroup ==== */
/* Radios dressed as slabs. */
.tgroup{display:inline-flex;flex-wrap:wrap}
.tgroup label{position:relative;cursor:pointer}
.tgroup input{position:absolute;opacity:0;width:1px;height:1px}
.tgroup span{display:block;padding:12px 2ch;color:var(--muted);text-transform:uppercase}
.tgroup input:checked + span{background:var(--ink);color:var(--bg);font-weight:700}
.tgroup input:focus-visible + span,.tgroup input:checked:focus-visible + span{background:var(--accent);color:var(--bg)}

/* ==== timeline: .timeline ==== */
/* Nodes are @, the wire is colons. .past nodes are #. */
.timeline{list-style:none;padding:0;margin:0;max-width:60ch}
.timeline li{position:relative;padding:0 0 var(--r) 4ch}
.timeline li::before{content:var(--k8) var(--k8);content:var(--k8) var(--k8) / "";position:absolute;left:0;top:0;font-weight:700;color:var(--hot)}
.timeline li::after{
  content:var(--v-shade);content:var(--v-shade) / "";
  position:absolute;left:0;top:var(--r);bottom:0;width:2ch;overflow:hidden;white-space:pre;color:var(--violet);font-weight:700;
}
.timeline li:last-child::after{display:none}
.timeline li.past::before{content:var(--k6) var(--k6);content:var(--k6) var(--k6) / "";color:var(--ink)}
.timeline b{display:block;text-transform:uppercase;font-weight:700}
.timeline span{color:var(--muted)}
.timeline time{color:var(--violet);font-weight:700}

/* ==== toast: .toast [data-aui-toast] ==== */
/* One line typed in from the left. Lime for good news, yellow (.err) for the
   other kind. ascii-ui.js adds the element; ASCIIUI.toast("Saved.") shows it. */
.toast{
  position:fixed;left:2ch;right:2ch;bottom:calc(var(--r) + env(safe-area-inset-bottom,0px));
  display:flex;pointer-events:none;z-index:70;
}
.toast > span{
  background:var(--ok);color:var(--bg);padding:0 1ch;max-width:100%;font-weight:700;
  clip-path:inset(0 100% 0 0);transition:clip-path var(--aui-base) var(--aui-ease-type);
}
.toast.on > span{clip-path:inset(-12px 0 -12px 0);pointer-events:auto}   /* a tap on it does not reach the page under it */
.toast.err > span{background:var(--warn)}
/* the way out, [x], at the end of the line. It takes the tap a row above and below */
.toast-x{
  font:inherit;font-weight:700;color:inherit;background:none;border:0;border-radius:0;cursor:pointer;
  padding:12px 0 12px 1ch;margin:-12px 0;outline:0;-webkit-tap-highlight-color:transparent;
}
.toast:not(.on) .toast-x{visibility:hidden}
.toast-x:focus-visible{background:var(--accent);color:var(--bg);background-clip:content-box}
/* on a phone the bottom row is where the thumb just was, so it drops in from the top */
@media (max-width:1023px),(pointer:coarse){
  .toast{bottom:auto;top:calc(var(--r)*3 + env(safe-area-inset-top,0px))}
}

/* ==== details: .acc ==== */
/* A native <details>. The marker is [+] closed and [-] open. */
.acc{position:relative;padding-bottom:var(--r)}
.acc::after{
  content:var(--h-faint);content:var(--h-faint) / "";
  position:absolute;left:0;right:0;bottom:0;height:var(--r);
  overflow:hidden;white-space:nowrap;color:var(--muted);pointer-events:none;
}
.acc summary{list-style:none;cursor:pointer;display:flex;gap:1ch;padding:12px 0;margin:-12px 0;outline:0;-webkit-tap-highlight-color:transparent}
.acc summary::-webkit-details-marker{display:none}
.acc summary::before{content:"[+]";content:"[+]" / "";flex:none;font-weight:700;color:var(--hot)}
.acc[open] summary::before{content:"[-]";content:"[-]" / "";color:var(--ok)}
.acc summary:focus-visible::before{background:var(--accent);color:var(--bg)}
.acc p{padding-left:4ch;margin-top:var(--r);color:var(--muted)}
.acc + .acc{margin-top:var(--r)}

/* ==== skeleton: .skel ==== */
/* data-aui="skeleton" draws a card silhouette with a wave through the ramp. */
.skel{color:var(--muted);font-weight:700;white-space:pre;overflow:hidden;min-height:calc(var(--r)*6)}
.skel .f{opacity:.5}

/* ==== table: .tablewrap .tbl ==== */
/* A real <table>. The head is an ink slab and every other row a faint tint.
   .tablewrap around it scrolls a table wider than its box, sideways. A row
   with aria-selected="true" is the picked one, an ink slab: give the rows
   tabindex="0" and set that attribute from your own script, the kit does
   not pick rows. */
.tablewrap{overflow-x:auto;max-width:100%}
.tbl{border-collapse:collapse;white-space:nowrap;min-width:100%}
.tbl th{text-align:left;background:var(--ink);color:var(--bg);text-transform:uppercase;padding:0 1ch;font-weight:700}
.tbl td{padding:calc(var(--r)/2) 1ch}   /* a row is two whole rows */
.tbl tbody tr:nth-child(even){background:rgba(128,128,128,.1);background:color-mix(in srgb,var(--ink) 7%,transparent)}
.tbl tbody tr[tabindex]{cursor:pointer;outline:0}
.tbl tbody tr[aria-selected="true"]{background:var(--ink);color:var(--bg);font-weight:700}
.tbl tbody tr[aria-selected="true"] .badge{background:var(--bg);color:var(--ink)}
/* a focused row is a > in its first character, which the cell's padding keeps free */
.tbl tbody td:first-child{position:relative}
.tbl tbody tr:focus-visible > td:first-child::before{content:">";content:">" / "";position:absolute;left:0;top:calc(var(--r)/2);font-weight:700;color:var(--accent)}

/* ==== media: @print @forced-colors @prefers-contrast ==== */
/* The page on media it was not drawn for. Last on purpose, so these win.
   All three keep the characters and change only what color they are.
     print                  black characters on white paper
     forced-colors:active   Windows High Contrast: the viewer's own colors
     prefers-contrast:more  no dim text, no faded rules */
@media print{
  @page{margin:12mm 10mm}
  /* three :root so it outranks the dark theme (two) */
  :root:root:root{
    --bg:#fff;--ink:#000;--muted:#3a3a3a;--hot:#000;--pink:#000;--cy:#000;--ok:#000;--warn:#000;
    --deep:#555;--violet:#000;--t0:#000;--t1:#000;--t2:#000;--t3:#000;--scan:transparent;
    color-scheme:light;print-color-adjust:exact;-webkit-print-color-adjust:exact;
  }
  html,body{background:#fff !important;color:#000}
  body{font-weight:400}
  *,*::before,*::after{animation:none !important;transition:none !important}
  /* what floats over the page is not on paper */
  .toast,.tip,.pane,.menu,dialog,body::before{display:none !important}
  body:has(dialog[open]){overflow:visible !important}
  .tablewrap,.progress{overflow:visible !important}
  .skel .f{opacity:1}
  /* a data table prints the rows it shows, without the controls around them */
  .dt-tools .field,.dt-pick,.dt-skel,[data-aui="datatable"] [data-aui="pagination"],[data-aui="datatable"] .tablewrap::after{display:none !important}
  /* keep a thing together, and a title with what follows it */
  .lift,.card,.frame,.acc,.cal,.otp,.field,.alert,.tbl tr,.timeline li{break-inside:avoid}
  h1,h2,h3,legend,.field-label{break-after:avoid}
  /* a link to another site says where it goes */
  a[href^="http"]:not(.btn):not(.ibtn)::after{content:" (" attr(href) ")";overflow-wrap:anywhere;font-weight:400}
}
/* Forced colors take away background colors and box shadows. Frames and
   markers made of characters survive (a button's = becomes @), slabs do not:
   the picked tab, the current page, the picked day. Here they come back as
   slabs in the system colors, CanvasText on Canvas for what is picked and
   Highlight for where the focus is. Focus comes after the picks, so a
   focused pick shows the focus */
@media (forced-colors:active){
  /* the transparent outline that waits for focus would be a box round every button and tab */
  .btn,.tab{outline:0 !important}
  :where(
    .tab[aria-selected="true"],.ibtn[aria-current="page"],.cal-grid button[aria-pressed="true"],
    .tgroup input:checked + span,.tbl tbody tr[aria-selected="true"],.tbl tbody tr[aria-selected="true"] td,
    .crumbs [aria-current] span,.opts [role="option"][aria-selected="true"],
    .badge:not(.b-out),.bar-title,.btn-primary .label,.toast > span,.tip,.avatar,.tbl th,
    .tbl tbody tr.dt-on,.tbl tbody tr.dt-on td
  ){forced-color-adjust:none !important;background:CanvasText !important;color:Canvas !important}
  :where(.tbl tbody tr[aria-selected="true"] .badge,.tbl tbody tr.dt-on .badge){background:Canvas !important;color:CanvasText !important}
  .tip::after{forced-color-adjust:none;color:CanvasText}
  :where(
    a:focus-visible,.tab:focus-visible,.crumbs a:focus-visible,.cal-grid button:focus-visible,.ibtn:focus-visible,
    .btn-primary:focus-visible .label,.otp input:focus,.tgroup input:focus-visible + span,
    .check input:focus-visible + .glyph,.sheet-x:focus-visible,.toast-x:focus-visible,
    .menu [role="menuitem"]:focus-visible,.opts [role="option"][data-active],.dt-sort:focus-visible
  ){forced-color-adjust:none !important;background:Highlight !important;color:HighlightText !important}
  .acc summary:focus-visible::before{forced-color-adjust:none;background:Highlight !important;color:HighlightText !important}
  :where(.check input:focus-visible + .glyph .switch-track)::before{forced-color-adjust:none;background:HighlightText !important}
  :where(.check input:focus-visible + .glyph .switch-track)::after{forced-color-adjust:none;color:HighlightText !important}
  .tabpanel:focus-visible::before,.tbl tbody tr:focus-visible > td:first-child::before{forced-color-adjust:none;color:Highlight}
  /* the row a context menu acts on: its violet bar was a shadow */
  .ctx [data-ctx]{outline:2px solid CanvasText;outline-offset:-2px}
}
/* More contrast: grey text moves two thirds of the way to ink, faded rules go
   solid, body type gets heavier and the dark theme's deep blue is lifted */
@media (prefers-contrast:more){
  :root:root{--muted:#2b2b28}
  @media (prefers-color-scheme:dark){:root:root:not([data-theme="light"]){--muted:#d8d2e0;--deep:#8f86ff}}
  :root:root[data-theme="dark"]{--muted:#d8d2e0;--deep:#8f86ff}
  body{font-weight:400}
  .skel .f{opacity:1}
  dialog[open]::before{color:color-mix(in srgb,var(--hot) 70%,transparent)}
}

/* ==== stat: .kpis .kpi ==== */
/* Stat tiles: a label, the number (a .ptitle, or plain bold words), and a
   line under it for the change. .kpis puts as many in a row as fit, 30
   characters each at the least, and stacks them when there is no room.
   In a .stack it takes the whole width, not the width of its words. */
.kpis{display:grid;gap:var(--r) 2ch;grid-template-columns:repeat(auto-fit,minmax(min(30ch,100%),1fr));justify-self:stretch}
.kpi{max-width:50ch;min-width:0}
.kpi .ptitle + *{margin-top:var(--r)}

/* ==== poster: .poster .ptitle ==== */
/* A title in big pixels made of small characters: a 5 by 7 bitmap face,
   two characters a pixel, one <span> a row, 14 rows in 4 of the page's.
   The rows run ink, pink, magenta and violet from the top (--t0 to --t3).
   The kit has no bitmap face: the html holds the finished rows, as the Code
   tab prints them. It is paint, so keep aria-hidden on it and put the words
   in the page too (a .vh next to it). */
.poster{font-weight:700;white-space:pre;overflow:hidden;-webkit-user-select:none;user-select:none}
.ptitle{font-size:calc(var(--r)/3);line-height:calc(var(--r)/4);height:calc(var(--r)*4);padding-top:calc(var(--r)/8)}
.ptitle > span{display:block;color:var(--t3)}
.ptitle > span:nth-child(-n+12){color:var(--t2)}
.ptitle > span:nth-child(-n+8){color:var(--t1)}
.ptitle > span:nth-child(-n+4){color:var(--t0)}

/* ==== grid: .grid2 .grid3 ==== */
/* Cards side by side: as many 37 character columns as the box holds
   (.grid2), or 27 (.grid3). Without the room they stack. It measures the
   box, not the window, so it works in a narrow column as well as a page.
   In a .stack it takes the whole width. */
.grid2,.grid3{display:grid;gap:var(--r) 2ch;grid-template-columns:repeat(auto-fit,minmax(min(37ch,100%),1fr));justify-self:stretch}
.grid3{grid-template-columns:repeat(auto-fit,minmax(min(27ch,100%),1fr))}
.grid2 > .lift,.grid3 > .lift{max-width:none}
.stack:has(> :is(.grid2,.grid3)){justify-self:stretch}   /* a stack in a stack would hold it to the width of its words */

/* ==== pricing: .pricing .price .feat .popular ==== */
/* Plans side by side, on a .grid3. Every card is as tall as the row, the
   feature list takes the slack and the button stays at the foot. The plan
   to pick wears the @ rim (tone-heavy) and a .popular badge on its top rule.
   .row.full stretches its buttons across the card. */
.pricing > .lift{display:flex;flex-direction:column}
.pricing .card{flex:1;display:flex;flex-direction:column}
.pricing .body{flex:1;display:flex;flex-direction:column}
.pricing .feat{flex:1}
.price{font-weight:700}
.price small{font:inherit;font-weight:300;color:var(--muted)}
.feat{list-style:none;padding:0;margin:var(--r) 0 0}
.feat li{position:relative;padding-left:2ch;color:var(--muted)}
.feat li::before{content:"+";content:"+" / "";position:absolute;left:0;color:var(--violet);font-weight:700}   /* a list mark, not a confirmation */
.popular{position:absolute;top:0;right:2ch;z-index:1}
.badge.popular{background:var(--violet)}   /* a label, so not lime: lime confirms */
.row.full .btn{flex:1}
.row.full .btn .mid{display:flex;justify-content:center}

/* ==== checklist: .checklist .meta ==== */
/* A list of checkboxes, one .check each, with a .meta line under it. A
   ticked one is struck through. data-aui="checklist" counts them into the
   progress bar next to the list. */
.checklist{list-style:none;padding:0;margin:0;max-width:60ch}
.checklist li{padding-bottom:var(--r)}
.checklist .meta{color:var(--muted);padding-left:4ch;display:flex;flex-wrap:wrap;gap:0 1ch}
.checklist .check input:checked ~ span:not(.glyph){text-decoration:line-through;color:var(--muted)}

/* ==== navlist: .navlist ==== */
/* The pages of an app, one button or link each, with a count on the right.
   The current one (aria-current) is an ink slab with a > in front, so it
   does not rest on color alone. Each is two rows tall and takes the tap a
   pixel past each edge: 44px. data-aui="pick" moves aria-current on a
   click; links from your server can carry it instead. */
.navlist{list-style:none;padding:0;margin:0;width:100%;max-width:32ch}
.navlist :is(a,button){
  display:flex;justify-content:space-between;width:100%;position:relative;
  font:inherit;background:none;border:0;border-radius:0;color:var(--muted);text-decoration:none;text-align:left;
  padding:calc(var(--r)/2) 1ch;margin:0;cursor:pointer;outline:0;-webkit-tap-highlight-color:transparent;
}
.navlist :is(a,button)::after{content:"";position:absolute;inset:-1px 0}
.navlist :is(a,button)::before{content:"  ";content:"  " / "";white-space:pre}
.navlist :is(a,button) span{margin-left:auto;padding-left:2ch}
.navlist [aria-current]:not([aria-current="false"]){background:var(--ink);color:var(--bg);font-weight:700}
.navlist [aria-current]:not([aria-current="false"])::before{content:"> ";content:"> " / ""}
.navlist :is(a,button):focus-visible{background:var(--accent);color:var(--bg)}
@media (hover:hover) and (pointer:fine){.navlist :is(a,button):not([aria-current]):hover{color:var(--ink)}}
@media (forced-colors:active){
  .navlist [aria-current]:not([aria-current="false"]){forced-color-adjust:none;background:CanvasText;color:Canvas}
  .navlist :is(a,button):focus-visible{forced-color-adjust:none;background:Highlight;color:HighlightText}
}

/* ==== pick: [data-aui="pick"] .lift[role="button"] ==== */
/* data-aui="pick" on a group of buttons, links or cards with role="button":
   one of them is the pick. A card you can pick is a .lift with
   role="button" and tabindex="0". The picked card's title is an ink slab
   with a > in front and its rim turns magenta; the focus turns rim and
   title the focus color. */
.lift[role="button"]{cursor:pointer;outline:0;-webkit-tap-highlight-color:transparent}
.lift[role="button"][aria-pressed="true"] .bar-title{background:var(--ink);color:var(--bg)}
.lift[role="button"][aria-pressed="true"] .bar-title::before{content:"> ";content:"> " / ""}
.lift[role="button"][aria-pressed="true"] .card{--frame-color:var(--hot)}
.lift[role="button"]:focus-visible .card{--frame-color:var(--accent)}
.lift[role="button"]:focus-visible .bar-title{background:var(--accent);color:var(--bg)}
@media (forced-colors:active){
  .lift[role="button"]:focus-visible .bar-title{forced-color-adjust:none;background:Highlight;color:HighlightText}
}

/* ==== kv: .kv .qty ==== */
/* A <dl> of labels and values, the labels grey in a column as wide as the
   longest (10 characters at least). Under 400px the value goes under its
   label. .qty is an amount: bold, magenta, never broken over two lines. */
.kv,.card .kv{display:grid;grid-template-columns:minmax(10ch,max-content) minmax(0,1fr);gap:0 1ch}
.kv dt{color:var(--muted)}
.qty{font-weight:700;color:var(--hot);white-space:nowrap}
@media (max-width:399px){
  .kv,.card .kv{grid-template-columns:1fr}
  .kv dd{margin-bottom:var(--r)}
}

/* ==== meter: [role="meter"] ==== */
/* A value on the halftone bar, next to words: how far into a book, how
   much of the budget. It is a .progress with role="meter" (a measure, not a
   task that finishes), drawn by data-aui="progress" from aria-valuenow (a
   percent), data-cells wide. aria-valuetext says the number in words. */
[role="meter"].progress{display:inline-flex;gap:1ch;vertical-align:top;max-width:100%}
[role="meter"].progress .bar{min-width:0;overflow:hidden}

/* ==== stepper: .stepper .steps ==== */
/* [-] 6 people [+]: a number between two .ibtn. data-aui="stepper" makes
   them count and scales the amounts (data-each) in the same card. .steps
   is a numbered method, grey, indented by the width of its numbers. */
.stepper{display:inline-flex;align-items:center;gap:1ch}
.stepper + *{margin-top:var(--r)}
.steps{margin:var(--r) 0 0;padding-left:4ch;color:var(--muted)}

/* ==== tags: .tags .b-violet .b-pink ==== */
/* A row of badges that label, nothing more. Labels are violet or pink:
   lime confirms and yellow warns, so a tag that only names a thing wears
   neither. --on-pink is the ink on a pink slab. */
.tags{display:flex;flex-wrap:wrap;gap:0 1ch;margin-top:var(--r)}
.b-violet{background:var(--violet)}
.b-pink{--on-pink:#111110;background:var(--pink);color:var(--on-pink)}

/* ==== thumb: .thumb ==== */
/* A picture slot: an <img>, or a <pre> of characters with role="img" and
   an aria-label. The characters are violet. */
.thumb{margin:0 0 var(--r)}
.thumb img{display:block;width:100%;height:auto}
.thumb pre{font-weight:700;color:var(--violet);white-space:pre;overflow:hidden}

/* ==== profile: .profile ==== */
/* A picture beside the words about a person: one column on a phone, the
   picture 28 characters wide from 720px. */
.profile{display:grid;gap:var(--r) 3ch;grid-template-columns:minmax(0,1fr);justify-self:stretch}
.profile > * > * + *{margin-top:var(--r)}
.profile .thumb{margin:0}
@media (min-width:720px){.profile{grid-template-columns:28ch minmax(0,1fr);align-items:start}}

/* ==== linkbtn: a.btn ==== */
/* A link dressed as a button, for a way out that goes to another page. */
a.btn{text-decoration:none}

/* ==== signal: [data-aui-signal] [data-aui-signal-level] aui-sr ==== */
/* Signal: the bad signal, opt in. Nothing here runs until an element asks
   with data-aui-signal="glitch", "scramble", "band" or "rot" (several, with
   spaces), or a script calls ASCIIUI.glitch(el) and the others. How loud is
   one token on :root, calm, normal or loud (off stops it), or
   data-aui-signal-level on the root or on any element around the effect.
   Everything is drawn on one fixed layer that takes no clicks and is paint
   (aria-hidden): the page under it keeps its boxes. Noise is magenta and
   pink, never cyan, which is focus. Reduced motion, forced colors and print
   switch all of it off, here and in ascii-ui.js. */
:root{--aui-signal:normal}
.aui-sig{position:fixed;left:0;top:0;width:0;height:0;overflow:visible;pointer-events:none;z-index:240}
.aui-sig > span{
  position:fixed;left:0;top:0;display:block;overflow:hidden;white-space:pre;pointer-events:none;
  font-weight:700;line-height:var(--r);color:var(--hot);opacity:.7;
}
.aui-sig > .aui-sig-pk{color:var(--pink)}
/* glitch: the element itself goes a character sideways for a frame. translate
   moves the paint, never the layout */
.aui-sig-g{translate:var(--aui-sig-x,0) 0}
/* scramble: while the words decode, aui-noise is the paint (aria-hidden) and
   aui-sr holds the words for a screen reader, out of sight */
aui-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
/* the noise is css content, not text: a script reading the words mid-decode gets the words */
aui-noise::before{content:attr(data-n);content:attr(data-n) / ""}
/* band: three rows of characters roll down through the box, a row a step */
.aui-sig > .aui-sig-band{opacity:1;color:var(--pink)}
.aui-sig-band > span{display:block;white-space:pre;opacity:var(--aui-sig-o,.32);animation:aui-sig-band var(--aui-sig-t,2.6s) steps(var(--aui-sig-rows,40),end) 1 both}
@keyframes aui-sig-band{
  from{transform:translateY(calc(var(--r)*-3))}
  to{transform:translateY(calc(var(--r)*(var(--aui-sig-rows,40) - 3)))}
}
/* scanlines, the other half of the signal: <html class="crt"> (base, above).
   <html class="crt interlace"> draws them every other pixel, fainter: an
   interlaced field. It holds still */
:root.crt.interlace body::before{background:repeating-linear-gradient(to bottom,transparent 0 1px,color-mix(in srgb,var(--scan) 60%,transparent) 1px 2px)}
:root.crt.interlace dialog[open]::after{background-image:repeating-linear-gradient(to bottom,transparent 0 1px,color-mix(in srgb,var(--scan,rgba(0,0,0,.12)) 60%,transparent) 1px 2px)}
@media (prefers-reduced-motion:reduce),(forced-colors:active),print{
  .aui-sig{display:none !important}
  .aui-sig-g{translate:none !important}
}
