/*! ascii/ui kit 1.2.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}
.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}
.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)}

/* ==== 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}

/* ==== 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:"!" / ""}

/* ==== 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}
  /* 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
  ){forced-color-adjust:none !important;background:CanvasText !important;color:Canvas !important}
  :where(.tbl tbody tr[aria-selected="true"] .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]
  ){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)}
}
