/* ═══ 0. TYPE ══════════════════════════════════════════════════════ */
@font-face{
  font-family:'Aeonik Pro';
  src:url('fonts/AeonikPro-Regular.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:block;
}
/* ═══ 1. TOKENS ════════════════════════════════════════════════════ */

:root{
  /* neutral ramp — the only raw values in the whole document */
  /* Ten steps, t1 lightest to t10 darkest. Two named variants — this
     one the default — share the dark half (t6–t10) and differ only
     in the light half (t1–t5): dark tones read the same whichever
     is chosen, warmth shows where it is seen, on a surface, not in
     a shadow. */
  --t1:#EBEBEB; --t2:#D1D1D1; --t3:#B8B8B8; --t4:#9C9DA0; --t5:#828387;
  --t6:#48494B; --t7:#393A3C; --t8:#2D2D2F; --t9:#202122; --t10:#141414;

  /* work field */
  --m:1.6vw;             /* app margin — JS rounds it to whole pixels */
  --col:1fr;             /* track width; JS swaps it for whole pixels */
  --measure:calc(12 * var(--col) + 11 * var(--gut));  /* measure of a line of text */
  --gut:8px;
  --col-min:22px;      /* the track stops shrinking here */
  --tracks-min:6;      /* smallest container, in tracks */             /* gutter between tracks */
  --tracks:24;

  /* vertical rhythm — step of 4 up to 24, then jumps */
  --sp-1:4px;  --sp-2:8px;  --sp-3:12px; --sp-4:16px; --sp-5:20px;
  --sp-6:24px; --sp-7:32px; --sp-8:48px; --sp-9:72px;

  /* inside of the container */
  --pad:var(--sp-5);       /* container frame on all sides: 20px */
  --row-h:32px;            /* fixed element height */
  --row-gap:var(--sp-1);   /* gap between elements: 4px */
  --mod:36px;              /* module: element + gap */

  /* inside of the element */
  --el-pad:var(--sp-3);    /* surface inset: 12px */
  --el-gap:var(--sp-1);
  --cell-pad:3px;        /* half of the 6 a cell adds to its content */    /* gap between parts of an element: 4px */
  --pill-h:var(--sp-5);    /* pill height: 20px */
  --pill-pad:var(--sp-2);  /* pill inset: 8px */
  --btn-pad:var(--sp-3);   /* button inset: 12px, same as the surface inset */
  --guard:var(--row-h);    /* guard zone for a glyph on a button: 32px */
  --av:24px;               /* avatar side: element 32 minus two gaps of 4 */
  --st-size:16px;          /* status glyph side: element 32 minus two gaps of 8 */
  --ic-s:12px;             /* glyph next to type */
  --ic-m:16px;             /* glyph in a row */
  --ic-l:24px;             /* glyph on its own */


  /* type */
  --fam:'Aeonik Pro','Aeonik',-apple-system,BlinkMacSystemFont,'Helvetica Neue',Arial,sans-serif;
  --fw-light:300; --fw-reg:400; --fw-med:500;
  --fs-1:11px; --lh-1:16px;   /* metric, captions, group heading */
  --fs-2:14px; --lh-2:20px;   /* running text, elements */
  --fs-3:18px; --lh-3:24px;   /* subsection heading */
  --fs-4:24px; --lh-4:32px;   /* section heading, title */

  --menu-cols:1;   /* columns in a menu panel; every column takes the width of the longest option in the whole panel */
  /* The panel's own frame, named. It was living inside .menu-list; a
     calendar is a menu panel too, so the value belongs to the scale
     rather than to one component. It rides --sp-1, so it scales. */
  --menu-pad:var(--sp-1);

  /* Two numbers the second run of components needed, and both are halves
     of numbers that were already here — nothing new entered the scale.
     A radio is a circle at EVERY radius step, so its corner cannot be the
     system corner: it is half its own side. The mark under a chosen tab is
     half a gap; a hair would be lost against the rule it sits on, and a
     whole gap would read as a surface. */
  --radio-r:10px;                     /* half the side of a check field */
  /* Two more, from the desktop layouts. --catch is the only new NUMBER:
     the grab zone of a boundary, the one place in the system where the
     area that reacts is bigger than what is drawn. --rail-w brings no new
     number — it is the frame and the element that were already here. */
  --catch:16px;
  --rail-w:calc(2 * var(--pad) + var(--row-h));
  --grip-w:2px;
  --tab-rule:calc(var(--sp-1) / 2);   /* 2 px, and 3 at one and a half times */
  --r:0px;                 /* radius: 0 / 8 / 16, switched in the bar */
  /* DIVIDING RULE — one value for both modes.
     It is not a tone from the ramp but the ink tone at 20% strength,
     transparent, so it mixes with whatever lies beneath. The ink tone
     inverts with the mode, and 20% comes out almost identically on both
     sides: ΔL* 14.8 in light and 14.2 in dark, on surface and canvas alike. */
  --line-w:0.5px;

  /* Time: three steps, the same way everything else has its scale.
     1 — a change of colour or opacity, the thing stays in place.
     2 — movement: height, position, rotation.
     3 — things entering and leaving that were not there before. */
  --dur-1:120ms; --dur-2:200ms; --dur-3:320ms;
  --ease:cubic-bezier(.2, 0, 0, 1);   /* arrives, does not brake */

  /* Pressure: states are made from ONE material — the ink tone at varying
     strength. There are no separate state colours, there is one value at
     three intensities plus full inversion for selection. */
  --press-hover:6%;
  --press-down:12%;
  --off:.4;                          /* opacity of a disabled thing */
}

body[data-radius="8"]{--r:8px}
body[data-radius="16"]{--r:16px}

/* the warm variant touches only the light half, t1–t5 — the dark
   half is shared, so dark mode looks identical under either choice */
body[data-neutral="warm"]{
  --t1:#E9E7E2; --t2:#D6D3CD; --t3:#BCB9B3; --t4:#A19F9B; --t5:#868583;
}

/* roles — light mode */
body{
  /* Shadow: the ink tone in light mode, tone 8 in dark — and since in
     light the ink IS tone 8, one value covers both. A shadow always
     darkens, so it does not invert with the rest of the roles. */
  /* the dividing rule is computed from the ink, so it has to stand where
     the ink does: in :root there is no --ink yet and the value came out
     invalid, which is why no rule was drawn anywhere in the file */
  --line:color-mix(in srgb, var(--ink) 20%, transparent);
  /* The state material, named. It was being mixed inline in three places;
     the Theme collection carries it as two roles, so the stylesheet does
     too — ink at 6% under the cursor, at 12% while pressed. */
  --veil-6:color-mix(in srgb, var(--ink) var(--press-hover), transparent);
  --veil-12:color-mix(in srgb, var(--ink) var(--press-down), transparent);
  --shadow-ink:var(--t10);
  --shadow-2:0 4px 16px color-mix(in srgb, var(--shadow-ink) 20%, transparent);
  /* signal: a strong tone on a light background, a light tone on a dark one —
     the direction follows the mode switch, not the lightness of the colour */
/* ── SYSTEM PALETTE ── the colours of the system itself
   Six values, identical in both modes. They carry meaning, not decoration:
   status, warning, error. The attachable palettes (--c-1 … --c-7) are the
   user's and say what a thing belongs to; these are the system's and say
   what is happening to it. That is why they do not invert with the mode
   and why they are not offered in the colour panel — a warning has to look
   the same in every application built here.
   They are made to be SURFACES: type holds on every one of them, and each
   records which end of the ramp it carries — dark on five, light on red.
   The lowest figure in the set is 4.47, the highest 12.09, in both modes. */
  --s-1:#C83533;   /* red    — alert, blocked, field error */
  --s-2:#F09237;   /* orange — warning */
  --s-3:#F7CD46;   /* yellow — a surface only: 1.15 as type on a light one */
  --s-4:#59C07A;   /* green  — done */
  --s-5:#3780E9;   /* blue   — in progress */
  --s-6:#A679CD;   /* violet — in review */
  /* The ink belongs to the colour, as it does in the attachable palettes.
     Five of the six carry dark type; red is dark enough that light type
     wins on it — 4.47 against 3.52, both in either mode. */
  --s-ink:var(--t10);
  --s-1-ink:var(--t1);
  --s-2-ink:var(--t10); --s-3-ink:var(--t10);
  --s-4-ink:var(--t10); --s-5-ink:var(--t10); --s-6-ink:var(--t10);
  /* the signal roles now point at the system palette, so a status keeps
     its colour when the user attaches a different palette */
  --sig-1:var(--s-1); --sig-2:var(--s-2); --sig-3:var(--s-3);
  --sig-4:var(--s-4); --sig-5:var(--s-5); --sig-6:var(--s-6);
  /* pill: background always the light tone of the family, ink always t8 —
     checked on all 14 tones of both palettes, minimum 5.05 */
  --pill-ink:var(--t10);     /* fallback value; families carry their own */
  --canvas:var(--t2);
  --panel:var(--t1);
  --panel-2:var(--t2);          /* element: sunk into the surface */
  --el-hover:var(--t3);         /* pointed at: one step towards the ink */
  --el-sel:var(--t8);           /* chosen: the far half of the ramp */
  --ink:var(--t10);
  --ink-2:var(--t6);
  --field:var(--t3);
  /* THE ONE VALUE OUTSIDE THE SCALE OF STATES. States move in 6% and 12%;
     a scrim has the opposite job — it has to CUT the work off, not shade
     it. 40% of the ink does that on a light canvas. */
  --scrim:color-mix(in srgb, var(--ink) 40%, transparent);
}
/* roles — dark mode */
body.dark{
  /* dark mode does not turn the signal over — the system palette is one set */
  --canvas:var(--t10);
  --panel:var(--t8);
  --panel-2:var(--t7);
  --el-hover:var(--t6);
  --el-sel:var(--t3);
  /* the ceiling: nothing on the darkest surfaces shines brighter
     than t3. Ink stops there; the second level takes t4, one step
     in — the ramp now has room for it as a full step. */
  --ink:var(--t3);
  --ink-2:var(--t4);
  --field:var(--t6);
  /* and 60% here, because in dark mode the ink is a LIGHT tone on a nearly
     black canvas: at 40% the work underneath still competes for attention.
     The one role in the system that carries a different strength in the
     two modes, written down so nobody later "fixes" it. */
  --scrim:color-mix(in srgb, var(--ink) 60%, transparent);
}

/* attachable palettes — one API, two sets of values */
body[data-palette="a"]{
  --c-1:#B8413A; --c-1-s:#E7BEBE;
  --c-2:#DB663B; --c-2-s:#ECBFA7;
  --c-3:#F7CD59; --c-3-s:#F9EEC0;
  --c-4:#58AD5E; --c-4-s:#C2ECC3;
  --c-5:#3D59D0; --c-5-s:#CBD7E7;
  --c-6:#543A91; --c-6-s:#B7ACD2;
  --c-7:#6F592F; --c-7-s:#D6CFC2;
  /* in this palette every light tone carries dark type */
  --c-1-ink:var(--t10); --c-2-ink:var(--t10); --c-3-ink:var(--t10);
  --c-4-ink:var(--t10); --c-5-ink:var(--t10); --c-6-ink:var(--t10);
  --c-7-ink:var(--t10);
}
body[data-palette="b"]{
  --c-1:#955643; --c-1-s:#BF9487;
  --c-2:#B88552; --c-2-s:#DCBB99;
  --c-3:#CDAD5F; --c-3-s:#E8D5A8;
  --c-4:#768E86; --c-4-s:#B0C8C0;
  --c-5:#263849; --c-5-s:#93A0AC;
  --c-6:#5E586E; --c-6-s:#A6A0B5;
  --c-7:#776B55; --c-7-s:#D5CFC3;
  /* same as A: every light tone in this palette carries dark type, none
     falls below the threshold on its own light tone */
  --c-1-ink:var(--t10); --c-2-ink:var(--t10); --c-3-ink:var(--t10);
  --c-4-ink:var(--t10); --c-5-ink:var(--t10); --c-6-ink:var(--t10);
  --c-7-ink:var(--t10);
}
body[data-palette="c"]{
  --c-1:#99453A; --c-1-s:#BC887C;
  --c-2:#AE7D4F; --c-2-s:#D4AE8A;
  --c-3:#B89A4B; --c-3-s:#D4C28E;
  --c-4:#629379; --c-4-s:#9DB8AB;
  --c-5:#415889; --c-5-s:#8996AF;
  --c-6:#5F5178; --c-6-s:#8D82A2;
  --c-7:#755D50; --c-7-s:#C2BAB5;
  /* same again: every light tone carries dark type, the weakest is mauve
     at 5.13 — still with room above the threshold */
  --c-1-ink:var(--t10); --c-2-ink:var(--t10); --c-3-ink:var(--t10);
  --c-4-ink:var(--t10); --c-5-ink:var(--t10); --c-6-ink:var(--t10);
  --c-7-ink:var(--t10);
}

/* ═══ 2. BASE ══════════════════════════════════════════════════════ */

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; padding:var(--m);
  background:var(--canvas); color:var(--ink);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em;
  font-synthesis:none;
  font-variant-numeric:tabular-nums;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,p,ul,li,table{margin:0; padding:0}
ul{list-style:none}
b,strong{font-weight:var(--fw-med)}

/* work field: 24 tracks between the margins */

/* ═══ 3. BAR ═══════════════════════════════════════════════════════ */

.bar{
  position:sticky; top:0; z-index:40;
  display:flex; align-items:center; gap:var(--sp-2);
  padding:var(--sp-2) 0;
  background:var(--canvas);
}
.bar .stamp{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.bar .meas{
  margin-left:auto; padding-right:var(--sp-2);
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
  font-variant-numeric:tabular-nums;
}
.bar .meas[hidden]{display:none}
.bar .spacer{margin-left:auto}
.bar .spacer[hidden]{display:none}
/* The bar is made of the same things as everything else: buttons take
   the element height and the button inset, and groups behave like a
   switch — a track with a 4 px frame and concentrically smaller
   positions. The bar lies on the canvas, so its material is the
   container tone. */
.btn{
  appearance:none; border:0;
  height:var(--row-h); min-width:var(--row-h);
  display:inline-flex; align-items:center; justify-content:center;
  padding:0 var(--btn-pad);
  background:var(--panel); color:var(--ink);
  border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; cursor:pointer;
  transition:background var(--dur-1) ease, color var(--dur-1) ease;
}
.btn:hover{opacity:.75}
.btn[aria-pressed="true"]{background:var(--ink); color:var(--panel)}
.btn:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}

.seg{
  display:flex; gap:var(--row-gap);
  padding:var(--sp-1);
  background:var(--panel); border-radius:var(--r);
}
.seg .btn{
  height:calc(var(--row-h) - 2 * var(--sp-1));
  min-width:calc(var(--row-h) - 2 * var(--sp-1));
  padding:0 calc(var(--btn-pad) - var(--sp-1));
  background:none;
  border-radius:max(0px, calc(var(--r) - var(--sp-1)));
}
.seg .btn:hover{opacity:.75}
.seg .btn[aria-pressed="true"]{background:var(--ink); color:var(--panel)}
@media (prefers-reduced-motion:reduce){.btn{transition:none}}

/* ── views ── */
.view[hidden]{display:none}
.app-view{
  /* The preview is an application, not text: nothing selects, right-click
     catches neither letters nor elements. Input fields are the exception. */
  user-select:none; -webkit-user-select:none; -webkit-touch-callout:none;
  /* When containers no longer fit at a minimum of six tracks, the work
     field scrolls sideways and the containers behave like cards: each one
     stops at the left edge. */
  overflow-x:auto; scroll-snap-type:x proximity;
  scrollbar-width:none;
  --top-bar:0px;                    /* no bar above the containers */
  display:grid;
  grid-template-columns:repeat(var(--tracks), var(--col));
  gap:var(--gut);
  align-content:start;
  padding-top:var(--m);
}
/* With a bar: the buttons sit against the containers across an 8 px
   gutter and keep the full app margin above them — the margin belongs to
   the work field, not to the container, so it is not split between them. */
.app-view.has-bar{--top-bar:calc(var(--row-h) + var(--gut))}
.app-bar{
  grid-column:1/-1; grid-row:1;
  height:var(--row-h);
  display:flex; align-items:center; gap:var(--row-gap);
  margin-bottom:var(--gut);
}
.app-bar .spacer{flex:1}
.app-view::-webkit-scrollbar{display:none}
.app-view.has-bar .frame{grid-row:2}
.app-view.has-bar .splitter{grid-row:2}
.frame{
  grid-column:1/span 6;
  background:var(--panel);
  padding:var(--pad);
  border-radius:var(--r);
  /* The container is FIXED: it takes its height from the window and does
     not grow with content. Window minus top and bottom margin, the bar,
     the gap under the bar and — when present — the button bar together
     with the gutter beneath it. */
  height:calc(100vh - 3 * var(--m) - var(--bar-h, 40px) - var(--top-bar, 0px));
  display:flex; flex-direction:column;
  overflow:hidden;
}
.fbody{
  position:relative;        /* reference point for section positions */
  flex:1 1 auto;
  min-height:0;            /* without this, flex will not let the child shrink */
  overflow-y:auto;
  overscroll-behavior:contain;
  /* the system scrollbar does not belong to the system — content scrolls
     without it, and a row clipped at the edge says that it goes further */
  scrollbar-width:none;
}
.fbody::-webkit-scrollbar{display:none}

/* Container width is movable: JS rules the tracks through grid-column,
   the handle sits in the track between them. The surface can be switched
   off — only the background goes, the layout and colours stay. */
body[data-panel="off"] .frame{background:none}

/* The handle takes no track — it sits in the 8 px gutter between
   containers. The catch zone is wider than the rule so it can be hit;
   only the rule is visible: 2 px by 32 px, in the ink tone. */
.frame{grid-row:1; scroll-snap-align:start; min-width:calc(6 * var(--col) + 5 * var(--gut))}
.splitter{
  grid-row:1;
  justify-self:end; align-self:center;
  width:16px; height:var(--row-h);
  margin-right:calc(-8px - var(--gut) / 2);
  display:flex; align-items:center; justify-content:center;
  cursor:col-resize; touch-action:none; z-index:5;
}
.splitter::before{
  content:''; display:block;
  width:2px; height:var(--row-h);
  background:var(--ink);
  border-radius:min(var(--r), 1px);
  opacity:0; transition:opacity var(--dur-1) ease;
}
.splitter:hover::before,
body.is-splitting .splitter::before{opacity:1}
.splitter[data-locked="true"]{cursor:default; pointer-events:none}
.splitter[data-locked="true"]::before{opacity:0}
body.is-splitting{cursor:col-resize}
@media (prefers-reduced-motion:reduce){.splitter::before{transition:none}}

/* ── button ──
   Width is NEVER set: a series is a grid with one max-content column, so
   the column takes the width of the longest label and every button
   stretches to it. Changing the longest label recalculates the whole
   series by itself. */
.buttons{display:grid; grid-template-columns:max-content; gap:var(--row-gap)}
/* in a series they all reach the column width — outside a series each
   stands on its own width */
.buttons .button{width:100%}
.button{
  height:var(--row-h);
  display:flex; align-items:center; justify-content:center;
  width:auto;                       /* measured by its own label */
  padding:0 var(--btn-pad);
  min-width:var(--row-h);           /* never narrower than its own height */
  background:var(--canvas); color:var(--ink);
  border:0; border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em;
  white-space:nowrap; cursor:pointer;
}
.button:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
/* THREE BUTTON MATERIALS — a closed list, nothing outside it.
   1. in the background colour — the default; only ON a container
   2. in the container colour  — only OUTSIDE a container
   3. no colour                — outline in the ink tone, anywhere
   The ink tone is not a button material: it is reserved for the active
   state. If a button could be made of it at rest, the active state would
   have nothing left to tell it apart. */
.button.is-panel{background:var(--panel); color:var(--ink)}
.button.is-canvas{background:var(--canvas); color:var(--ink)}
.button.is-outline{
  background:none; color:var(--ink);
  outline:var(--line-w) solid var(--ink); outline-offset:-1px;
}

/* STATES — identical on all of them.
   Under the cursor the material loses a quarter of its strength. The
   chosen one goes active: it takes the type colour and the type takes the
   surface colour — the same inversion as in a menu option and a switch. */
.button:hover{opacity:.75}
.button[aria-pressed="true"],
.button.is-active{background:var(--ink); color:var(--panel); outline:none; opacity:1}

/* Button with a glyph: the glyph enters the width measurement like a
   letter, but standing on the opposite side from the label it also takes
   a guard zone — 32 px the label does not enter. */
.button.has-chev{justify-content:space-between; gap:var(--guard)}
/* a swatch in a button is a graphic thing: it stands as far from the left
   edge as it does from the top and bottom — (32 − 20) / 2 = 6, six less
   than the type inset. */
.button .swatch{
  margin-left:calc((var(--row-h) - var(--pill-h)) / 2 - var(--btn-pad));
  margin-right:calc(-1 * var(--guard) + var(--el-gap));
}
.button .chev{width:var(--ic-s); height:var(--ic-s); flex:none; transition:transform var(--dur-2) var(--ease)}
.button[aria-expanded="true"] .chev{transform:rotate(90deg)}
@media (prefers-reduced-motion:reduce){.button .chev{transition:none}}

/* Tool button: a square with the side of a row and nothing but a glyph
   inside. There is no label, so there is nothing to measure — the side
   comes from the height. */
.button.is-icon{
  width:var(--row-h); padding:0;
  justify-content:center;
}
.button.is-icon .chev{transform:none; width:var(--ic-m); height:var(--ic-m)}

/* tools in the header — on the right, in a row */
.fhead .tools{display:flex; align-items:center; gap:var(--row-gap)}

/* Menu: the button and the options sit in ONE max-content column, so
   they are measured together — a menu is never narrower than its button
   or the other way round. */
.menu{display:grid; grid-template-columns:max-content}

/* Menu panel. Two values rule everything: --menu-ink and --menu-bg.
   The frame, the labels and the selection fill take ink; the panel
   surface and the label ON the selection take bg. Changing one of them
   resets the whole thing, because no element here has a colour of its own. */
.menu-list{
  /* OVERLAY: it lies above everything, nothing covers it, and JS computes
     its position — first below the button, and when the window does not
     allow it, above or beside; always 4 px away. */
  position:fixed; z-index:200; width:max-content;
  visibility:hidden; opacity:0; transform:translateY(-4px);
  transition:opacity var(--dur-1) ease, transform var(--dur-2) var(--ease), visibility 0s var(--dur-2);
  box-shadow:var(--shadow-2);
  --menu-bg:var(--panel);
  --menu-ink:var(--ink);
  --menu-pad:var(--sp-1);             /* the panel's own frame: 4px */
  display:grid;
  padding:var(--menu-pad);
  background:var(--menu-bg);
  color:var(--menu-ink);
  border:var(--line-w) solid var(--menu-ink);
  border-radius:var(--r);
}
.menu[data-open="true"] .menu-list{
  visibility:visible; opacity:1; transform:none;
  transition:opacity var(--dur-1) ease, transform var(--dur-2) var(--ease);
}
@media (prefers-reduced-motion:reduce){.menu-list{transition:none}}
/* The panel can lay its options out in two columns — nothing but the
   number of columns changes: the same frame, the same choice, the same
   highlight. */
/* A palette without names: a row is a family, a column is a shade. With
   one shade the families go two to a row; with two or more each family
   gets its own row, so shades of the same colour read across and families
   read down. */
.menu-list.is-grid{grid-template-columns:repeat(var(--tones, 2), var(--row-h))}
.menu-list.is-grid .opt{
  width:var(--row-h); height:var(--row-h);
  padding:0; justify-content:center;
}
.menu-list.is-grid .opt .swatch{margin-left:0}
 /* no colour speaks with a label, not a glyph — there is nothing to show,
    so it has to be said */
.menu-list.is-grid .opt.is-wide{
  grid-column:1 / -1; width:auto;
  justify-content:flex-start;
  padding:0 calc(var(--btn-pad) - var(--menu-pad));
}
.button .swatch[hidden]{display:none}
.menu-list .opt{gap:var(--el-gap)}
/* colour swatch: a graphic thing with the side of a pill, so it stands
   as far from the panel edge as a pill stands from the surface edge */
.swatch{
  width:var(--pill-h); height:var(--pill-h); flex:none;
  border-radius:min(var(--r), calc(var(--pill-h) / 2));
  margin-left:calc((var(--row-h) - var(--pill-h)) / 2 - var(--btn-pad) + var(--menu-pad));
}
.menu-list .opt{gap:var(--el-gap)}
/* colour swatch: a graphic thing with the side of a pill, so it stands
   as far from the panel edge as a pill stands from the surface edge */
.swatch{
  width:var(--pill-h); height:var(--pill-h); flex:none;
  border-radius:min(var(--r), calc(var(--pill-h) / 2));
  margin-left:calc((var(--row-h) - var(--pill-h)) / 2 - var(--btn-pad) + var(--menu-pad));
}
/* No colour: an empty field crossed by a diagonal rule from corner to
   corner. The rule has the weight of the dividing rule and goes in the
   ink tone — this is not a colour, this is its cancellation. */
.menu-list .opt{
  height:var(--row-h);
  display:flex; align-items:center;
  padding:0 calc(var(--btn-pad) - var(--menu-pad));
  background:none; border:0; width:100%;
  color:inherit;
  /* concentric: the option radius is smaller by the panel frame */
  border-radius:max(0px, calc(var(--r) - var(--menu-pad)));
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; text-align:left; cursor:pointer;
}
/* Selection and highlight are the same material: a fill in the ink tone,
   the label in the surface tone. Only one lights at a time — when the
   cursor enters the panel the highlight follows it and the selected
   option gives way. */
.menu-list .opt[aria-selected="true"],
.menu-list .opt:hover{
  background:var(--menu-ink);
  color:var(--menu-bg);
}
.menu-list:hover .opt[aria-selected="true"]:not(:hover){
  background:none;
  color:var(--menu-ink);
}
.menu-list .opt:focus-visible{outline:2px solid var(--menu-ink); outline-offset:-2px}

/* The section name and the dividing rule have no colour of their own
   either: they take the ink tone and merely give part of their strength
   back to the background. */
.menu-list .sec{
  height:var(--sp-6);
  display:flex; align-items:center;
  padding:0 calc(var(--btn-pad) - var(--menu-pad));
  font-size:var(--fs-1); line-height:var(--lh-1);
  color:var(--menu-ink); opacity:.55;
}
.menu-list .sep{
  height:var(--sp-2);
  display:flex; align-items:center;
  /* the rule runs across the whole panel, so it reaches past its frame */
  margin-inline:calc(-1 * var(--menu-pad));
}
.menu-list .sep::before{
  content:''; display:block; width:100%;
  height:var(--line-w);
  background:color-mix(in srgb, var(--menu-ink) 20%, transparent);
}

/* one changed value — a different frame, different labels, a different selection */
.menu-list.is-signal{--menu-ink:var(--sig-5)}

/* ── LABEL AND FIELD ERROR ──
   The label stands ABOVE the field, not inside it: a hint inside
   disappears after the first letter, a label stays. The error message
   goes below the field, in the first signal tone; the field then changes
   the tone of its outline. */
.fld{display:grid; gap:var(--row-gap)}
.fld .lab{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.fld .err{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--sig-1)}
.fld.is-bad .input{outline-color:var(--sig-1)}

/* ── INPUT FIELDS ──
   Two variants and nothing else: outlined (empty, a frame in the ink
   tone) or sunken (in the canvas tone — as if cut out of the container
   surface). Height and inset as in a button, because it is the same
   family of forms. */
.input{
  height:var(--row-h);
  /* The field has its own width of 100% and may not exceed it: an input is
     an element with an intrinsic size of its own (the size attribute), so
     without this it pushes past the track and the right edge reaches
     outside the container body. */
  /* A fractional width (a track is 1fr) makes the box-model edge fall
     between pixels, and the right side can disappear. The outline is
     therefore drawn with OUTLINE pulled inwards: it is painted outside
     the box model, as one rectangle, so it loses no side and takes no
     room. */
  width:100%; max-width:100%; min-width:0;
  box-sizing:border-box;
  padding:0 var(--btn-pad);
  background:none; color:var(--ink);
  border:0;
  /* The line lies ENTIRELY inside the field, a pixel from its edge — not
     on it. On the edge of a fractional width the right side gets lost in
     rounding or clipped by overflow; pulled inwards it has nothing to
     catch on. */
  outline:var(--line-w) solid var(--ink);
  outline-offset:-1px;
  border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em;
  user-select:text; -webkit-user-select:text;
  transition:outline-color var(--dur-1) ease, outline-width var(--dur-1) ease;
}
.input.is-sunken{background:var(--canvas); outline-color:transparent}
.input::placeholder{color:var(--ink-2)}
.input:focus{outline:1.5px solid var(--ink); outline-offset:-1.5px}
/* text area: a whole number of modules, nothing in between */
textarea.input{
  height:calc(3 * var(--mod) - var(--row-gap));
  padding:var(--sp-2) var(--btn-pad);
  line-height:var(--lh-2); resize:none;
  display:block;                    /* without this it leaves a baseline underneath itself */
}

/* Multiple and single choice fields: the side of a status glyph, so they
   stand on the same line in a row as the glyphs do. Checked ones fill
   with the ink tone and the mark is cut out of them — as in "done". */
/* One check field, not two. It behaves like a pill with equal sides: the
   same height, the same inset from the edge and the same gap to the
   label. The radius follows the system radius — square at zero, a circle
   at the highest step. */
.check{
  width:var(--pill-h); height:var(--pill-h); flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:0; padding:0; cursor:pointer;
  border-radius:min(var(--r), calc(var(--pill-h) / 2));
  box-shadow:inset 0 0 0 var(--line-w) var(--ink);
  transition:background var(--dur-1) ease;
}
.check[aria-checked="true"]{background:var(--ink)}
.check svg{width:var(--ic-s); height:var(--ic-s); display:block; color:var(--panel); opacity:0}
.check[aria-checked="true"] svg{opacity:1}

/* Two-state switch: a track the height of a pill, a knob the size of a
   glyph, the width of an element — three measures, all already in the
   system. */
/* Two-state: the knob is 12, the track 20 — so 4 is left above, below
   and at the side. One distance all round, as in a pill and a check field. */
.toggle{
  width:var(--row-h); height:var(--pill-h); flex:none;
  padding:var(--sp-1); border:0; cursor:pointer;
  border-radius:min(var(--r), calc(var(--pill-h) / 2));
  background:color-mix(in srgb, var(--ink) 20%, var(--panel-2));
  display:flex; align-items:center;
  transition:background var(--dur-1) ease;
}
.toggle::before{
  content:''; display:block;
  width:var(--ic-s); height:var(--ic-s);
  border-radius:min(var(--r), calc(var(--ic-s) / 2));
  background:var(--panel);
  transition:transform var(--dur-2) var(--ease);
}
.toggle[aria-checked="true"]{background:var(--ink)}
.toggle[aria-checked="true"]::before{transform:translateX(calc(var(--row-h) - var(--ic-s) - 2 * var(--sp-1)))}
@media (prefers-reduced-motion:reduce){.toggle::before{transition:none}}

/* Switch: several positions in one track, the chosen one filled with the
   ink tone. The track has its own 4 px frame, so the positions are
   concentrically smaller — exactly like options in a menu panel. */
.switch{
  display:inline-grid; grid-auto-flow:column; grid-auto-columns:1fr;
  gap:var(--row-gap);
  height:var(--row-h); padding:var(--sp-1);
  background:var(--panel-2); border-radius:var(--r);
  width:max-content;
}
.switch.is-panel{background:var(--panel)}
.switch.is-outline{
  background:none;
  box-shadow:inset 0 0 0 var(--line-w) var(--ink);
}
.switch .sw-opt{
  display:flex; align-items:center; justify-content:center;
  min-width:calc(var(--row-h) - 2 * var(--sp-1));
  padding:0 calc(var(--btn-pad) - var(--sp-1));
  background:none; border:0; color:var(--ink);
  border-radius:max(0px, calc(var(--r) - var(--sp-1)));
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; cursor:pointer;
  transition:background var(--dur-1) ease, color var(--dur-1) ease;
}
.switch .sw-opt[aria-selected="true"]{background:var(--ink); color:var(--panel)}
.switch .sw-opt:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
@media (prefers-reduced-motion:reduce){.switch.is-panel{background:var(--panel)}
.switch.is-outline{
  background:none;
  box-shadow:inset 0 0 0 var(--line-w) var(--ink);
}
.switch .sw-opt{transition:none}}

/* Avatar: a circle with a side of 24 — element 32 minus two gaps of 4.
   One or two letters, or a number. The neutral one takes the metric tone
   with type in the surface tone; the coloured one takes the light tone of
   the family with the pill ink — the same material as a pill, only closed
   in a circle. */
.av{
  width:var(--av); height:var(--av); flex:none;
  border-radius:50%;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--ink-2); color:var(--panel);
  font-size:var(--fs-1); line-height:1;
  letter-spacing:0;                 /* two characters in a circle do not take negative tracking */
  text-transform:uppercase;
  font-variant-numeric:tabular-nums;
}
.av.a1{background:var(--c-1-s); color:var(--c-1-ink)}
.av.a4{background:var(--c-4-s); color:var(--c-4-ink)}
.av.a5{background:var(--c-5-s); color:var(--c-5-ink)}
.av.a6{background:var(--c-6-s); color:var(--c-6-ink)}
.avs{display:flex; align-items:center; gap:var(--el-gap)}

/* Header with an avatar: avatar on top, name below it, caption below
   that. 24 + 4 + 24 + 16 = 68, exactly two modules — the header grows by
   a module, not by an arbitrary number. */
.fhead.is-stacked{
  height:calc(2 * var(--mod) - var(--row-gap));
  display:grid; grid-template-columns:1fr auto;
  align-items:start; gap:var(--el-gap);
}
.fhead.is-stacked .l{display:block}
.fhead.is-stacked .av{margin-bottom:var(--row-gap)}
.fhead.is-stacked .n{display:block}
.fhead.is-stacked .sub{
  display:block;
  font-size:var(--fs-1); line-height:var(--lh-1);
  color:var(--ink-2);
}

/* container header — the first module, then one module skipped. No inset
   of its own: the header sits on the container frame, so the name and the
   number stand exactly on its edges. The 12 px inset belongs to the
   element surface, not to the container. */
.fhead{
  flex:none;
  height:var(--row-h);
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);
  font-size:var(--fs-3); line-height:var(--lh-3);
  margin-bottom:calc(var(--mod) + var(--row-gap));
}
.fhead .n{font-weight:var(--fw-med)}
.fhead .v{color:var(--ink-2); font-variant-numeric:tabular-nums}

/* ── blocks: everything stands on the module 32 + 4 ── */
.stack{
  display:grid; gap:var(--row-gap);
  /* minmax(0,1fr), not auto: without it the track grows to the widest
     content (a row with pills does not wrap), and anything standing at
     100% reaches past the container body and can be clipped. */
  grid-template-columns:minmax(0, 1fr);
}
.stack > *{min-width:0}
/* the break between groups is one skipped module: 36 + 4 */
.stack + .stack{margin-top:calc(var(--mod) + var(--row-gap))}
/* a group with a heading needs no break — the heading occupies that module */
.stack + .stack.titled{margin-top:var(--row-gap)}

.ghead{
  height:var(--row-h);
  display:flex; align-items:center; gap:var(--sp-2);
  /* the chevron hangs in the frame; the label sits on the type line of the rows (12 px) */
  padding:0 var(--sp-3) 0 calc(var(--sp-3) - var(--sp-2) - 12px);
  background:none; border:0; width:100%;
  color:var(--ink-2);
  font:var(--fw-reg) var(--fs-1)/var(--lh-1) var(--fam);
  letter-spacing:-.011em; text-align:left; cursor:pointer;
}
.ghead:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
.ghead svg{width:var(--ic-s); height:var(--ic-s); flex:none; color:var(--ink); transform:rotate(90deg); transition:transform var(--dur-2) var(--ease)}
.ghead[aria-expanded="false"] svg{transform:rotate(0deg)}
.ghead .c{margin-left:auto; font-variant-numeric:tabular-nums}

/* A series of values without edges: value, gap, dot, gap, value. The gap
   is the ordinary 4 px — the dot is a part like any other, so it gets no
   measure of its own. It stands at half the type height (·) and gives
   part of its strength back to the background. */
.vals{display:inline-flex; align-items:baseline; gap:var(--el-gap); font-variant-numeric:tabular-nums}
.vals .d{opacity:.55}
@media (prefers-reduced-motion:reduce){.ghead svg{transition:none}}
.stack > [hidden]{display:none}
.row{
  height:var(--row-h);
  background:var(--panel-2);
  border-radius:var(--r);
  display:flex; align-items:center; justify-content:space-between;
  padding:0 var(--sp-3);
  font-size:var(--fs-2); line-height:var(--lh-2);
}
.row .m{color:var(--ink-2); font-size:var(--fs-1); line-height:var(--lh-1)}
.row .l{display:flex; align-items:center; gap:var(--el-gap)}
.row .rt{display:flex; align-items:center; gap:var(--el-gap)}
/* labels to the baseline, glyphs and the pill to the middle of the row */
.row .txt{display:flex; align-items:baseline; gap:var(--el-gap)}
.row .t{font-size:var(--fs-2); line-height:var(--lh-2)}
.row .s{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}

.pill{
  height:var(--pill-h);
  display:inline-flex; align-items:center;
  padding:0 var(--pill-pad);
  background:var(--c-4-s); color:var(--c-4-ink);
  font-size:var(--fs-1); line-height:1;
  /* the pill radius cannot exceed half its height */
  border-radius:min(var(--r), calc(var(--pill-h) / 2));
}
.pill.p1{background:var(--c-1-s); color:var(--c-1-ink)}
.pill.p5{background:var(--c-5-s); color:var(--c-5-ink)}
/* DOPISANE PRZEZ AIC — nie nowa reguła, tylko ta sama dopowiedziana do
   końca. Guidelines piszą wprost: „A pill takes the light tone of the
   family and that family's own ink — the rule is one, so it stands here
   for all seven, not for two”. W arkuszu stały tylko dwie rodziny, bo
   tyle pokazywał przykład; panel potrzebuje siedmiu, a ósma pozycja —
   p0 — to plakietka bez rodziny: ton elementu i zwykły atrament
   metryczny, dla stanu, który niczego nie sygnalizuje. */
.pill.p2{background:var(--c-2-s); color:var(--c-2-ink)}
.pill.p3{background:var(--c-3-s); color:var(--c-3-ink)}
.pill.p4{background:var(--c-4-s); color:var(--c-4-ink)}
.pill.p6{background:var(--c-6-s); color:var(--c-6-ink)}
.pill.p7{background:var(--c-7-s); color:var(--c-7-ink)}
.pill.p0{background:var(--panel-2); color:var(--ink-2); box-shadow:inset 0 0 0 var(--line-w) var(--line)}

/* GRAPHIC INSET
   A thing with a form of its own — glyph, avatar, pill — stands at the
   surface edge at the same distance that separates it from the top and
   the bottom. The 12 px inset belongs to type; the form fits a square, so
   its side is computed from its own height: (32 − side) / 2. */
.row .l > .ic:first-child,
.row .l > .sw-i:first-child,
.row .l > .av:first-child{margin-left:calc((var(--row-h) - var(--av)) / 2 - var(--el-pad))}
.row .l > .ic:first-child,
.row .l > .sw-i:first-child{margin-left:calc((var(--row-h) - var(--st-size)) / 2 - var(--el-pad))}
.row .rt > .av:last-child{margin-right:calc((var(--row-h) - var(--av)) / 2 - var(--el-pad))}
.row .rt > .pill:last-child,
.row .rt > .toggle:last-child{margin-right:calc((var(--row-h) - var(--pill-h)) / 2 - var(--el-pad))}
.row .l > .check:first-child{margin-left:calc((var(--row-h) - var(--pill-h)) / 2 - var(--el-pad))}

/* ── WHAT GIVES WAY WHEN THERE IS NO ROOM ──
   The order is fixed and runs from the least significant: first the
   metric, then the pills, then the small label. The name never
   disappears — it is the last to start truncating, and only once it is
   left alone with the glyph. The container body is measured, not the window. */
.fbody{container-type:inline-size; container-name:body}
.row .txt{min-width:0}
.row .t{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.row .s{flex:none}
@container body (max-width: 300px){
  body:not([data-scale="large"]) .row .rt .m{display:none}
}
@container body (max-width: 250px){
  body:not([data-scale="large"]) .row .rt .pill{display:none}
}
@container body (max-width: 200px){
  body:not([data-scale="large"]) .row .txt .s{display:none}
}
.row.is-status{padding-left:var(--sp-3); cursor:pointer; text-align:left; border:0; width:100%; font:inherit; color:var(--ink)}
.row.is-status:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}

/* status glyph — one field, colour from the palette */
.ic{display:flex; flex:none}
/* colour of the cut-out in the "done" glyph — it takes the background
   the glyph lies on */
.st{--st-bg:var(--panel-2)}
.st{
  /* the glyph side follows the row: height minus two gaps — 16 px at 32 */
  width:var(--st-size); height:var(--st-size);
  flex:none; display:block;
}
[data-status="new"] .st{color:var(--ink-2)}
[data-status="progress"] .st{color:var(--sig-5)}
[data-status="review"] .st{color:var(--sig-6)}
[data-status="done"] .st{color:var(--sig-4)}
[data-status="done-o"] .st{color:var(--sig-4)}   /* PANEL: zrobione, ale jeszcze nie zamknięte */
[data-status="blocked"] .st{color:var(--sig-1)}
/* monochrome variant: the state carries shape alone, there is no colour */
.row.is-mono .st, .tile.is-mono .st{color:var(--ink)}

/* filled variant: colour takes the whole row, and the glyph and type
   drop to the pill ink — the same one, checked on every light tone */
.row.is-fill{color:var(--pill-ink)}
.row.is-fill .st{color:inherit}
.row.is-fill[data-status="progress"]{--pill-ink:var(--c-5-ink); color:var(--c-5-ink)}
.row.is-fill[data-status="review"]{--pill-ink:var(--c-6-ink); color:var(--c-6-ink)}
.row.is-fill[data-status="done"]{--pill-ink:var(--c-4-ink); color:var(--c-4-ink)}
.row.is-fill[data-status="blocked"]{--pill-ink:var(--c-1-ink); color:var(--c-1-ink)}
.row.is-fill .m{color:inherit; opacity:1}
.row.is-fill[data-status="new"]{background:var(--panel-2); color:var(--ink)}
.row.is-fill[data-status="progress"]{background:var(--c-5-s)}
.row.is-fill[data-status="review"]{background:var(--c-6-s)}
.row.is-fill[data-status="done"]{background:var(--c-4-s)}
.row.is-fill[data-status="blocked"]{background:var(--c-1-s)}
/* the cut-out in the "done" glyph takes the row background, not the surface */
.row.is-fill[data-status="done"] .st{--st-bg:var(--c-4-s)}

/* TILE IN THE STATUS COLOUR
   The whole card takes the colour. Every label — the small one and the
   metric included — then goes in FULL pill ink: dimming by opacity drops
   below the threshold on darker families (plum gives 3.2 at 65%), while
   the full tone holds at least 5.3 on every family of both palettes.
   Hierarchy is carried by the type level, not by tone strength. */
.tile.is-fill{color:var(--pill-ink)}
.tile.is-fill[data-status="progress"]{--pill-ink:var(--c-5-ink); color:var(--c-5-ink)}
.tile.is-fill[data-status="review"]{--pill-ink:var(--c-6-ink); color:var(--c-6-ink)}
.tile.is-fill[data-status="done"]{--pill-ink:var(--c-4-ink); color:var(--c-4-ink)}
.tile.is-fill[data-status="blocked"]{--pill-ink:var(--c-1-ink); color:var(--c-1-ink)}
.tile.is-fill .s,
.tile.is-fill .m{color:inherit; opacity:1}
.tile.is-fill .st{color:inherit}
.tile.is-fill[data-status="new"]{background:var(--panel-2); color:var(--ink)}
.tile.is-fill[data-status="new"] .s,
.tile.is-fill[data-status="new"] .m{color:var(--ink-2)}
.tile.is-fill[data-status="progress"]{background:var(--c-5-s)}
.tile.is-fill[data-status="review"]{background:var(--c-6-s)}
.tile.is-fill[data-status="done"]{background:var(--c-4-s)}
.tile.is-fill[data-status="blocked"]{background:var(--c-1-s)}
.tile.is-fill[data-status="done"] .st{--st-bg:var(--c-4-s)}
/* a pill and an avatar on colour lose their own hue — the colour is
   already taken by the card; what is left is the state material: ink at
   low strength */
.tile.is-fill .pill{
  background:color-mix(in srgb, var(--pill-ink) 12%, transparent);
  color:var(--pill-ink);
}
.tile.is-fill .av{
  background:color-mix(in srgb, var(--pill-ink) 20%, transparent);
  color:var(--pill-ink);
}

/* MIARA JEDNEGO TORU — pusty element o szerokości jednego toru i zerowej
   wysokości. Skrypt czyta z niego szerokość toru w pikselach zamiast liczyć
   ją z ułamków. Wycięty przy pierwszym przenoszeniu przez pomyłkę. */
.probe{grid-column:1/span 1; height:0}

/* status legend in the guidelines */







/* TILE — the height follows the content, but in jumps of a module.
   A tile is a card in a column view: switch off the caption or the pills
   and no empty space is left behind them — the tile drops by a whole
   module. The number of bands sets the number of modules (36n − 4). */
.tile{
  --rows:3;
  height:calc(var(--rows) * var(--mod) - var(--row-gap));
  background:var(--panel-2);
  border-radius:var(--r);
  padding:var(--el-pad);
  /* The gaps between bands are fixed (4), and the surplus is taken by the
     first band — the one with the name. The caption and the pills then
     hold to the bottom of the tile no matter how many modules it has. */
  display:flex; flex-direction:column; gap:var(--row-gap);
}
.tile .top{flex:1 1 auto}
.tile .m{color:var(--ink-2); font-variant-numeric:tabular-nums}
/* in a tile the name and the number are a pair on one line — one level for both */
.tile .top .m{font-size:var(--fs-2); line-height:var(--lh-2)}
.tile .top{display:flex; align-items:baseline; justify-content:space-between; gap:var(--el-gap)}
.tile .top .l{display:flex; align-items:center; gap:var(--el-gap); min-width:0}
.tile .t{font-size:var(--fs-2); line-height:var(--lh-2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.tile .s{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.tile .pills{display:flex; align-items:center; gap:var(--el-gap)}
/* Things standing in one row share one height: an avatar next to pills
   drops to their height, so the bottom band of the tile stays at 20. */
.tile .pills .av{width:var(--pill-h); height:var(--pill-h)}


/* ═══ 10. CLOSINGS ═════════════════════════════════════════════════

   The grid overlay lay in the window rather than in the work field: the
   field has its margin enlarged by the remainder of the track division,
   so a grid drawn on the bare margin stood a few pixels beside the
   containers. It now takes the same two values JS writes into the field
   padding. */


/* A pill takes the light tone of the family and that family's own ink —
   the rule is one, so it stands here for all seven, not for two. */

/* ── GLYPH AND LABEL IN A BUTTON ──
   Glyph and label are one block at the left edge; the chevron stands
   alone at the right and it is the only thing that rotates. Before, all
   three were separate children of the button, so they spread across the
   whole width and the rotation caught every glyph, not the one that
   speaks about state. */
.button .lab{
  --g:var(--ic-m);
  display:flex; align-items:center;
  /* glyph: as much as its own distance from the edge */
  gap:calc((var(--row-h) - var(--g)) / 2);
  min-width:0;
}
/* a swatch is not a glyph — it keeps the doubled gap, like an avatar */
.button .lab.has-swatch{--g:var(--pill-h); gap:calc(var(--row-h) - var(--g))}
/* ONE INSET FOR TYPE: 12 px, the same on an element surface and in a
   button. Only a graphic thing goes closer, by its own rule — a label
   never does, so the left edge of type is one line through the whole
   application. */
.button .lab > svg:first-child,
.button .lab > .swatch:first-child{
  flex:none;
  margin-left:calc((var(--row-h) - var(--g)) / 2 - var(--btn-pad));
}
.button .lab > svg:first-child{width:var(--g); height:var(--g); display:block}
/* a swatch inside the block no longer needs its own inset or a negative
   margin for the guard zone — the block computes them itself */
.button .lab .swatch{margin-right:0}

/* ── DISTANCE OF THE LABEL FROM A GRAPHIC THING ──
   A thing with a form of its own stands from the left edge at
   (32 − side) / 2. The label moves away from it by that distance times
   TWO when the thing has a surface of its own — avatar, check field,
   swatch — and times ONE when it is only a glyph: a glyph weighs less
   visually than the circle of an avatar, so at the doubled gap the label
   came loose from it. Which gives: avatar 24 → 4 and 8, check field and
   swatch 20 → 6 and 12, glyph 16 → 8 and 8. The block gap is 4, the
   margin adds the rest. */
.row .l > .av:first-child{--g:var(--av)}
.row .l > .ic:first-child,
.row .l > .sw-i:first-child{--g:var(--st-size)}
.row .l > .check:first-child{--g:var(--pill-h)}
.row .l > .av:first-child,
.row .l > .check:first-child{
  margin-left:calc((var(--row-h) - var(--g)) / 2 - var(--el-pad));
  margin-right:calc(var(--row-h) - var(--g) - var(--el-gap));
}
.row .l > .ic:first-child,
.row .l > .sw-i:first-child,
.tile .top .l > .ic:first-child{
  --g:var(--st-size);
  margin-left:calc((var(--row-h) - var(--g)) / 2 - var(--el-pad));
  margin-right:calc((var(--row-h) - var(--g)) / 2 - var(--el-gap));
}
/* menu option: a swatch has a surface, so the gap is doubled */
.menu-list .opt{gap:calc(var(--row-h) - var(--pill-h))}

/* ── GROUP READING ──
   A heading can carry a reading of the group: a word saying what is
   measured — Load, Pulse — and a number saying how much. The word takes
   a level, the number never does: a figure that changed colour would say
   the same thing twice, and the level is a judgement, not a measurement.

   SIGNAL INK. The word takes the strong tone of the family — the same
   one in both modes, not the light tone in dark. Two families only:
   yellow for a warning, red for an alert.
   The cost is written down rather than hidden: in dark mode both hold
   type (10.18 and 2.97 in palette A, 7.23 and 2.41 in palette B), and in
   light mode red holds at 3.96 and 4.88, but YELLOW GIVES 1.15 — it is
   a mark on a light surface, not a word that can be read. Yellow in
   light mode therefore needs a darker tone of the family, the same third
   tone the pill ink is already waiting for. */
.ghead .read{
  margin-left:auto;
  display:inline-flex; align-items:baseline; gap:var(--el-gap);
  font-variant-numeric:tabular-nums;
}
.ghead .read .w{color:var(--ink-2)}
.ghead .read .w[data-level="warn"]{color:var(--c-3)}
.ghead .read .w[data-level="alert"]{color:var(--c-1)}
.ghead .read .n{color:var(--ink-2)}

/* ── FIELD BLOCK ON THE MODULE ──
   A label plus a field is 16 + 4 + 32 = 52, which is not 36n − 4, so a
   form made of them drifts off the container grid. The missing 16 goes
   UNDER the block, not above the label: light above the label would sit
   between the group heading and the first field, where the ordinary gap
   of 4 belongs, and the form would open with a hole. Underneath it falls
   into the break that separates one field from the next.
   So: label 16, gap 4, field 32, break 16 — and with the 4 of the stack
   the next block starts 72 later, two modules exactly. With an error
   message it is 108, three; with a text area 144, four. */
.fld{gap:var(--row-gap); margin-bottom:var(--sp-4)}
/* The error message takes a module of its own, set to the top of it, so
   the block closes on the grid and the spare light falls under the
   message — where the break between fields already is. */
.fld .err{height:var(--row-h); display:flex; align-items:flex-start}

/* the system palette shows in the same shape as the ramp, six across */


/* ── FILL IN THE STATUS COLOUR, FROM THE SYSTEM PALETTE ──
   A filled row or card takes the system colour itself, not a light tone
   of a family, and the ink on it is always --s-ink. That is what makes a
   warning look identical in both modes and under either attached palette:
   the surface does not move, so the ink does not have to.
   Dark ink holds on all six — 4.76 on blue at the lowest. */
.row.is-fill[data-status="progress"], .tile.is-fill[data-status="progress"]{background:var(--s-5)}
.row.is-fill[data-status="review"],   .tile.is-fill[data-status="review"]{background:var(--s-6)}
.row.is-fill[data-status="done"],     .tile.is-fill[data-status="done"]{background:var(--s-4)}
.row.is-fill[data-status="blocked"],  .tile.is-fill[data-status="blocked"]{background:var(--s-1)}
.row.is-fill[data-status="progress"], .tile.is-fill[data-status="progress"]{--pill-ink:var(--s-5-ink)}
.row.is-fill[data-status="review"],   .tile.is-fill[data-status="review"]{--pill-ink:var(--s-6-ink)}
.row.is-fill[data-status="done"],     .tile.is-fill[data-status="done"]{--pill-ink:var(--s-4-ink)}
/* red is the one that carries light type — the ink belongs to the colour */
.row.is-fill[data-status="blocked"],  .tile.is-fill[data-status="blocked"]{--pill-ink:var(--s-1-ink)}
.row.is-fill[data-status="progress"], .tile.is-fill[data-status="progress"],
.row.is-fill[data-status="review"],   .tile.is-fill[data-status="review"],
.row.is-fill[data-status="done"],     .tile.is-fill[data-status="done"],
.row.is-fill[data-status="blocked"],  .tile.is-fill[data-status="blocked"]{color:var(--pill-ink)}
.row.is-fill[data-status="done"] .st{--st-bg:var(--s-4)}
.tile.is-fill[data-status="done"] .st{--st-bg:var(--s-4)}

/* the warning word in a group reading comes from the system palette too */
.ghead .read .w[data-level="warn"]{color:var(--s-2)}
.ghead .read .w[data-level="alert"]{color:var(--s-1)}

/* ── THE BAR HOLDS ITS TWO DISTANCES ──
   The work field is a grid with a row gap of 8, and the bar also carried
   a bottom margin of 8, so the buttons stood 16 from the containers —
   twice the gutter — and the distance moved with the layout instead of
   staying put. The gap alone does the work: 8 to the containers, always,
   whatever the margin does. Between the buttons it is the element gap, 4,
   and neither number is relative to anything. */
.app-bar{margin-bottom:0; gap:var(--row-gap)}

/* ── MOVING AN ELEMENT ──
   Dragging is the one place where the system finally uses its shadow: a
   thing being carried is raised, and a raised thing casts one. Everything
   else stays exactly as it is — the row keeps its tone, its height and its
   place in the module grid, because it is going to land back on it.
   The rest of the list answers with movement, not with a line: there is no
   insertion marker, no dotted frame, no gap of a different colour. The
   rows simply step aside by a module and the empty place they leave IS
   the drop target — it is already the right size, so what you see while
   dragging is what you get after letting go.
   Time: the carried row follows the pointer with no transition at all
   (anything else lags behind the hand), the rows stepping aside take
   dur-2, and the shadow fades in and out in dur-1. */
.row.is-move{touch-action:none}
.row.is-shift{transition:transform var(--dur-2) var(--ease)}
.row.is-lift{
  position:relative; z-index:5;
  box-shadow:var(--shadow-2);
  cursor:grabbing;
  transition:box-shadow var(--dur-1) ease;
}
/* the place it came from: the same module, held open, nothing drawn in it */
.row.is-hole{visibility:hidden}
@media (prefers-reduced-motion:reduce){
  .row.is-shift{transition:none}
}

/* An empty group holds one open module: without it there would be nothing
   to aim at, and a group you cannot drop into is a group you cannot fill.
   It is the same 32 the row will occupy, so the list does not jump when
   the row lands. */
.movelist .inner:empty{min-height:var(--row-h)}

/* While something is in the air the rows underneath stop answering the
   cursor: the pointer is over them the whole time, so their hover tone
   was lighting up behind the carried row and the list looked like it was
   flickering. They are still measured — geometry does not need events. */
.movelist.is-dragging .row:not(.is-lift){pointer-events:none; will-change:transform}

/* ── THE SURFACE BELONGS TO THE CONTAINER ──
   Whether a container shows its surface is a property of that container,
   not a setting for the whole application. All-or-nothing was the wrong
   shape: a work field usually wants one loud container and two quiet ones.
   The switch in the bar sets the DEFAULT; any container may say otherwise
   about itself.
   ONE THING CHANGES AND NOTHING ELSE: the container takes the tone of the
   application background. It does not go away — it keeps its tracks, its
   frame of 20, its header, its content and its movable boundary, and it
   can still be resized. Only the tone stops separating it from the canvas,
   so it reads as absent while behaving exactly as before. */
.frame[data-surface="off"]{background:var(--canvas)}
body[data-panel="off"] .frame:not([data-surface="on"]){background:var(--canvas)}
body[data-panel="off"] .frame[data-surface="on"]{background:var(--panel)}

/* ── ONE CHEVRON FOR ONE JOB ──
   A group heading is a row, so the mark on it is a glyph in a row: 16, not
   12. Twelve is the size of a glyph standing next to type — this one does
   not stand next to type, it stands in the frame, and at 16 it lands
   exactly on the graphic inset the system already has: (32 − 16) ÷ 2 = 8,
   which is where it was hanging anyway. One rule fewer, one exception
   fewer, and the same mark in a list and in a table. */
.ghead svg{width:var(--ic-m); height:var(--ic-m)}

/* ── GROUPS, ROWS AND COLUMNS IN A TABLE ────────────────────────────────
   A group in a table is a row that spans every column and carries a
   heading instead of values: the same 32, the same hairline, the same
   chevron. It cannot be a fold like a group in a list, because its rows
   belong to the table's own grid and a wrapper would take them out of it.
   So it closes the way a container closes — by height. Each row travels
   from 32 to 0 in dur-2, the hairline goes with it, and the rows below
   arrive without a jump.
   Rows and columns are carried the same way everything else is: the thing
   leaves the table, a hollow of its size stands in its place, and the rest
   steps aside. A column's hollow is a band down the whole table, because
   that is the shape of what is being moved. */
.trow.is-group{
  grid-column:1 / -1;
  display:flex; align-items:center; gap:var(--el-gap);
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
  cursor:pointer;
}
.trow.is-group .chev{width:var(--ic-m); height:var(--ic-m); flex:none; pointer-events:none;
  color:var(--ink);   /* the mark is a glyph, not a caption: full ink, as in a list */
  /* a cell's field adds 3 a side; a glyph is not a cell and must not
     shrink inside one, or 16 becomes 10 */
  padding:0; box-sizing:content-box;
  transition:transform var(--dur-2) var(--ease); transform:rotate(90deg)}
.trow.is-group[aria-expanded="false"] .chev{transform:none}
.trow.is-group .c{margin-left:auto; padding-inline:var(--cell-pad)}
.trow{transition:height var(--dur-2) var(--ease)}
.trow.is-folded{height:0; overflow:hidden; box-shadow:none}
@media (prefers-reduced-motion:reduce){.trow{transition:none}}

/* A row in a table has no surface — it lies on the container — but a row
   IN THE AIR must have one, or the list shows through it. It takes the
   container tone it was lying on, and the shadow every raised thing takes. */
.trow.is-lift{
  background:var(--panel);
  box-shadow:var(--shadow-2);
  z-index:5;
}

/* a row's hollow spans the table; a column's runs down it */
.trow.drop{grid-column:1 / -1; display:block; height:auto; padding:0; outline:0}
.col-drop{
  position:fixed; z-index:4; pointer-events:none;
  border-radius:var(--r);
  background:color-mix(in srgb, var(--ink) var(--press-down), transparent);
}
.trow.is-head > *{cursor:grab}
.trow.is-head.is-lift > *{cursor:grabbing}
.thead-ghost{
  position:fixed; z-index:5; pointer-events:none;
  box-shadow:var(--shadow-2);
  background:var(--panel);
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
  height:var(--row-h); display:flex; align-items:center;
  padding-inline:var(--cell-pad);
  border-radius:var(--r);
}

/* ── WRITING IN PLACE ────────────────────────────────────────────────────
   Not everything that can be written in is a field. A button's label, a
   group heading, a column head, a cell: these are things that already
   stand somewhere, and renaming one should not make it grow a box. So
   nothing is drawn — the caret goes in, the selection shows what will be
   replaced, and Enter keeps it.
   The two input fields (outlined and sunken) are for values that do not
   exist yet: an empty field is an invitation. A name that is already there
   needs no invitation, only permission. */
.is-writable{cursor:text}
.is-writable[contenteditable]{
  outline:0;
  /* the shape it lives in is not touched: no frame, no fill, no change of
     tone, no shift of a single pixel */
}
.is-writable[contenteditable]:focus-visible{outline:0}
/* the selection is the dividing rule's material: the ink tone at 20%,
   which is what a rule is made of — the one thing in the system that marks
   without occupying */
::selection{background:color-mix(in srgb, var(--ink) 20%, transparent)}

/* ── TABLE ──────────────────────────────────────────────────────────────
   A table is a list that divides with a RULE instead of a gap. That is the
   whole difference, and it decides everything else:
   — the rows touch, so the rhythm is the element alone: 32, and n rows are
     32n plus the hairlines between them;
   — a row has NO surface of its own. A surface and a rule would divide the
     same thing twice, and the system allows one division at a time. The
     row therefore lies on the container the way a group heading does;
   — and because it has no surface, its type stands on the container frame,
     not 12 px inside it: the 12 belongs to an element's surface, and there
     is none here.
   The rule goes only BETWEEN rows — never above the first or below the
   last, because that is where the surface ends and the surface draws its
   own boundary. It is drawn with an inset shadow rather than a border, so
   it takes no room and the row stays exactly 32.
   Columns are a grid: the first takes what is left, the others take their
   content. Between them stands one gap from the scale — 16 — and figures
   go to the right, so a column of numbers reads down its last digit. */
/* THE COLUMNS BELONG TO THE TABLE, NOT TO THE ROW. Each row measuring its
   own columns is what a list does, and a list is read across one line at a
   time; a table is read DOWN, so a column that moves from row to row
   cannot be read at all. The tracks are therefore set once on the table
   and every row takes them with subgrid — the rows keep their own surface
   for the hover and their own hairline, and the columns keep one edge.
   ALIGNMENT follows what stands in the cell: words and pills go left,
   because that is where reading starts; figures go right, so a column of
   numbers lines up on its last digit and can be compared without being
   read. */
.table{
  display:grid; width:100%;
  grid-template-columns:minmax(0, 1fr) repeat(var(--cols, 3), max-content);
  column-gap:var(--sp-4);
}
.trow{
  display:grid;
  grid-column:1 / -1;
  grid-template-columns:subgrid;
  align-items:center;
  height:var(--row-h);
  font-size:var(--fs-2); line-height:var(--lh-2);
}
.trow + .trow{box-shadow:inset 0 var(--line-w) 0 var(--line)}
/* EVERY CELL IS A FIELD, and the field is invisible until it is used.
   It is 6 px wider than what stands in it — 3 on each side — so there is
   something to hit beside the last letter, and so a column measures itself
   by the field rather than by the text. The minimum width of a column is
   therefore its widest cell plus those 6; the gap of 16 stands between the
   fields, not between the words. */
.trow > *{
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  padding-inline:var(--cell-pad);
  border-radius:var(--r);
}
.trow > *{text-align:left}
.trow .num{text-align:right; font-variant-numeric:tabular-nums}
/* THE HEAD DOES NOT FOLLOW THE COLUMN — it follows its own rule: every
   name reads left, because that is where reading starts, except the
   last, which sits at the right edge the way the last column's own
   content usually does. This holds regardless of what a column carries —
   a numeric column in the middle still gets a left-aligned name, and the
   last column's name goes right even where the column itself is text. */
.trow.is-head > *{text-align:left}
.trow.is-head > *:last-child{text-align:right}
/* the head of a table speaks with tone, like a group heading */
.trow.is-head{
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
}
/* a clickable row answers with the same material as everything else, but
   as an overlay: it has no surface to change */
.trow.is-live{cursor:pointer; transition:background var(--dur-1) ease}
.trow.is-live:hover{background:color-mix(in srgb, var(--ink) var(--press-hover), transparent)}
.trow.is-live:active{background:color-mix(in srgb, var(--ink) var(--press-down), transparent)}
/* the overlay is a background rather than a layer over the row: a layer
   would have to be stacked, and a stacked row traps the panel a pill in it
   opens underneath its neighbours */

/* A pill in a table is either a value or a handle. As a handle it takes a
   chevron and opens a panel — the same panel as a button does, measured by
   the same rule, so nothing new is introduced. The chevron is the system
   chevron at 12, and it sits inside the pill's own inset. */
.pill.is-menu-trigger{
  display:inline-flex; align-items:center; gap:var(--el-gap);
  cursor:pointer;
  border:0; font:inherit; letter-spacing:inherit;
  padding-right:calc(var(--pill-pad) - 2px);
}
.pill.is-menu-trigger .chev{
  width:var(--ic-s); height:var(--ic-s); flex:none;
  transition:transform var(--dur-2) var(--ease);
}
.pill.is-menu-trigger[aria-expanded="true"] .chev{transform:rotate(90deg)}
.pill.is-outline.is-menu-trigger{background:none}

/* A panel opened at the pointer is the same panel, only without a button
   to hold on to — so it keeps its own frame and its own measurement. */
.menu-list.is-context{position:fixed; visibility:visible; opacity:1; transform:none}

/* A cell that can be written in shows nothing until it is written in: the
   outline is the focus outline every other thing uses, pulled inwards. */
/* WRITING IN A TABLE IS WRITING IN PLACE — there is no field. The row
   answers the cursor, and that is the only answer; the cell shows nothing
   of its own, before or during the writing. What says where the writing is
   happening is the caret and the selection, which is what says it in a
   sentence too. A frame or a fill would be a field, and a field is a
   promise that something can be typed — a cell keeps that promise without
   drawing anything. */
.trow [contenteditable]{outline:0; cursor:text; min-width:1ch}
.trow [contenteditable]:focus{overflow:visible; text-overflow:clip}
/* not even for the keyboard: a browser treats a writable cell as
   focus-visible the moment it is clicked, so the outline would appear
   under the pointer as well. The sunken tone says it, and the caret
   stands in it. */
.trow [contenteditable]:focus-visible{outline:0}
.trow.is-head{cursor:context-menu}

/* ── THE HOLLOW LEFT BEHIND ──
   The carried thing leaves the list and this stands in its place: the ink
   of the surface at 6%, the material that already means "this one", at
   exactly the height of what left. No line, no dashed frame, no colour
   that means nothing anywhere else — a hollow rather than a mark.
   It is a real place in the list, not a drawing over it, which is why the
   rows below step apart to let it through. */
/* POPRAWKA AIC (nie z guidelines): w źródle obie te rzeczy nazywały się
   .drop — zagłębienie po podniesionym wierszu (tutaj) i strefa wgrywania
   z rozdziału 19. Rozdział 19 stoi niżej, więc wygrywał i zagłębienie
   dostawało wysokość 104 px oraz ramkę strefy. Zagłębienie jest zawężone
   do listy, w której się je przenosi; strefa zostaje bez zmian.
   DO ZGŁOSZENIA w guidelines — kolizja jest w źródle, nie tutaj. */
.movelist .drop{
  flex:none;
  border-radius:var(--r);
  background:color-mix(in srgb, var(--ink) var(--press-down), transparent);
}
/* a group is carried by its heading, so that is where the hand goes */
.is-movegroup > .ghead{cursor:grab}
.is-movegroup.is-lift > .ghead{cursor:grabbing}
/* a group in the air keeps the width it had and folds up inside it */
.stack.is-lift{box-shadow:var(--shadow-2); z-index:5}

/* ── A GROUP OPENING AND CLOSING ──
   Collapsing is a change of height, so it takes dur-2 and the rows below
   travel the same distance in the same time — the list closes as one
   movement rather than as a jump followed by a settle. The heading stays
   where it is, because it holds the module either way. */
.stack .fold{
  display:grid; grid-template-rows:1fr;
  transition:grid-template-rows var(--dur-2) var(--ease);
  min-height:0;
}
.stack .fold > .inner{overflow:hidden; display:flex; flex-direction:column; gap:var(--row-gap)}
/* the fold clips its content so it can close by height — but a row being
   carried has to be able to leave it, so the clipping is lifted for as
   long as something is in the air */
.movelist.is-dragging .stack:not(.is-lift) > .fold > .inner{overflow:visible}
.stack .fold.is-closed{grid-template-rows:0fr}
.stack .fold.is-closed > .inner{opacity:0; transition:opacity var(--dur-1) ease}
.stack .fold > .inner{opacity:1; transition:opacity var(--dur-1) ease var(--dur-1)}
@media (prefers-reduced-motion:reduce){
  .stack .fold, .stack .fold > .inner{transition:none}
}

/* ── A TILE OF ONE BAND ──
   36n − 4 has an n = 1, and it comes to 32 — the height of an element.
   A tile with a single band therefore looks exactly like a row, and that
   is right rather than a collision: both stand on the same grid, so at
   the bottom of the scale they meet. What tells them apart is what they
   are, not how tall they are — a tile can grow a band, a row cannot.
   Only the vertical inset changes. At two bands and more it is the
   surface inset of 12; at one band the light above and below the line
   goes to 6, exactly as in a row, because 12 would leave the line no
   room inside 32. The horizontal inset never moves — it is the type
   inset, and type does not care how many bands stand under it. */
.tile{
  --pad-y:calc(var(--el-pad) - (2 - min(var(--rows), 2)) * 6px);
  padding:var(--pad-y) var(--el-pad);
}

/* ── A CLICKABLE ELEMENT ──
   An element that can be clicked can be pointed at, so it has to answer.
   The answer is the material the system already uses for every state: the
   ink of THAT surface, laid over it at 6% under the cursor and 12% while
   pressed. It is one overlay, not two rules, so it does not need to know
   what it lies on — which is what lets it work equally on a row in the
   system tone and on a row filled with a family colour, where the ink is
   the pill ink of that family.
   Only .is-live reacts. An element that carries information and does
   nothing stays still, and the difference between the two is visible
   before the click rather than after it. */
.row.is-live, .tile.is-live{cursor:pointer; position:relative}
.row.is-live::after, .tile.is-live::after{
  content:''; position:absolute; inset:0;
  border-radius:inherit;
  background:var(--state-ink, var(--ink));
  opacity:0;
  transition:opacity var(--dur-1) ease;
  pointer-events:none;
}
/* ON A COLOUR the overlay does the work, because a family colour has no
   ramp to step along: the ink that writes on that colour, at 6% and 12%. */
.row.is-fill.is-live:not(.is-lift):hover::after,
.tile.is-fill.is-live:not(.is-lift):hover::after{opacity:var(--press-hover)}
.row.is-fill.is-live:not(.is-lift):active::after,
.tile.is-fill.is-live:not(.is-lift):active::after{opacity:var(--press-down)}
.row.is-fill, .tile.is-fill{--state-ink:var(--pill-ink)}

/* IN THE SYSTEM TONE the state steps along the ramp itself. The ramp
   already holds exactly these steps, so nothing has to be mixed:
   light  — element t3 (the canvas tone) · pointed at t4 · chosen t7
   dark   — element t6 · pointed at t5 · chosen t4
   One step towards the ink under the cursor, and the far half of the ramp
   for the choice. Pressing shows the tone the choice will land on, so the
   answer to a click is the same thing before and after it. */
/* A thing in the air is neither pointed at nor chosen — it is raised.
   Without this it took the pressed tone for as long as the button was
   held, and the carried row read as selected. */
.row.is-live:not(.is-lift):hover, .tile.is-live:not(.is-lift):hover{background:var(--el-hover)}
.row.is-live:not(.is-lift):active, .tile.is-live:not(.is-lift):active,
.row.is-live[aria-selected="true"], .tile.is-live[aria-selected="true"]{
  background:var(--el-sel); color:var(--panel);
}
/* the small type is the one thing that would fall under the threshold on
   the hover tone (3.75 in dark mode), so under the cursor it comes up to
   the full ink — the state has the attention anyway */
.row.is-live:hover .m, .row.is-live:hover .s,
.tile.is-live:hover .m, .tile.is-live:hover .s{color:var(--ink)}

/* A TILE IN THE SYSTEM TONE FOLLOWS THE ELEMENT, exactly. Same tone, same
   four intensities, same cursor — a card is a row that grew a band, so it
   answers the same way. Only a tile in the status colour differs, and it
   differs the way a filled row does: by overlay, because a colour has no
   ramp to step along.
   Selection wins over a colour fill — the fill says what the thing IS, the
   selection says what I am doing with it now, and the thing that speaks
   about this moment comes first. The state glyph stays, so nothing is lost
   but the tone. */
.tile.is-live[aria-selected="true"] .m,
.tile.is-live[aria-selected="true"] .s,
.tile.is-live:active .m, .tile.is-live:active .s{color:inherit; opacity:.65}
.tile.is-live[aria-selected="true"] .st{color:var(--panel)}
.tile.is-live[aria-selected="true"]::after{background:var(--panel)}
.row.is-live[aria-selected="true"] .m,
.row.is-live[aria-selected="true"] .s{color:inherit; opacity:.65}
.row.is-live[aria-selected="true"] .st{color:var(--panel)}
.row.is-live[aria-selected="true"]::after{background:var(--panel)}

/* Focus does not replace any of them — it adds an outline, and only for
   keyboard navigation. */
.row.is-live:focus-visible, .tile.is-live:focus-visible{
  outline:2px solid var(--ink); outline-offset:-2px;
}
[disabled], [aria-disabled="true"]{opacity:var(--off); pointer-events:none}
@media (prefers-reduced-motion:reduce){
  .row.is-live::after, .tile.is-live::after{transition:none}
}

/* ══ 11. TWO SCALES ═══════════════════════════════════════════════════
   The same system at one and a half times the size — not a zoom, a second
   set of values. 1.5 is the only multiplier that keeps every number whole:
   the system stands on 4, and 4 × 1.5 = 6, while 1.6 would give 6.4 and
   take the base apart. Every measure below is exactly 1.5 × its normal
   self, so the proportions are identical — the light above type in a row
   goes from 6 to 9, the graphic inset from 4 to 6 and from 8 to 12, all
   by the same rule and not by a second decision.
   One value cannot be multiplied cleanly: type level 1, 11 × 1.5 = 16.5.
   It takes 16, so that its leading is 24 — a whole step of the enlarged
   rhythm — rather than a fraction nobody could stand anything on.
   The hairline stays at 0.5: a hair is a hair at any size, and it is the
   one thing in the system that draws a boundary rather than occupies a
   place. */
body[data-scale="large"]{
  /* work field */
  --gut:12px;
  --col-min:33px;                     /* the track stops shrinking later */
  --tracks-min:9;                     /* and a container needs more of them */

  /* spacing scale — the whole of it, one step up */
  --sp-1:6px;  --sp-2:12px; --sp-3:18px; --sp-4:24px; --sp-5:30px;
  --sp-6:36px; --sp-7:48px; --sp-8:72px; --sp-9:108px;

  /* inside of the container */
  --pad:30px;
  --row-h:48px;
  --row-gap:var(--sp-1);
  --mod:54px;                         /* 48 + 6 → anything taller: 54n − 6 */

  /* inside of the element */
  --el-pad:18px;
  --el-gap:var(--sp-1);
  --pill-h:30px;
  --pill-pad:12px;
  --btn-pad:18px;
  --guard:48px;
  --av:36px;                          /* 48 − two gaps of 6 */
  --st-size:24px;                     /* 48 − two gaps of 12 */
  --ic-s:18px; --ic-m:24px; --ic-l:36px;
  --radio-r:15px;                     /* half of 30, the enlarged check field */

  /* type — leading is a multiple of 6 here, as it was of 4 before */
  --fs-1:16px; --lh-1:24px;
  --fs-2:21px; --lh-2:30px;
  --fs-3:27px; --lh-3:36px;
  --fs-4:36px; --lh-4:48px;
}
/* The bar of THIS document stays at the normal size. It is the instrument
   the system is being looked at with, not part of what is being shown —
   a ruler does not grow with the thing it measures. In an application the
   bar above the containers is part of the work and grows with everything
   else. */
body[data-scale="large"] .bar{
  --row-h:32px; --row-gap:4px; --btn-pad:12px; --guard:32px;
  --pill-h:20px; --ic-s:12px; --ic-m:16px;
  --fs-1:11px; --lh-1:16px; --fs-2:14px; --lh-2:20px;
  --sp-1:4px; --sp-2:8px; --sp-3:12px;
}

/* the radius steps go up with everything else */
body[data-scale="large"][data-radius="8"]{--r:12px}
body[data-scale="large"][data-radius="16"]{--r:24px}

/* What gives way, at the enlarged size: the same order, the same reason,
   the thresholds multiplied like everything else. */
@container body (max-width: 450px){
  body[data-scale="large"] .row .rt .m{display:none}
}
@container body (max-width: 375px){
  body[data-scale="large"] .row .rt .pill{display:none}
}
@container body (max-width: 300px){
  body[data-scale="large"] .row .txt .s{display:none}
}

/* ── DEMONSTRATION BLOCK ──
   A piece of the work field standing inside the text: canvas, and on it
   the containers the paragraph above is talking about. Nothing here is
   hidden behind a switch — the rule and the thing it rules stand on one
   scroll, so reading and checking are the same movement.
   The container inside a block is not fixed: it takes its height from its
   content, because it is an illustration, not a work field. */

/* inside a block the bar keeps the first row and the containers take the
   second — a full-width child would otherwise be pushed below them */






/* ── COLUMNS IN A MENU PANEL ──
   A panel may lay its options out in more than one column. When it does,
   every column takes the width of the LONGEST option in the whole panel,
   not of the longest one in its own column — otherwise two columns of
   different widths would read as two different lists standing side by
   side, and the eye would look for a reason that is not there.
   One token does it: the panel is measured by its content (width is
   max-content), and the columns are 1fr, which in that measurement means
   "as wide as the widest thing anywhere in me". Nothing is computed. */
.menu-list{grid-template-columns:repeat(var(--menu-cols), 1fr)}

/* ── INK ON A COLOUR ──
   A colour field carries text, so the panel that picks the colour also
   picks the ink on it. Light and Dark are the two ends of the ramp —
   between them there is nothing, because ink on colour is a decision
   about contrast, not a third tone.
   The swatches are one position in the panel, not the panel itself: a
   grid of squares 32 wide at the ordinary gap of 4. Everything below is
   an ordinary position, so the panel measures itself by the widest label
   and the swatches keep their rhythm whatever those labels say. */
.menu-list .sw-grid{
  display:grid;
  grid-template-columns:repeat(var(--tones, 2), var(--row-h));
  gap:var(--row-gap);
  padding-bottom:var(--row-gap);
}
.menu-list .sw-grid .opt{
  width:var(--row-h); height:var(--row-h);
  padding:0; justify-content:center;
}
.menu-list .sw-grid .opt .swatch{margin-left:0}
.menu-list .opt .m{margin-left:auto; font-variant-numeric:tabular-nums; opacity:.55}
.menu-list .opt[aria-selected="true"] .m,
.menu-list .opt:hover .m{opacity:.75}
/* the picked colour with its ink, shown as an ordinary element */
.row.is-pick{background:var(--pick, var(--panel-2)); color:var(--pick-ink, var(--ink))}
.row.is-pick .m, .row.is-pick .s{color:inherit; opacity:.75}

/* A row of tools: the same as in the container header — things on one
   line, the element gap. The rule was already in the stylesheet, it only
   has a wider reach. */
.tools{display:flex; align-items:center; gap:var(--row-gap); min-height:var(--row-h); flex-wrap:wrap}
.tools .buttons{width:100%}

/* Pill */
.pill.p1{background:var(--c-1-s); color:var(--c-1-ink)}
.pill.p2{background:var(--c-2-s); color:var(--c-2-ink)}
.pill.p3{background:var(--c-3-s); color:var(--c-3-ink)}
.pill.p4{background:var(--c-4-s); color:var(--c-4-ink)}
.pill.p5{background:var(--c-5-s); color:var(--c-5-ink)}
.pill.p6{background:var(--c-6-s); color:var(--c-6-ink)}
.pill.p7{background:var(--c-7-s); color:var(--c-7-ink)}

/* THIRD PILL MATERIAL: no fill, an outline in the ink tone. The closed
   list is then the same as the button's — a colour, and no colour at
   all — so a pill that carries no family says nothing with colour and
   does not have to borrow one. The outline is an inset shadow, not a
   border, so it takes no room from the 20 px and the pill keeps its
   place in the row. */
.pill.is-outline{
  background:none;
  color:var(--ink);
  box-shadow:inset 0 0 0 var(--line-w) var(--ink);
}
/* on a card or a row in the status colour it takes the pill ink, like
   every other part standing there */
.tile.is-fill .pill.is-outline,
.row.is-fill .pill.is-outline{
  background:none;
  color:var(--pill-ink);
  box-shadow:inset 0 0 0 var(--line-w) var(--pill-ink);
}

/* ══ 12. FORM ═════════════════════════════════════════════════════════
   The second run of components. Nothing below invents a measure: the
   element is still 32, the inset still 12, the gap still 4, the corner
   still the system corner. Two numbers are new and both are halves of
   numbers that were already here — the radius of a radio (half the side
   of a check field) and the mark under a chosen tab (half the gap). */

/* RADIO — one answer out of several.
   The check field follows the system radius, so at step 0 it is a
   square. A radio doing the same would be a SECOND square, and the
   difference between "several answers" and "one answer" would disappear
   together with the shape. So the radio opts out of the radius switch:
   it is a circle at every step. That is the whole reason --radio-r
   exists instead of --r. Everything else is the check field — the same
   side, the same hairline frame, the same fill in the ink tone once it
   is answered. */
.radio{
  width:var(--pill-h); height:var(--pill-h); flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:0; padding:0; cursor:pointer;
  border-radius:var(--radio-r);
  box-shadow:inset 0 0 0 var(--line-w) var(--ink);
  transition:background var(--dur-1) ease;
}
/* the dot is 8 — the gap of the system doubled, so 6 is left all round */
.radio::before{
  content:''; display:block;
  width:var(--sp-2); height:var(--sp-2); border-radius:50%;
  background:var(--panel);
  transform:scale(0);
  transition:transform var(--dur-1) var(--ease);
}
/* the frame comes to a full pixel under the cursor — the field answers
   without changing size, and the row it stands in answers too */
.radio:hover{box-shadow:inset 0 0 0 1px var(--ink)}
.radio[aria-checked="true"]{background:var(--ink)}
.radio[aria-checked="true"]::before{transform:none}
.radio:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
.row .l > .radio:first-child{margin-left:calc((var(--row-h) - var(--pill-h)) / 2 - var(--el-pad))}
@media (prefers-reduced-motion:reduce){.radio,.radio::before{transition:none}}

/* SELECT — a field that opens a panel.
   It is the input's geometry and the button's behaviour: the height and
   the inset of a field, the chevron and the guard zone of a button, the
   panel of a menu. Two materials, the same two the input has — outlined
   and sunken; there is no third, because a select is a field and a field
   is either drawn or cut out. */
.menu.is-field{grid-template-columns:minmax(0, 1fr)}
.sel{
  height:var(--row-h); width:100%;
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--guard);
  padding:0 var(--btn-pad);
  background:none; color:var(--ink); border:0;
  outline:var(--line-w) solid var(--ink); outline-offset:-1px;
  border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; text-align:left; cursor:pointer;
  transition:outline-color var(--dur-1) ease, opacity var(--dur-1) ease;
}
.sel.is-sunken{background:var(--canvas); outline-color:transparent}
/* Under the cursor a FIELD takes the state material — ink at 6% — rather
   than losing a quarter of its strength the way a button does. A button
   is made of a material and can give some of it back; an outlined field
   is made of nothing but its own frame, and dimming the frame reads as
   the field switching off, not as the field answering. */
.sel:hover{background:var(--veil-6)}
.sel[aria-expanded="true"],
.sel:focus-visible{outline:1.5px solid var(--ink); outline-offset:-1.5px; opacity:1}
.sel .chev{width:var(--ic-s); height:var(--ic-s); flex:none; transition:transform var(--dur-2) var(--ease)}
.sel[aria-expanded="true"] .chev{transform:rotate(90deg)}
.sel .lab{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* the placeholder is not a value: it takes the metric tone until one is chosen */
.sel.is-empty .lab{color:var(--ink-2)}
@media (prefers-reduced-motion:reduce){.sel .chev{transition:none}}

/* SEARCH — a field with a glyph in front of it.
   The glyph is a graphic thing, so it stands at the field edge by the
   distance that separates it from the top and the bottom: (32 − 16) / 2
   = 8. The text then starts at 8 + 16 + 4 = 28, and that is the only
   place in the system where type does not begin at 12 — the glyph took
   the inset and the gap, and both are system numbers. */
.search{
  height:var(--row-h); width:100%;
  display:flex; align-items:center; gap:var(--sp-2);
  padding-left:calc((var(--row-h) - var(--ic-m)) / 2);
  padding-right:var(--btn-pad);
  background:var(--canvas); border-radius:var(--r);
  outline:var(--line-w) solid transparent; outline-offset:-1px;
  transition:outline-color var(--dur-1) ease;
}
.search:focus-within{outline-color:var(--ink); outline-width:1.5px}
.search .gl{width:var(--ic-m); height:var(--ic-m); flex:none; color:var(--ink-2)}
.search:focus-within .gl{color:var(--ink)}
.search input{
  flex:1; min-width:0; border:0; background:none; outline:0; color:var(--ink);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam); letter-spacing:-.011em;
  user-select:text; -webkit-user-select:text;
}
.search input::placeholder{color:var(--ink-2)}
.search .clear{
  appearance:none; border:0; background:none; cursor:pointer; padding:0;
  width:var(--ic-s); height:var(--ic-s); flex:none; color:var(--ink-2);
  display:grid; place-items:center;
}
.search .clear[hidden]{display:none}
.search .clear:hover{color:var(--ink)}
.search .clear svg{width:var(--ic-s); height:var(--ic-s)}

.tools .cap{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2); padding-left:var(--sp-2)}

/* STEPPER — a number and two tools.
   Not a new element: a field between two square tool buttons at the
   ordinary gap of 4. The number is centred and set in tabular figures,
   so the field does not change width while it counts. */
.stepper{display:inline-flex; align-items:center; gap:var(--row-gap)}
/* The value is SUNKEN — the canvas tone, as if cut out of the container —
   and the two tools take the element tone. That way the thing being
   changed and the things that change it read as two different kinds of
   object, without either of them taking a frame. */
.stepper .num{
  height:var(--row-h); min-width:calc(2 * var(--row-h));
  display:inline-flex; align-items:center; justify-content:center;
  padding:0 var(--btn-pad);
  background:var(--canvas);
  border-radius:var(--r);
  font-variant-numeric:tabular-nums;
}
.stepper .button{background:var(--panel-2)}
.stepper .button:hover{background:var(--el-hover); opacity:1}
.stepper .button[disabled]{opacity:var(--off); cursor:default}

/* ══ 13. FEEDBACK ═════════════════════════════════════════════════════
   What the application says about itself. The system palette carries the
   meaning and — the rule from the colour section — it carries it AS A
   SURFACE, never as type: the six signal tones as letters on a light
   panel give between 1.15 and 3.96. So a level is said by a filled form
   of 16 or 8 px standing next to ordinary ink, not by ink of its own. */

/* BADGE — how many, and that something is happening.
   The side is 16 — the side of a status glyph — so a badge sits on a
   glyph, on an avatar and in a row without introducing a height. The dot
   is 8, which is the gap doubled. The count is set in the smallest type
   level and in tabular figures; the pill inset would be too much at this
   side, so the padding is half a gap and the min-width does the rest. */
.badge{
  height:var(--st-size); min-width:var(--st-size);
  padding:0 var(--sp-1);
  display:inline-flex; align-items:center; justify-content:center;
  flex:none;
  background:var(--ink-2); color:var(--panel);
  font-size:var(--fs-1); line-height:1;
  font-variant-numeric:tabular-nums;
  letter-spacing:0;
  border-radius:min(var(--r), calc(var(--st-size) / 2));
}
.badge.is-dot{
  width:var(--sp-2); min-width:0; height:var(--sp-2);
  padding:0; border-radius:50%;
}
/* five levels, and yellow is not one of them: it is the one system tone
   that may only ever be a surface under dark type, and a warning in this
   system speaks in orange — the same decision the group reading makes */
.badge.l-info{background:var(--sig-5); color:var(--s-5-ink)}
.badge.l-done{background:var(--sig-4); color:var(--s-4-ink)}
.badge.l-warn{background:var(--sig-2); color:var(--s-2-ink)}
.badge.l-error{background:var(--sig-1); color:var(--s-1-ink)}
/* A badge ON something — a glyph, an avatar, a tool button. It hangs by
   one gap over the top right corner, so it overlaps the thing it belongs
   to instead of pushing it aside. */
.badged{position:relative; display:inline-flex}
.badged > .badge{position:absolute; top:calc(-1 * var(--sp-1)); right:calc(-1 * var(--sp-1))}

/* a badge is a graphic thing, so at the edge of a row it keeps the
   distance that separates it from the top and the bottom: (32 − 16) / 2 */
.row .rt > .badge:last-child{margin-right:calc((var(--row-h) - var(--st-size)) / 2 - var(--el-pad))}
.row .l > .badge:first-child{margin-left:calc((var(--row-h) - var(--st-size)) / 2 - var(--el-pad))}

/* ALERT — a sentence the application says without being asked.
   The surface IS the level. The system palette was made for exactly this:
   every one of its six tones carries a checked ink of its own, so the
   alert takes s-N as a fill and ink/s-N as type and nothing has to be
   picked by hand. Red is the one that carries light type — visible below
   without a second rule anywhere.
   Height: one module when it is a sentence, two when the sentence has an
   explanation under it. The glyph is 16 and stands on its own inset of 8;
   type stands on 12. The neutral level takes the element tone and ordinary
   ink — a message that is not a signal does not take a colour. */
.alert{
  display:flex; align-items:flex-start; gap:var(--sp-2);
  width:100%; min-height:var(--row-h);
  padding:var(--sp-2) var(--el-pad) var(--sp-2) var(--sp-2);
  border-radius:var(--r);
  background:var(--panel-2); color:var(--ink);
}
/* the glyph sits on the middle of the first line: (20 − 16) / 2.
   It is .gl, not .ic — .ic belongs to the status glyph, which is drawn by
   the generator and would overwrite anything placed inside it. */
.alert > .gl{
  width:var(--st-size); height:var(--st-size); flex:none;
  margin-top:calc((var(--lh-2) - var(--st-size)) / 2);
}
.alert .txt{display:flex; flex-direction:column; min-width:0; flex:1}
.alert .t{font-weight:var(--fw-med)}
.alert .d{font-size:var(--fs-1); line-height:var(--lh-1); opacity:.8}
.alert[data-level="error"]{background:var(--s-1); color:var(--s-1-ink)}
.alert[data-level="warning"]{background:var(--s-2); color:var(--s-2-ink)}
.alert[data-level="done"]{background:var(--s-4); color:var(--s-4-ink)}
.alert[data-level="info"]{background:var(--s-5); color:var(--s-5-ink)}
/* the dismiss glyph takes the ink of whatever surface it is standing on,
   so it needs no colour of its own on any of the five levels */
.alert .close{
  appearance:none; border:0; background:none; cursor:pointer; color:inherit;
  width:var(--st-size); height:var(--st-size); flex:none; padding:0;
  margin-top:calc((var(--lh-2) - var(--st-size)) / 2);
  display:grid; place-items:center;
}
.alert .close:hover{opacity:.75}
.alert .close:focus-visible{outline:2px solid currentColor; outline-offset:2px}

/* PROGRESS — a number told as a length.
   The library holds ONE bar and moves the width of its fill; a variant
   per ten percent would be modelling a number with variants. Two tones
   and no more: ink, when the bar is only saying how far the work has
   gone, and the signal tone when the state itself is being reported.
   The height is 4 — the gap of the system, its smallest measure, and the
   only one small enough to read as a rule rather than as a surface. */
.prog{
  display:block; width:100%;
  height:var(--sp-1);
  background:var(--line);
  border-radius:min(var(--r), calc(var(--sp-1) / 2));
  overflow:hidden;
}
.prog > .fill{
  display:block; height:100%; width:0;
  background:var(--ink);
  border-radius:inherit;
  transition:width var(--dur-2) var(--ease);
}
.prog.is-signal > .fill{background:var(--sig-5)}
.prog-row{display:grid; gap:var(--el-gap); padding:var(--el-gap) 0}
.prog-row .top{display:flex; align-items:baseline; justify-content:space-between; gap:var(--el-gap)}
.prog-row .m{color:var(--ink-2); font-size:var(--fs-1); line-height:var(--lh-1); font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){.prog > .fill{transition:none}}

/* ══ 14. OVERLAY ══════════════════════════════════════════════════════
   Three things that lie above the work instead of in it. */

/* SCRIM — the only value in the system outside the scale of states.
   States move in 6% and 12%; a scrim has the opposite job — it has to
   CUT the work off, not shade it. 40% does that in light mode. In dark
   mode 40% would not: the ink there is a light tone and the canvas is
   nearly black, so the veil has to reach 60% before what is under it
   stops competing for attention. */
.scrim{
  position:fixed; inset:0; z-index:300;
  background:var(--scrim);
  opacity:0; visibility:hidden;
  transition:opacity var(--dur-3) ease, visibility 0s var(--dur-3);
}
.scrim[data-open="true"]{opacity:1; visibility:visible; transition:opacity var(--dur-3) ease}

/* MODAL — a container that came to the front.
   Everything about it is the container: the panel tone, the 20 px frame,
   the system corner, the header on the first module with one module
   skipped under it. Its width is measured in tracks, like every other
   container — eight of them — and it never passes the app margin.
   It casts NO shadow. The scrim is what lifts it; a shadow on top of a
   scrim would be a second answer to a question already answered, and the
   shadow in this system belongs to exactly two things — a row being
   carried and a menu panel. */
.modal{
  position:fixed; z-index:301;
  top:50%; left:50%; transform:translate(-50%, -50%) scale(.98);
  width:min(calc(8 * var(--col) + 7 * var(--gut)), calc(100vw - 2 * var(--m)));
  max-height:calc(100vh - 2 * var(--m));
  background:var(--panel); color:var(--ink);
  border-radius:var(--r);
  box-shadow:var(--shadow-2);
  padding:var(--pad);
  display:flex; flex-direction:column;
  opacity:0; visibility:hidden;
  transition:opacity var(--dur-3) ease, transform var(--dur-3) var(--ease), visibility 0s var(--dur-3);
}
.modal[data-open="true"]{
  opacity:1; visibility:visible; transform:translate(-50%, -50%) scale(1);
  transition:opacity var(--dur-3) ease, transform var(--dur-3) var(--ease);
}
.modal .mhead{
  flex:none; height:var(--row-h);
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);
  font-size:var(--fs-3); line-height:var(--lh-3);
  margin-bottom:calc(var(--mod) + var(--row-gap));
}
.modal .mhead .n{font-weight:var(--fw-med)}
.modal .mhead .close{
  appearance:none; border:0; background:none; cursor:pointer; color:inherit;
  width:var(--ic-m); height:var(--ic-m); padding:0; flex:none;
  display:grid; place-items:center;
}
.modal .mhead .close:hover{opacity:.75}
/* A BUTTON IN THE SIGNAL TONE — the one place a button takes a colour.
   It is not a fourth material: it is the ordinary button with the system
   red as its surface and that red's own ink as its type. Reserved for the
   answer that destroys something, and never more than one per view. */
.button.is-signal{background:var(--s-1); color:var(--s-1-ink)}
.button.is-signal:hover{opacity:.85}
.modal .mbody{flex:1 1 auto; min-height:0; overflow-y:auto; scrollbar-width:none}
.modal .mbody::-webkit-scrollbar{display:none}
.modal .mbody p{font-size:var(--fs-2); line-height:var(--lh-2)}
.modal .mbody p + p{margin-top:var(--sp-3)}
/* ACTION BAR — the answers, at the end, in reading order: the one that
   changes nothing first, the one that does the thing last. One module
   is skipped above it, exactly as under a container header. */
.abar{
  flex:none;
  display:flex; align-items:center; justify-content:flex-end; gap:var(--row-gap);
  margin-top:calc(var(--mod) + var(--row-gap));
}
.abar .spacer{flex:1}
/* the same thing standing inside the text, so it can be read without
   being opened */
.modal.is-static{
  position:static; transform:none; width:auto; max-height:none;
  opacity:1; visibility:visible; transition:none;
}
@media (prefers-reduced-motion:reduce){.modal,.scrim{transition:none}}

/* TOOLTIP — the name of a thing that shows no name.
   A pill of ink with type in the surface tone: the same inversion a
   chosen option and a chosen element use, at the smallest type level. It
   carries the keyboard shortcut after the label, and the shortcut gives
   part of its strength back — it is the second thing said, not the
   first.
   It waits before it appears: one full step of time, 320 ms, which is
   the step reserved for things that were not there before. It leaves at
   once, because by then the cursor has already moved on. */
.tip{
  height:var(--sp-6);
  display:inline-flex; align-items:center;
  padding:0 var(--sp-2);
  background:var(--ink); color:var(--panel);
  font-size:var(--fs-1); line-height:var(--lh-1);
  /* concentric with the panel it belongs to the family of: the menu frame
     is 4, so the tooltip corner is the system corner less that frame */
  border-radius:max(0px, calc(var(--r) - var(--menu-pad)));
  box-shadow:var(--shadow-2);
  white-space:nowrap;
}
.tip .k{margin-left:var(--el-gap); opacity:.55}
.has-tip{position:relative}
.has-tip > .tip{
  position:absolute; z-index:250;
  left:50%; bottom:calc(100% + var(--sp-1));
  transform:translateX(-50%);
  opacity:0; visibility:hidden;
  transition:opacity var(--dur-1) ease, visibility 0s var(--dur-1);
}
.has-tip:hover > .tip,
.has-tip:focus-within > .tip{
  opacity:1; visibility:visible;
  transition:opacity var(--dur-1) ease var(--dur-3), visibility 0s var(--dur-3);
}
@media (prefers-reduced-motion:reduce){.has-tip > .tip{transition:none}}

/* ══ 15. NAVIGATION ═══════════════════════════════════════════════════
   Where the thing being looked at stands, and how to go somewhere else. */

/* TAB — one view out of several, side by side.
   The chosen tab is marked by a rule under it, 2 px in the ink tone —
   half a gap, the only new number in this run besides the radio radius.
   The label does NOT change weight when it is chosen: a heavier label is
   a wider label, and the row would shift under the cursor. What changes
   is the tone — the ones not chosen are set in the metric tone and come
   up to full ink under the cursor. */
.tabs{
  display:flex; align-items:center;
  /* NO gap. Tabs divide by a line, the way a table divides its rows —
     put a gap between them and they turn into a series of buttons, which
     is a different thing that answers a different question. */
  box-shadow:inset 0 -1px 0 0 var(--line);
  overflow-x:auto; scrollbar-width:none;
}
.tabs::-webkit-scrollbar{display:none}
.tab{
  position:relative; flex:none;
  height:var(--row-h); padding:0 var(--el-pad);
  display:inline-flex; align-items:center; gap:var(--el-gap);
  background:none; border:0; color:var(--ink-2);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; white-space:nowrap; cursor:pointer;
  transition:color var(--dur-1) ease;
}
.tab:hover{color:var(--ink); background:var(--veil-6)}
.tab[aria-selected="true"]{color:var(--ink)}
.tab[aria-selected="true"]::after{
  content:''; position:absolute;
  left:0; right:0; bottom:calc(-1 * var(--line-w));
  height:var(--tab-rule);
  background:var(--ink);
}
.tab[disabled]{opacity:var(--off); cursor:default}
.tab[disabled]:hover{color:var(--ink-2); background:none}
.tab .count{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.tab[aria-selected="true"] .count{color:var(--ink-2)}
.tab:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
@media (prefers-reduced-motion:reduce){.tab{transition:none}}

/* PAGE — one page out of many.
   The cell is 32 by 32: the element side, squared. Nothing here is 40,
   which is what a system standing on 8 would use — the cell has to line
   up with every other element in the row it stands in.
   The current page is the full inversion, the same one a chosen option
   and a chosen element take. Under the cursor a cell steps one tone
   along the ramp, like any other clickable thing in the system tone.
   The break in the sequence is not a button: it takes no cursor and
   gives part of its strength back. */
.pager{display:flex; align-items:center; gap:var(--row-gap); flex-wrap:wrap}
.page{
  min-width:var(--row-h); height:var(--row-h); padding:0 var(--sp-2); flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:0; color:var(--ink);
  border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; cursor:pointer;
  font-variant-numeric:tabular-nums;
  transition:background var(--dur-1) ease, color var(--dur-1) ease;
}
.page:hover{background:var(--el-hover)}
.page[aria-current="true"]{background:var(--ink); color:var(--panel)}
.page[disabled]{opacity:var(--off); cursor:default}
.page[disabled]:hover{background:none}
.page.is-gap{cursor:default; opacity:.55}
.page.is-gap:hover{background:none}
.page svg{width:var(--ic-s); height:var(--ic-s); display:block}
.page.is-prev svg{transform:rotate(180deg)}
.page:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
@media (prefers-reduced-motion:reduce){.page{transition:none}}

/* BREADCRUMB — the way back, written out.
   The steps behind are in the metric tone, the one being looked at is in
   full ink and takes no cursor — it is where you already are. The
   separator is the same chevron the whole file uses, at the type glyph
   size, giving part of its strength back. */
.crumbs{display:flex; align-items:center; gap:var(--el-gap); min-height:var(--row-h); flex-wrap:wrap}
.crumb{
  background:none; border:0; padding:0;
  color:var(--ink-2);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam);
  letter-spacing:-.011em; cursor:pointer;
  text-decoration:none;
  transition:color var(--dur-1) ease;
}
.crumb:hover{
  color:var(--ink);
  text-decoration:underline;
  text-decoration-thickness:var(--line-w);
  text-underline-offset:calc(var(--sp-1) / 2);
}
.crumb[aria-current="page"]{color:var(--ink); cursor:default; text-decoration:none}
.crumbs .sep{width:var(--ic-s); height:var(--ic-s); flex:none; color:var(--ink-2); opacity:.55}
.crumb:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
@media (prefers-reduced-motion:reduce){.crumb{transition:none}}

/* LINK — a word that leads somewhere, and has no colour of its own.
   This goes against the convention of nearly every interface, and on
   purpose. In this system colour means either BELONGING (the attachable
   palette) or STATE (the system palette). A link is neither. A blue link
   would lie twice — pretending to be a category and pretending to be a
   signal.
   What is left is the hairline under the word and the button's rule for
   the cursor: the material loses a quarter of its strength. The quiet
   variant keeps its underline in the tone of the dividing rule and only
   comes to full ink when it is pointed at. */
.link{
  background:none; border:0; padding:0 0 1px;
  color:inherit; font:inherit; cursor:pointer;
  text-decoration:none;
  /* the underline is drawn, not decorated: an inset rule sits at a fixed
     distance from the baseline and does not thin out with the type size */
  box-shadow:inset 0 -1px 0 0 currentColor;
  transition:opacity var(--dur-1) ease, box-shadow var(--dur-1) ease;
}
.link:hover{opacity:.75}
.link.is-quiet{box-shadow:inset 0 -1px 0 0 var(--line)}
.link.is-quiet:hover{box-shadow:inset 0 -1px 0 0 currentColor; opacity:1}
.link:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
@media (prefers-reduced-motion:reduce){.link{transition:none}}

/* DIVIDER — the dividing rule, made into a thing.
   One value for both modes: the ink tone at 20%, transparent, so it
   mixes with whatever lies beneath it. It divides things OF THE SAME
   ORDER — rows from rows, groups from groups. Between things of
   different orders the distance does the dividing, not a line. */
.divider{height:var(--line-w); width:100%; background:var(--line); flex:none}
.divider.is-vert{width:var(--line-w); height:var(--row-h); align-self:center}
.divider.is-inset{width:auto; margin-inline:var(--el-pad)}

/* ══ 16. DESKTOP LAYOUT ═══════════════════════════════════════════════
   The application in the preview already has one layout: two columns of
   work, an action bar above them, a movable boundary between the
   containers. That is not one layout out of many — it is ONE MACHINE,
   and a layout is nothing but the list of numbers handed to it. Below,
   the same machine with seven lists instead of one, plus two states it
   did not have before: a minimised column and a ceiling on the number of
   columns.
   Two values are added here and only one of them is a number:
   --catch, the grab zone of a boundary, and --rail-w, which is computed
   from the frame and the element that were already there. */

/* the tables of rules — the same hairline the document divides with */











@media (max-width:900px){.tbl{grid-template-columns:1fr !important} .tbl .th{display:none}}

/* the working field of the demonstration: the same construction as the
   application — first row the bar, second row the containers */
.lay-stage{display:block}
.lay-field{
  position:relative; display:grid;
  height:calc(12 * var(--mod) - var(--row-gap));
  grid-template-rows:var(--row-h) minmax(0, 1fr);
  gap:var(--gut);
}
/* The track underlay lies under the WORKING FIELD, not under the whole
   frame — a rail has left the division into tracks, so the underlay does
   not cover it. */
.lay-tracks{
  position:absolute; top:calc(var(--row-h) + var(--gut)); bottom:0;
  display:grid; grid-template-columns:repeat(24, 1fr); gap:var(--gut);
  pointer-events:none; z-index:0;
}
.lay-tracks i{background:var(--field); opacity:.24}
.lay-stage:not([data-grid="on"]) .lay-tracks{display:none}

/* ACTION BAR — it stands OUTSIDE the container, straight on the canvas,
   so its buttons take the material in the container tone: the one place
   where that material means anything, because it makes itself a surface
   that is not there underneath it. */
.lay-bar{
  grid-column:1 / -1; grid-row:1; height:var(--row-h);
  display:flex; align-items:center; gap:var(--el-gap); z-index:2;
}
.lay-bar .right{margin-left:auto; display:flex; align-items:center; gap:var(--el-gap)}

.lay-box{
  grid-row:2; min-height:0; position:relative;
  display:flex; flex-direction:column;
  padding:var(--pad);
  background:var(--panel); border-radius:var(--r);
  container-type:inline-size; container-name:box;
  transition:background var(--dur-1) var(--ease);
}
.lay-box .head{
  height:var(--row-h); flex:none;
  display:flex; align-items:center; gap:var(--el-gap);
  margin-bottom:calc(var(--mod) + var(--row-gap));
}
.lay-box .head .name{
  font-size:var(--fs-3); line-height:var(--lh-3); font-weight:var(--fw-med);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.lay-box .head .tools{margin-left:auto; display:flex; align-items:center; gap:var(--el-gap); min-height:0}
/* A button standing ON a container takes the canvas tone. The container
   is a panel, so the button has to be told apart from it, and the only
   tone on the other side of the panel is the canvas. In the action bar it
   is the other way round: the bar lies on the canvas, so its buttons take
   the panel tone. One rule, two results — a button takes the tone
   OPPOSITE to what it stands on. */
.lay-box .button{background:var(--canvas)}
.lay-box .button[disabled]{opacity:var(--off); cursor:default; pointer-events:none}
.lay-box .rows{
  flex:1; min-height:0; overflow:auto; scrollbar-width:none;
  display:flex; flex-direction:column; gap:var(--row-gap);
}
.lay-box .rows::-webkit-scrollbar{display:none}

/* MINIMISED COLUMN — not a narrow container. A container that has given
   up its content. The frame of 20 and the height of the header do not
   change: the button stands exactly where it stood before, at the right
   edge of the frame on the first module. The panel changes, the button
   does not — it is the same button, turned round. */
.lay-box[data-min="true"]{cursor:pointer}
.lay-box[data-min="true"] .head .name{display:none}
.lay-box[data-min="true"] .rows{display:none}
.lay-box[data-min="true"] .tools > :not(.mini){display:none}
.lay-box[data-min="true"]:hover{background:var(--el-hover)}

/* MOVABLE BOUNDARY — the handle sits in the ordinary gutter and takes no
   track. Only the rule is visible, and only under the cursor: at rest
   there is nothing to show. The catch zone is eight times wider than what
   is drawn — the one place in the system where the area that reacts is
   bigger than the drawing, which is why it is written down as an
   exception rather than as a pattern. */
.lay-grip{
  position:absolute; z-index:10; width:var(--catch);
  top:calc(var(--row-h) + var(--gut)); bottom:0;
  display:grid; place-items:center; cursor:col-resize; touch-action:none;
}
.lay-grip::after{
  content:''; width:var(--grip-w); height:var(--row-h);
  background:var(--ink); border-radius:min(var(--r), 1px);
  opacity:0; transition:opacity var(--dur-1) var(--ease);
}
.lay-grip:hover::after, .lay-grip.is-live::after{opacity:1}
.lay-grip:focus-visible::after{opacity:1}
.lay-grip.is-locked{cursor:default}
.lay-grip.is-locked:hover::after{opacity:var(--off)}
body.is-sizing{cursor:col-resize}
body.is-sizing .lay-box{transition:none}

/* the reading of the current layout, in the same figures the rules use */



.switch .sw-opt[disabled]{opacity:var(--off); cursor:default; pointer-events:none}


/* ══ 17. AUTHENTICATION ═══════════════════════════════════════════════
   A sign-in screen is not a separate country with rules of its own: it is
   a container standing on the canvas. What it does add is two tones and
   one thicker line, and all three exist for a reason that can be measured
   rather than argued.

   THE TWO TONES. s-1 and s-4 are SURFACE tones — they are meant to glow
   under a light glyph, and they do. As type and as a border they miss the
   threshold: red on a panel gives 4.39 against the 4.5 required, and
   green as a border gives 1.91 against the 3 required; in dark mode the
   red falls to 2.63. That is not a fault in the tones. The system has
   solved this once already in the other direction — a palette carries its
   own ink tones, because one colour cannot be both the ground and the
   letter. Here the same thing is needed the other way round: the same
   hue, a different lightness, its own role. */
body{
  --sig-1-t:#A82927;   /* 5.85:1 on the panel · 4.57:1 on the canvas */
  --sig-4-t:#2A6E3F;   /* 5.18:1 on the panel */
}
body.dark{
  --sig-1-t:#E59B99;    /* 6.20:1 on the panel · 8.32:1 on the canvas */
  --sig-4-t:var(--s-4); /* 6.05:1 on the panel — dark mode needs no change */
}

/* THE FIELD — three parts stacked: the label (always), the box with the
   content (element height 32) and the message (a hint or an error, in the
   same place). The gap is 4: the label and the message belong to the
   field, so they hold closer to it than fields hold to each other. */
.f{display:flex; flex-direction:column; gap:var(--sp-1); max-width:420px}
.f-label{
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
  display:flex; align-items:baseline; gap:var(--sp-2);
}
.f-label .req{color:var(--sig-1-t)}
/* The border is 1.5, not the 0.5 hairline. 1.4.11 asks for 3:1 on the
   boundary of a component, and a line thinner than a pixel at 100% zoom
   can disappear on a poor screen. It is the one place where the system
   draws a heavier line than usual, and it does it for a measurable
   reason. */
.f-box{
  position:relative; display:flex; align-items:center; gap:var(--el-gap);
  height:var(--row-h); padding-inline:var(--el-pad);
  background:var(--panel); border-radius:var(--r);
  box-shadow:inset 0 0 0 1.5px var(--ink-2);
  transition:box-shadow var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.f-box input, .f-box select{
  appearance:none; border:0; background:none; outline:none; padding:0;
  flex:1; min-width:0; color:var(--ink);
  font:var(--fw-reg) var(--fs-2)/1 var(--fam); letter-spacing:-.011em;
  user-select:text; -webkit-user-select:text;
}
.f-box input::placeholder{color:var(--ink-2); opacity:1}
.f-box:hover{box-shadow:inset 0 0 0 1.5px var(--ink)}
/* FOCUS: a ring OUTSIDE the field, in the ink tone, with a gap in the
   tone of the ground. Two tones next to each other mean the ring is
   visible on a light ground and on a dark one — 1.4.11 with 2.4.13. Both
   measures come from the element gap of 4, split in half. */
.f-box:focus-within, .f-box[data-state="focus"]{
  box-shadow:inset 0 0 0 1.5px var(--ink),
             0 0 0 2px var(--canvas),
             0 0 0 4px var(--ink);
}
.f-box[data-state="filled"]{box-shadow:inset 0 0 0 1.5px var(--ink)}
.f-box[data-state="disabled"]{opacity:var(--off); pointer-events:none; box-shadow:inset 0 0 0 1.5px var(--ink-2)}
.f-box[data-state="readonly"]{background:var(--canvas); box-shadow:inset 0 0 0 1.5px var(--line)}
.f-box[data-state="error"]{box-shadow:inset 0 0 0 1.5px var(--sig-1-t)}
.f-box[data-state="error"]:focus-within{
  box-shadow:inset 0 0 0 1.5px var(--sig-1-t),
             0 0 0 2px var(--canvas),
             0 0 0 4px var(--ink);
}
.f-box[data-state="success"]{box-shadow:inset 0 0 0 1.5px var(--sig-4-t)}
.f-box[data-state="loading"] input{opacity:.6}
.f-box .lead, .f-box .trail{
  width:var(--ic-m); height:var(--ic-m); flex:none; color:var(--ink-2);
  display:grid; place-items:center;
}
.f-box svg{width:var(--ic-m); height:var(--ic-m)}
/* A button inside a field has a 24 × 24 target around a glyph of 16 —
   2.5.8 measures the area that can be hit, not the drawing. */
.f-btn{
  appearance:none; border:0; background:none; cursor:pointer; color:var(--ink);
  width:var(--sp-6); height:var(--sp-6); flex:none;
  margin-right:calc(-1 * var(--sp-1)); padding:0;
  display:grid; place-items:center;
  border-radius:max(0px, calc(var(--r) - var(--sp-1)));
}
.f-btn:hover{background:var(--veil-6)}
.f-btn:focus-visible{outline:2px solid var(--ink); outline-offset:2px}

/* The message under the field. A glyph and words, never colour alone — 1.4.1. */
.f-msg{
  display:flex; align-items:flex-start; gap:var(--sp-1);
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
}
.f-msg svg{width:var(--ic-s); height:var(--ic-s); flex:none; margin-top:2px}
.f-msg.err{color:var(--sig-1-t)}
.f-msg.ok{color:var(--ink)}
.f-msg.ok svg{color:var(--sig-4-t)}

/* The one-time code — ONE field, not six boxes. The reason is in the text. */
.f-box.otp{height:var(--mod)}
.f-box.otp input{
  font-size:var(--fs-3); line-height:var(--lh-3);
  letter-spacing:.4em; font-variant-numeric:tabular-nums;
}

/* A choice: the whole row is the target, so the target is 32 even though
   the square is 20. The square takes the pill height and the mark takes
   the glyph size — both numbers scale together, so the proportion holds
   at 1.5× as well. */
.chk{
  display:flex; align-items:flex-start; gap:var(--sp-2); cursor:pointer;
  min-height:var(--row-h); padding:var(--sp-1) 0; max-width:420px;
}
.chk input{
  appearance:none; margin:0; flex:none; cursor:pointer;
  width:var(--pill-h); height:var(--pill-h);
  border-radius:max(0px, calc(var(--r) - var(--sp-1)));
  box-shadow:inset 0 0 0 1.5px var(--ink-2);
  background:var(--panel); position:relative;
}
.chk input:hover{box-shadow:inset 0 0 0 1.5px var(--ink)}
.chk input:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
.chk input:checked{background:var(--ink); box-shadow:none}
.chk input:checked::after{
  content:''; position:absolute; inset:0; background:var(--panel);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath transform='translate(2.567 3.983)' d='M%203.8%208.017%20L%200%204.217%20L%200.95%203.267%20L%203.8%206.117%20L%209.917%200%20L%2010.867%200.95%20L%203.8%208.017%20Z'/%3E%3C/svg%3E") center / var(--ic-m) var(--ic-m) no-repeat;
          mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath transform='translate(2.567 3.983)' d='M%203.8%208.017%20L%200%204.217%20L%200.95%203.267%20L%203.8%206.117%20L%209.917%200%20L%2010.867%200.95%20L%203.8%208.017%20Z'/%3E%3C/svg%3E") center / var(--ic-m) var(--ic-m) no-repeat;
}
.chk .txt{font-size:var(--fs-2); line-height:var(--lh-2)}
.chk .sub{display:block; font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}

/* The strength meter — ADVICE, never a gate. Four bars of 4. */
.pw{display:flex; flex-direction:column; gap:var(--sp-1); padding-top:var(--sp-1)}
.pw .bars{display:flex; gap:2px; height:var(--sp-1)}
.pw .bars i{flex:1; background:var(--field); border-radius:min(var(--r), 1px)}
.pw[data-level="1"] .bars i:nth-child(-n+1){background:var(--sig-1-t)}
.pw[data-level="2"] .bars i:nth-child(-n+2){background:var(--s-2)}
.pw[data-level="3"] .bars i:nth-child(-n+3){background:var(--sig-4-t)}
.pw[data-level="4"] .bars i{background:var(--sig-4-t)}
.pw .say{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}

/* THE VIEW — a container on the canvas, 400 wide, frame 20, the system
   corner. Nothing here is a new kind of surface. */

.auth-view{
  width:400px; flex:none; max-width:100%;
  background:var(--panel); border-radius:var(--r); padding:var(--pad);
  display:flex; flex-direction:column; gap:var(--sp-5);
}
.auth-view .mark{
  height:var(--row-h); display:flex; align-items:center;
  font-size:var(--fs-3); line-height:var(--lh-3); font-weight:var(--fw-med);
}
.auth-view h4{margin:0; font-size:var(--fs-3); line-height:var(--lh-3); font-weight:var(--fw-med)}
.auth-view .say{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.auth-view form{display:flex; flex-direction:column; gap:var(--sp-4)}
.auth-view .f, .auth-view .chk{max-width:none}
.auth-view .foot{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-3); font-size:var(--fs-1); line-height:var(--lh-1);
}
.button.is-wide{width:100%}
.button.is-primary{background:var(--ink); color:var(--panel)}
.button.is-primary:hover{opacity:.85}

/* The summary of errors at the top of the form — 3.3.1. */
.summary{
  display:flex; flex-direction:column; gap:var(--sp-1);
  padding:var(--sp-3); border-radius:var(--r);
  background:var(--panel-2); color:var(--ink);
  box-shadow:inset 0 0 0 1.5px var(--sig-1-t);
  font-size:var(--fs-1); line-height:var(--lh-1);
}
.summary b{font-weight:var(--fw-med); color:var(--sig-1-t)}

/* the grid of states, and of field kinds */




/* ══ 18. DATE ═════════════════════════════════════════════════════════
   A calendar adds NOTHING to the foundation. A day is the element, a
   column is the module, the grid is 36n − 4, the frame is the menu
   panel's frame, the band is the state material at two of its three
   settings, the mark on today is the mark on a chosen tab, and the
   shortcuts are ordinary menu options. That is the test: the densest
   thing in the library fitted into numbers that were already there. */

/* THE FIELD — the select's geometry with a different panel behind it */
.dfield{
  height:var(--row-h); width:100%;
  display:flex; align-items:center; gap:var(--sp-2);
  padding-left:calc((var(--row-h) - var(--ic-m)) / 2);   /* the glyph takes the graphic inset: 8 */
  padding-right:var(--btn-pad);                          /* the chevron takes the type inset: 12 */
  background:none; color:var(--ink); border:0;
  outline:var(--line-w) solid var(--ink); outline-offset:-1px;
  border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam); letter-spacing:-.011em;
  text-align:left; cursor:pointer;
  transition:outline-color var(--dur-1) ease, background var(--dur-1) ease;
}
.dfield.is-sunken{background:var(--canvas); outline-color:transparent}
.dfield:hover{background:var(--veil-6)}
.dfield[aria-expanded="true"], .dfield:focus-visible{outline:1.5px solid var(--ink); outline-offset:-1.5px}
.dfield.is-bad{outline-color:var(--sig-1-t); outline-width:1.5px}
.dfield[disabled]{opacity:var(--off); pointer-events:none}
.dfield .gl{width:var(--ic-m); height:var(--ic-m); flex:none; color:var(--ink-2)}
.dfield .val{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.dfield.is-empty .val{color:var(--ink-2)}
.dfield .chev{width:var(--ic-s); height:var(--ic-s); flex:none; transform:rotate(90deg)}
.dp-f{display:flex; flex-direction:column; gap:var(--sp-1); max-width:320px}
.dp-f.is-wide{max-width:420px}
.dp-lab{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.dp-msg{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.dp-msg.err{color:var(--sig-1-t)}

/* THE PANEL is a MENU PANEL, not a container: it lies above the work
   rather than in the working field, so it takes the menu frame of 4, the
   hairline outline in the ink tone, the system corner and the shadow.
   A container would have the frame of 20 and tracks; a calendar has
   neither. Its width comes from its content: 4 + 248 + 4 = 256. */
.cal{
  display:inline-flex; flex-direction:column;
  padding:var(--menu-pad);
  background:var(--panel); color:var(--ink);
  border:var(--line-w) solid var(--ink);
  border-radius:var(--r);
  box-shadow:var(--shadow-2);
  width:max-content;
}
/* Two months are separated by a COLUMN THAT IS NOT THERE: 36, the module —
   a day plus its gap. No new number, no line; the distance is enough,
   because it divides things of the same order. */
.cal-body{display:flex; align-items:flex-start; gap:var(--mod)}
.cal-month{display:flex; flex-direction:column; gap:var(--row-gap)}
.cal-head{
  height:var(--row-h); flex:none;
  display:flex; align-items:center; justify-content:space-between; gap:var(--el-gap);
}
.cal-head .nm{
  flex:1; height:var(--row-h);
  display:inline-flex; align-items:center; justify-content:center; gap:var(--el-gap);
  background:none; border:0; color:inherit; cursor:pointer;
  border-radius:max(0px, calc(var(--r) - var(--menu-pad)));
  font:var(--fw-med) var(--fs-2)/1 var(--fam); letter-spacing:-.011em;
}
.cal-head .nm:hover{background:var(--veil-6)}
.cal-head .nm:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
.cal-head .nm .y{color:var(--ink-2); font-weight:var(--fw-reg)}
.cal-head .nav{
  width:var(--row-h); height:var(--row-h); flex:none;
  display:inline-grid; place-items:center;
  background:none; border:0; color:var(--ink); cursor:pointer;
  border-radius:max(0px, calc(var(--r) - var(--menu-pad)));
}
.cal-head .nav:hover{background:var(--veil-6)}
.cal-head .nav:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
.cal-head .nav svg{width:var(--ic-s); height:var(--ic-s)}
.cal-head .nav.is-prev svg{transform:rotate(180deg)}
.cal-head .nav.is-ghost{visibility:hidden}
/* the row of weekday letters is a GROUP HEADING: element height, the
   smallest type level, the metric tone — the same thing that stands over
   a group of rows */
.cal-days, .cal-grid{
  display:grid;
  grid-template-columns:repeat(7, var(--row-h));
  gap:var(--row-gap);
}
.cal-days > span{
  height:var(--row-h); display:grid; place-items:center;
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
}
/* The grid is ALWAYS six weeks tall. A panel that changes height while
   the months are being paged runs away from under the hand — and under
   the hand there is an arrow that has to be hit a second time. */
.cal-grid{grid-template-rows:repeat(6, var(--row-h))}

/* THE CELL carries the band, the DAY is the element. A day is 32 × 32
   with the system corner — the pagination cell, put in a grid instead of
   a row. Selection is the full inversion, the same one a chosen row, a
   chosen menu option and the current page take. */
.cell{position:relative; display:grid; place-items:center}
.day{
  position:relative; z-index:1;
  width:var(--row-h); height:var(--row-h);
  display:inline-grid; place-items:center;
  background:none; border:0; color:var(--ink); cursor:pointer;
  border-radius:var(--r);
  font:var(--fw-reg) var(--fs-2)/1 var(--fam); letter-spacing:-.011em;
  font-variant-numeric:tabular-nums;
  transition:background var(--dur-1) ease, color var(--dur-1) ease;
}
.day:hover{background:var(--el-hover)}
.day:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
.day[disabled]{opacity:var(--off); pointer-events:none}
.day.is-sel{background:var(--ink); color:var(--panel)}
.day.is-sel:hover{background:var(--ink)}
/* TODAY — a 2 px rule under the number, the same one that marks a chosen
   tab. A tab says "this is the one you are on", and so does today. It is
   NOT a selection, so it does not take the inversion. */
.day.is-today::after{
  content:''; position:absolute;
  left:var(--el-pad); right:var(--el-pad); bottom:var(--sp-1);
  height:var(--tab-rule); background:currentColor;
}
/* THE BAND. A range is a STATE of the days it covers, not a selection of
   each of them — so it takes the state material: ink at 12%. The preview,
   while the second end is not yet placed, takes the same material at 6%.
   Three intensities, the same as everywhere: 6% provisional, 12% settled,
   full inversion at the ends.
   The band closes the gap — half of 4 on each side — so a run of days
   reads as one strip while every day keeps its own module. */
.cell::before{content:''; position:absolute; top:0; bottom:0; z-index:0; background:transparent}
.cell[data-band]::before{background:var(--veil-12)}
.cell[data-preview]::before{background:var(--veil-6)}
.cell[data-band="mid"]::before, .cell[data-preview="mid"]::before{
  left:calc(-1 * var(--row-gap) / 2); right:calc(-1 * var(--row-gap) / 2);
}
.cell[data-band="start"]::before, .cell[data-preview="start"]::before{left:50%; right:calc(-1 * var(--row-gap) / 2)}
.cell[data-band="end"]::before, .cell[data-preview="end"]::before{left:calc(-1 * var(--row-gap) / 2); right:50%}
.cell[data-band="solo"]::before, .cell[data-preview="solo"]::before{left:50%; right:50%}
/* The end of a run takes the system corner. Inside a run the bands of
   neighbours overlap in the gap, so ONLY the edge of the run in that row
   is rounded — otherwise every cell boundary would show a pinch. */
.cell[data-cap~="l"]::before{
  border-top-left-radius:min(var(--r), calc(var(--row-h) / 2));
  border-bottom-left-radius:min(var(--r), calc(var(--row-h) / 2));
}
.cell[data-cap~="r"]::before{
  border-top-right-radius:min(var(--r), calc(var(--row-h) / 2));
  border-bottom-right-radius:min(var(--r), calc(var(--row-h) / 2));
}
.day.is-mid{background:none}
.day.is-mid:hover{background:var(--el-hover)}

/* the separator is the menu panel's rule: it runs across the whole panel,
   so it reaches past its frame */
.cal-sep{height:var(--sp-2); display:flex; align-items:center; margin-inline:calc(-1 * var(--menu-pad))}
.cal-sep::before{content:''; display:block; width:100%; height:var(--line-w); background:var(--line)}
.cal-foot{
  height:var(--row-h);
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3);
  padding-left:var(--sp-2);
}
.cal-foot .read{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.cal-foot .read b{color:var(--ink); font-weight:var(--fw-med)}
.cal-foot .acts{display:flex; align-items:center; gap:var(--row-gap)}
.cal-foot .button{height:calc(var(--row-h) - 2 * var(--menu-pad)); padding-inline:calc(var(--btn-pad) - var(--menu-pad))}
/* the shortcuts are menu options standing in a column, so the element gap
   divides them — 4, not zero */
.cal-side{
  display:flex; flex-direction:column; gap:var(--row-gap);
  padding-right:var(--menu-pad);
  border-right:var(--line-w) solid var(--line);
  margin-right:calc(var(--mod) - var(--menu-pad) - var(--sp-2));
}
.cal-side .opt{
  height:var(--row-h); display:flex; align-items:center;
  padding:0 calc(var(--btn-pad) - var(--menu-pad));
  background:none; border:0; width:100%; color:inherit; cursor:pointer;
  border-radius:max(0px, calc(var(--r) - var(--menu-pad)));
  font:var(--fw-reg) var(--fs-2)/1 var(--fam); letter-spacing:-.011em;
  text-align:left; white-space:nowrap;
}
/* hover is the state material, like every other button in this panel;
   the full inversion is left to the selection */
.cal-side .opt:hover{background:var(--veil-6)}
.cal-side .opt[aria-selected="true"],
.cal-side .opt[aria-selected="true"]:hover{background:var(--ink); color:var(--panel)}
.cal-side .opt:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
/* the month is chosen IN PLACE OF the grid, not in a second panel: a panel
   above a panel is two things raised over one another, and this system
   raises a thing once */
.cal-pick{display:none; grid-template-columns:repeat(3, 1fr); gap:var(--row-gap); padding-top:var(--row-gap)}
.cal-month[data-pick="true"] .cal-pick{display:grid}
.cal-month[data-pick="true"] .cal-days,
.cal-month[data-pick="true"] .cal-grid{display:none}
.cal-pick button{
  height:var(--row-h);
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:0; color:var(--ink); cursor:pointer;
  border-radius:max(0px, calc(var(--r) - var(--menu-pad)));
  font:var(--fw-reg) var(--fs-2)/1 var(--fam); letter-spacing:-.011em;
}
.cal-pick button:hover{background:var(--el-hover)}
.cal-pick button[aria-selected="true"]{background:var(--ink); color:var(--panel)}
/* the panel hangs under the field, 4 away from it, like any menu panel */
.pop{position:relative}
.pop .cal{position:absolute; top:calc(100% + var(--sp-1)); left:0; z-index:80; display:none}
.pop[data-open="true"] .cal{display:inline-flex}
/* In the document the two-month panel stands inline, so at one and a half
   times the size it is wider than the container it is being shown in. In
   an application it is an overlay and nothing clips it; here the body of
   the demonstration scrolls sideways instead. */
#date .demo .fbody{overflow-x:auto; scrollbar-width:none}
#date .demo .fbody::-webkit-scrollbar{display:none}

/* the state grid in this document */




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

/* ══ 19. FILE UPLOAD ══════════════════════════════════════════════════
   Two things: the ZONE, which takes files by drag or by click, and the
   FILE ROW, which says what is happening to one of them. Taken from a
   system that draws the zone with a DASHED border — the one thing not
   carried over. This system has exactly one line, the hairline in the ink
   tone at 20%, and a second kind of line would be claimed by every empty
   state in the library. What a dashed border tries to say, the system
   already says with a SUNKEN surface: the canvas tone cut into the panel,
   which is what a text field says too. */
.drop{
  /* three modules: 3 × 36 − 4 = 104, the tile's formula */
  height:calc(3 * var(--mod) - var(--row-gap));
  width:100%;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:var(--sp-2);
  padding:var(--sp-3) var(--el-pad);
  background:var(--canvas); color:var(--ink);
  border:0; border-radius:var(--r);
  outline:var(--line-w) solid var(--line); outline-offset:-1px;
  font:var(--fw-reg) var(--fs-2)/var(--lh-2) var(--fam); letter-spacing:-.011em;
  cursor:pointer; text-align:center;
  transition:background var(--dur-1) ease, outline-color var(--dur-1) ease;
}
.drop .gl{width:var(--ic-l); height:var(--ic-l); color:var(--ink-2)}
.drop .say{display:block}
/* The underlined words say WHAT THE CLICK DOES. They are not a separate
   control: the whole zone is one target, and a button inside a button is
   two targets in one place plus the question of which one was hit. */
.drop .say u{text-decoration:none; box-shadow:inset 0 -1px 0 0 currentColor; padding-bottom:1px}
.drop .lim{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.drop:hover{background:var(--veil-6)}
.drop:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
/* SOMETHING IS BEING HELD OVER IT — the heavier setting of the same
   material. 6% says "pointed at", 12% says "you are holding this over
   me", and the outline comes to full ink at 1.5, the thickness the form
   field took for reasons of contrast. */
.drop.is-over{background:var(--veil-12); outline:1.5px solid var(--ink); outline-offset:-1.5px}
.drop.is-over .gl{color:var(--ink)}
.drop[disabled]{opacity:var(--off); pointer-events:none}
.drop.is-bad{outline:1.5px solid var(--sig-1-t); outline-offset:-1.5px}

/* THE FILE ROW — one module while it carries one line, two (68) when a
   word about progress or about an error stands under the name. The same
   arithmetic as the alert and the tile.
   IT HAS NO SURFACE OF ITS OWN. The list stands in a panel and the panel
   is already a surface; a second surface under a thing that is NOT
   clicked is one material too many — an element in the sunken tone says
   "I am clickable", and a file row is not: its tools are. */
.frow{
  min-height:var(--row-h);
  background:none; border-radius:var(--r);
  --st-bg:var(--panel);        /* the cut-out in a glyph takes what the row lies on */
  display:flex; align-items:center; gap:var(--el-gap);
  padding:0 var(--el-pad);
  font-size:var(--fs-2); line-height:var(--lh-2);
}
.frow.is-two{
  height:calc(2 * var(--mod) - var(--row-gap));
  align-items:flex-start; padding-top:var(--sp-2);
}
.frow > .st{
  width:var(--st-size); height:var(--st-size); flex:none;
  margin-left:calc((var(--row-h) - var(--st-size)) / 2 - var(--el-pad));
}
.frow.is-two > .st{margin-top:calc((var(--lh-2) - var(--st-size)) / 2)}
.frow .txt{display:flex; flex-direction:column; min-width:0; flex:1}
.frow .nm{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.frow .sub{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2)}
.frow .sub.err{color:var(--sig-1-t)}
.frow .m{font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2); flex:none; font-variant-numeric:tabular-nums}
.frow.is-two .m{margin-top:calc((var(--lh-2) - var(--lh-1)) / 2)}
/* a tool has the side of the row, so its graphic inset is zero — it
   stands at the very edge, and the frame of 12 has to be given back */
.frow .tools{display:flex; align-items:center; gap:var(--row-gap); flex:none; margin-right:calc(-1 * var(--el-pad))}
.frow.is-two .tools{margin-top:calc(-1 * var(--sp-2))}
.frow .tool{
  width:var(--row-h); height:var(--row-h); flex:none;
  display:inline-grid; place-items:center;
  background:none; border:0; color:var(--ink); cursor:pointer; border-radius:var(--r);
}
.frow .tool:hover{background:var(--veil-6)}
.frow .tool:focus-visible{outline:2px solid var(--ink); outline-offset:-2px}
.frow .tool svg{width:var(--ic-m); height:var(--ic-m)}
.frow[data-state="queued"] .st{color:var(--ink-2)}
.frow[data-state="uploading"] .st{color:var(--sig-5)}
/* a file that already lies there is not a signal — the glyph goes in the
   metric tone and the name carries the weight of the sentence */
.frow[data-state="done"] .st{color:var(--ink-2)}
.frow[data-state="error"] .st{color:var(--sig-1)}

/* the list heading is a group heading: 32, the smallest type, metric tone */
.fhead-l{
  height:var(--row-h);
  display:flex; align-items:center; justify-content:space-between;
  padding:0 var(--el-pad);
  font-size:var(--fs-1); line-height:var(--lh-1); color:var(--ink-2);
}
.flist{display:grid; gap:var(--row-gap); grid-template-columns:minmax(0,1fr)}
.uploader{display:grid; gap:var(--row-gap); grid-template-columns:minmax(0,1fr); width:100%}
.fu-panel{background:var(--panel); border-radius:var(--r); padding:var(--pad); width:100%; max-width:520px}
.fu-rowset{display:grid; gap:var(--row-gap); width:100%; max-width:520px}
