/*
  The Anti design system. v0.1
  Paper and ink, mono type as texture, hairline geometry, one accent.
  Instrument language, not poster language: every control reads like a
  marking on a machine. Motion settles, sound speaks through the engine.
  Prefix: at- (classes), --at- (tokens). Zero build, file:// friendly.
*/

/* ---------- fonts ---------- */

@font-face {
  font-family: 'PP Fraktion Mono';
  src: url('../brand/PPFraktionMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'PP Fraktion Sans';
  src: url('../brand/PPFraktionSans-Bold.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}

/* ---------- tokens ---------- */

:root {
  /* color: the split rule (2026-08-18, settled in the color-tune rounds after
     record red replaced voltage blue). The GROUND keeps the cool air: paper,
     and the panel, which is the paper's own spread one step down. The CONTENT
     plane is neutral: the ink ramp and its on-ink mirror, achromatic to at
     most one point warm, never blue. The ACCENT is record red. The old
     all-cool set was tuned for a blue accent and collided with the red
     everywhere content met it; a dead-neutral panel read warm-dirty inside
     the cool page by simultaneous contrast. So nothing here sits between the
     two temperatures: a token is the paper's material or it is neutral.
     Enforced by the token suite in the private workshop, so the rule outlives the session that
     made it. */
  --at-paper: #EDEFF2;
  --at-surface: #FFFFFF;
  /* The panel: the one container fill this system allows, for the tray a set
     of controls sits in (the studio's edit column). It steps AWAY from the
     control fill, and the direction is the whole point. Painting a container
     with --at-surface is what made panels swallow their own controls, because
     every field, menu and chip in this system fills white and assumes paper
     behind it. Stepping the other way, controls lift off the tray slightly
     harder than they lift off paper, so the region reads as a recess and the
     instrument standing on it reads as before.
     The control fill is the lighter thing in both themes, so away from it is
     darker in both: this is one step under paper, and night is too (see
     below), rather than inverting with the field. Never use it on a control.
     The value is the paper's own temperature at its exact spread, 18 a
     channel down: the panel is the same material as the page, recessed, never
     a third gray of its own. It was 10 down (0.03 OKLab, #E3E5E8) until
     2026-10-01, a step of 1.10 against the paper, which a phone in daylight
     shows as one grey (Adi, from the studio's edit sheet). 18 is 1.18, and it
     is as far as the step goes while ink-2 keeps 6.5:1 and the accent as type
     keeps 5:1 on it. The token test holds the step and both floors. */
  --at-panel: #DBDDE0;
  /* The quietest ink, on the panel. The panel stands a step closer to the ink
     than the paper does, so a caption on it loses that step: --at-ink-3 would
     be 3.7:1 there. This holds a caption at 4:1 on the panel in both themes
     (see "on the panel" below). */
  --at-panel-ink-3: #686767;
  --at-ink: #0A0A0A;
  --at-ink-2: #494949;
  --at-ink-3: #706F6F;
  --at-hairline: rgba(10, 10, 10, 0.9);
  --at-hairline-2: rgba(10, 10, 10, 0.22);
  /* The accent, and it is two values because it does two jobs.
     --at-accent is the lamp. It is the GROUND that --at-on-accent stands on:
     chips, the active menu row, the current room button, selection, the inked
     dots under a hand. That job's contrast is against its own type, which does
     not change with the field, so it is ONE value in both themes. A brand
     colour that takes a different value at night is not one colour.
     --at-accent-ink is the accent when it has to be READ: a live value, a
     field being edited, a number in a diagram. That job's contrast is against
     the field, so it flips with the field exactly the way the ink ramp does.
     The split is forced rather than stylistic. Red at the brand's saturation
     cannot do both jobs: white on #DC1C2E is 4.94:1 and passes, and the same
     red as 11px type on paper is 4.29:1 and does not. Darkening the accent
     until the type passed would have handed the brand colour to four small
     labels, so the roles separated instead. Never set --at-accent on type, and
     never fill with --at-accent-ink. */
  --at-accent: #DC1C2E;
  --at-on-accent: #FFFFFF;
  --at-accent-ink: #B01623;

  /* The ink ramp read the other way: what type does when it stands ON an ink
     field rather than on paper. Principle 1 says everything on a fill inverts
     with it, and the hover states honour that because they only ever set one
     colour; the ink CONTAINERS did not, because every label, leader and value
     in this system names --at-ink outright, so a label on at-card--ink stayed
     ink on ink and disappeared. These are the three steps flipped, plus the
     hairline, applied by the ink containers below.
     They are theme-aware, and they have to be: an ink block is dark by day and
     light by night, so what stands on it goes the other way each time. Day's
     on-ink ramp is night's ink ramp, and the reverse.
     Written as literals, not as `var(--at-paper)`: the theme override lives on
     `body` while these live on `:root`, so a var() here would resolve against
     the root's day paper and stay light after the field went light. A token
     that has to flip with the theme is declared in both theme blocks, the same
     way --at-paper itself is. */
  --at-on-ink: #F0F0F0;
  --at-on-ink-2: #B7B7B7;
  --at-on-ink-3: #686767;
  --at-on-hairline-2: rgba(240, 240, 240, 0.24);

  /* type */
  --at-mono: 'PP Fraktion Mono', ui-monospace, monospace;
  --at-sans: 'PP Fraktion Sans', system-ui, sans-serif;
  --at-fs-micro: 9px;
  --at-fs-caption: 10px;
  --at-fs-label: 11px;
  --at-fs-body: 13px;
  --at-fs-lead: 15px;
  --at-fs-display: 24px;
  --at-fs-hero: 40px;
  --at-track: 0.08em;

  /* space, 4px base */
  --at-s1: 4px;
  --at-s2: 8px;
  --at-s3: 12px;
  --at-s4: 16px;
  --at-s5: 24px;
  --at-s6: 32px;
  --at-s7: 48px;
  --at-s8: 64px;
  /* macro space: air is a material here, not leftover */
  --at-s9: 96px;
  --at-s10: 160px;

  /* The gutter: the one distance from the edge of the viewport to anything.
     Every surface in this project takes it (page or app, marketing or
     instrument), on every edge including the top, so the brand keeps one
     margin wherever it is met.
     Two values and one step, deliberately: 24 from the medium frame up, 16
     below it. It does not grow with the display. A gutter that widens on a
     big screen buys air the layouts already have (they cap their own measure
     and centre it), at the cost of the edge reading differently on a laptop
     than on a studio display, which is the drift this token exists to end. */
  --at-gutter: var(--at-s5);

  /* shape: three, and each one means something.
       square  a mechanism, which is nearly everything here
       pill    language, a label you read rather than press
       circle  a point, set with 50% where a thing genuinely is a dot
     There were eight. --at-r-s was 6, --at-r-m was 12, and beside them sat
     hardcoded 2s and 3s and a 10, so a card, a screen, a stage, a key cap and
     a slot all rounded by different amounts and none of them said why. That is
     not a scale, it is drift: a scale has steps that mean different things, and
     these all meant "slightly soft". Principle 4 already had the answer, pills
     for language and squares for machines, so the corner token is 0 and the
     only other shape is the pill. --at-r keeps its name rather than going away
     so a surface can still ask for "the corner" and get the system's answer. */
  --at-r: 0;
  --at-r-pill: 999px;
  --at-line: 1px;

  /* control sizing: one row height for every interactive element, a
     related shorter height for passive pills. Horizontal padding is a
     token so buttons, fields, and menus share a rhythm. */
  --at-control-h: 24px;
  --at-chip-h: 22px;
  --at-pill-h: 28px;
  --at-pad-x: 12px;
  --at-pad-x-tight: 10px;
  /* The label lane of a field row. A token rather than a literal, because a
     narrow region legitimately wants it tighter and every app was writing the
     same override into its own sheet to get there (the two machines both had
     76 hand-set). The tray sets it once, below. */
  --at-field-label: 96px;

  /* shell: the application frame. The bar has no height token: it is a row of
     controls framed by the gutter, so it sizes itself and its content sits the
     same distance from the top edge as a page's does. The edit column has one
     width per frame, and room content is capped so a wide display buys more
     columns rather than longer lines.
     Breakpoints cannot themselves be tokens (a media query cannot read a
     custom property), so the three frames are named here and the literals
     appear in exactly one place, the shell section below:
       compact  below 720   one column, the edit column becomes a sheet
       medium   720 to 1119 the split holds, tightened
       wide     1120 and up the split at full width (1600 and up widens it) */
  --at-edit-w: 360px;
  /* The dock is three control rows plus a band title plus its own padding:
     the height at which a front panel shows a whole section at once, which is
     the only height worth having. Taller and it eats the room; shorter and the
     sections it exists to show get clipped. */
  --at-dock-h: 288px;
  /* One bay of the dock. Wide enough for the widest control a band holds (a
     fader needs 220), and every band takes it whether it needs it or not. */
  --at-dock-col: 232px;
  --at-room-max: 1280px;
  /* The compact sheet: open it takes half the screen and a little, which is
     what leaves a room worth reading above it (Topograph's phone panel is the
     reference); shut it is one handle row, always there. */
  --at-sheet-h: 56dvh;
  --at-sheet-handle: 44px;

  /* icons: a tight, grid-aligned size ramp. Default 20 sits inside a 32
     control. Source stroke is uniform (see anti-icons.js). */
  --at-icon-s: 16px;
  --at-icon-m: 20px;
  --at-icon-l: 24px;
  --at-icon-xl: 40px;

  /* select caret, inlined so file:// works; night overrides the stroke */
  --at-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%230A0A0A' stroke-width='1.3'/%3E%3C/svg%3E");

  /* motion: exponential settle, never bounce */
  --at-settle: cubic-bezier(0.16, 1, 0.3, 1);
  --at-t1: 90ms;
  --at-t2: 180ms;
  --at-t3: 360ms;
  --at-t4: 600ms;
}

/* night: the black panel from the base sheet. Same system, inverted field. */
[data-at-theme='night'] {
  /* The split rule holds at night: the ground (paper, panel) keeps the cool
     air, and the surface, which is the control fill and therefore content,
     goes neutral. The old #14161A was the most saturated cool token in the
     whole system, and the place the red vibrated hardest. */
  --at-paper: #0A0B0C;
  --at-surface: #151515;
  /* At night the panel is RAISED: the paper's own spread, 18 a channel UP,
     the same step against the paper the day panel takes down (1.17).
     Until 2026-10-01 it recessed here too (#060708, darker than the paper, so
     that the control fill stayed the lighter thing in both themes). The
     argument was sound and the result could not be seen: there is nothing
     under near-black to recess into, the step measured 1.02, and on a phone
     the edit sheet could not be told from the room it was drawn over. The
     cost the old note named is real, moving up closes the gap to the control
     fill, so on the panel a control stops being lifted off its ground and is
     cut through it instead (see "on the panel" below). */
  --at-panel: #1C1D1E;
  --at-panel-ink-3: #7C7B7B;
  --at-ink: #F0F0F0;
  --at-ink-2: #B7B7B7;
  --at-ink-3: #686767;
  --at-hairline: rgba(240, 240, 240, 0.9);
  --at-hairline-2: rgba(240, 240, 240, 0.24);
  /* An ink block at night is the light one, so what stands on it goes dark:
     the on-ink ramp is exactly the day theme's ink ramp. */
  --at-on-ink: #0A0A0A;
  --at-on-ink-2: #494949;
  --at-on-ink-3: #706F6F;
  --at-on-hairline-2: rgba(10, 10, 10, 0.22);
  /* The lamp does not change, the reading of it does: the same red that reads
     at 6.11:1 on day paper reads at 3.35:1 on night paper, so this is the one
     half of the accent that flips. --at-accent itself is deliberately absent
     from this block. */
  --at-accent-ink: #F2454E;
  --at-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23F0F0F0' stroke-width='1.3'/%3E%3C/svg%3E");
}

/* The ink containers, and the one rule that makes them behave.
   The remap sits on the children rather than on the container itself: a
   custom property set on an element applies to that element too, so
   `--at-ink: var(--at-on-ink)` on the card would repaint its own background
   with the flipped value and the field would vanish along with the type.
   Custom properties inherit, so setting them one level in reaches everything.
   Every ink container in this sheet belongs on this list, and the error toast
   was left off it: it sets `color: var(--at-paper)` for its message, which
   covers the one thing that inherits colour and nothing else, so its timestamp
   went on naming --at-ink-3 and sat at 3.5:1 on near-black. An ink field
   inverts what stands on it, all of it. */
.at-card--ink > *,
.at-screen--ink > *,
.at-toast--error > * {
  --at-ink: var(--at-on-ink);
  --at-ink-2: var(--at-on-ink-2);
  --at-ink-3: var(--at-on-ink-3);
  --at-hairline-2: var(--at-on-hairline-2);
}

/* ---------- base ---------- */

:where(.at, .at *, .at *::before, .at *::after) { margin: 0; padding: 0; box-sizing: border-box; }

.at {
  font-family: var(--at-mono);
  font-size: var(--at-fs-body);
  line-height: 1.55;
  background: var(--at-paper);
  color: var(--at-ink);
  -webkit-font-smoothing: antialiased;
  transition: background var(--at-t3) var(--at-settle), color var(--at-t3) var(--at-settle);
}

.at ::selection { background: var(--at-accent); color: var(--at-on-accent); }

/* `hidden` has to win, and in a system like this one it does not by default.
   The browser hides a hidden element with `[hidden] { display: none }` in its
   own sheet, and any author rule that sets display outranks a UA rule: this
   system sets display on nearly every component (at-row and at-stack flex,
   at-field grids, at-group inline-flexes, the shell's areas), so marking one
   hidden left it on screen and only the attribute changed. Four surfaces had
   already written the override by hand for their own components, which is what
   a missing system rule looks like from the outside.
   `!important` is the correct tool here and the only place this sheet uses it:
   hidden is not a style, it is a statement that the element is not there, and
   nothing about how a thing looks should be able to argue with that. */
.at [hidden] { display: none !important; }

/* ---------- type ---------- */

/* Labels are typed in sentence case in markup; the system sets them.

   Three steps, and the step says what the thing is, not how important it felt
   at the time:
     section title   --at-ink     a band of the panel (Identity, Waveforms)
     control label   --at-ink-2   the name of one control (Name, Key, Firmness)
     value           --at-ink     what the control currently says
   A panel then reads as sections first and fields second. Before this the two
   were the wrong way round in places and inconsistent with themselves: a
   fader's name sat at --at-ink-3 while the menu's name directly under it sat
   at full ink, so identical things in one column were three different greys. */
.at-label {
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  color: var(--at-ink);
}
.at-label--quiet { color: var(--at-ink-3); }
/* The name of a single control, wherever it is written. */
.at-label--field { color: var(--at-ink-2); }

.at-caption {
  font-size: var(--at-fs-caption);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--at-ink-3);
  text-wrap: pretty;
}

/* A link in running text is the text's own ink, underlined. Left to the
   browser a bare anchor is its default blue, which is a second accent by day
   and 2.1:1 on the paper by night. An anchor that is a component (a button, a
   rail item) sets its own colour and is not touched by this. */
a { color: inherit; text-underline-offset: 0.2em; }
/* Its focus is the word-button's: the block inverts. No ring (see .at-btn). */
a:focus-visible { outline: none; background: var(--at-ink); color: var(--at-paper); }

/* Text takes the width of its column. The one ceiling is a reading measure
   for long paragraphs, set wide enough that a short line never breaks early
   beside content that runs further. text-wrap: pretty keeps a last line from
   holding one word. */
.at-body { font-size: var(--at-fs-body); color: var(--at-ink-2); max-width: 100ch; text-wrap: pretty; }
.at-lead { font-size: var(--at-fs-lead); color: var(--at-ink); max-width: 80ch; text-wrap: pretty; }

.at-display {
  font-family: var(--at-sans);
  font-weight: 700;
  font-size: var(--at-fs-display);
  line-height: 1.15;
  letter-spacing: -0.01em;
}
.at-hero {
  font-family: var(--at-sans);
  font-weight: 700;
  font-size: var(--at-fs-hero);
  line-height: 1.05;
  letter-spacing: -0.015em;
}

/* Blinking underscore, the site's typing cursor */
.at-cursor::after {
  content: '_';
  animation: at-blink 1s step-end infinite;
}
@keyframes at-blink { 50% { opacity: 0; } }

/* Spec-sheet row: label ... value, dotted leader between.

   The leader sits on the BASELINE, not on the row's centre line. A leader is
   the type continuing by other means, so it has to start where the type sits;
   centred, it ran through the middle of the words at either end and read as a
   rule that happened to be there rather than as the line carrying the eye
   across. The difference is a few pixels and it is the whole difference
   between a spec sheet and a table with a stripe in it.
   `align-items: baseline` does it: a flex item with no baseline of its own
   synthesises one from its bottom margin edge, so a zero-height box with a
   bottom border lands that border exactly on the type's baseline.
   `align-content: center` then puts the whole line back in the middle of the
   24px row, so the rhythm the min-height buys is not spent on the shift. */
.at-leaders {
  display: flex;
  align-items: baseline;
  align-content: center;
  gap: var(--at-s2);
  min-height: var(--at-control-h);      /* the row rides the 24px control rhythm */
  font-size: var(--at-fs-label);
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.at-leaders > .at-leaders-fill {
  flex: 1;
  height: 0;
  border-bottom: var(--at-line) dotted var(--at-hairline-2);
}
/* A glyph has no baseline to give, and synthesising one from its bottom edge
   would hang it below the words it sits among. Icons stay on the centre line. */
.at-leaders > .at-icon { align-self: center; }
.at-leaders > b { font-weight: 400; color: var(--at-ink); }
.at-leaders > span:first-child { color: var(--at-ink-3); }

/* ---------- icons ---------- */

/* overflow visible so hairline strokes that sit on the viewBox edge
   (circles, boxes) are not clipped at their extremes */
.at-icon { display: inline-block; width: var(--at-icon-m); height: var(--at-icon-m); vertical-align: middle; flex: none; overflow: visible; }
.at-icon--s { width: var(--at-icon-s); height: var(--at-icon-s); }
.at-icon--l { width: var(--at-icon-l); height: var(--at-icon-l); }
.at-icon--xl { width: var(--at-icon-xl); height: var(--at-icon-xl); }
.at-icon > svg { display: block; width: 100%; height: 100%; overflow: visible; }
svg.at-icon { display: block; }
/* non-square glyphs keep their aspect: width follows height */
/* The non-square glyphs: the ratios are the line glyphs' own viewBoxes (the
   sheet's 25 box), and the specimen overrides per glyph from ATIcons.aspect,
   which is the authority, so a fill on its 24 box paints true as well. */
.at-icon--pill { width: auto; aspect-ratio: 49 / 25; }
.at-icon--narrow { width: auto; aspect-ratio: 15 / 25; }
/* dot-matrix motif: a wide 6-by-3 grid (see anti-icons.js) */
.at-icon--matrix { width: auto; aspect-ratio: 29 / 14; }

.at-spin { animation: at-spin 2.4s linear infinite; transform-origin: 50% 50%; }
@keyframes at-spin { to { transform: rotate(360deg); } }

/* ---------- diagram: a drawing that carries type ---------- */

/* **A drawing is laid out in the same pixels as everything else.**
   An svg with a viewBox scales its whole coordinate space to fit its box, and
   type is part of that space: a label declared at 9 inside a drawing that
   happens to render at 1.5x paints at 13.4, which is larger than this system's
   body copy, and it paints at a different size in every card it lands in. The
   type scale stops meaning anything the moment a drawing is scaled, and no
   amount of choosing a smaller number inside the viewBox fixes it, because the
   number is not the size.
   So a diagram never scales. `ATDiagram.fit` sets its viewBox to its own pixel
   box, which makes the scale exactly 1, which makes `--at-fs-micro` mean 9px
   here the way it means 9px everywhere. A wider card gives the drawing more
   room to lay out in, not a bigger drawing. The spacing tokens work inside it
   for the same reason: it is on the same grid as the page.
   Type inside one is filled and never stroked: a stroke rule meant for the
   geometry that also reaches the type paints an outline over every glyph. */
.at-diagram { display: block; width: 100%; }
.at-diagram text {
  font-family: var(--at-mono);
  font-size: var(--at-fs-micro);
  letter-spacing: 0.08em;
  fill: var(--at-ink-3);
  stroke: none;
}
.at-diagram .at-diagram-ink { fill: var(--at-ink); }
/* The live mark in a diagram is applied to <text> as well as to geometry (the
   specimen's ladder labels its marked rung with it), so it reads. */
.at-diagram .at-diagram-live { fill: var(--at-accent-ink); }

/* ---------- chip ---------- */

.at-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--at-s2);
  height: var(--at-chip-h);
  padding: 0 var(--at-pad-x);
  background: var(--at-surface);
  border-radius: var(--at-r-pill);
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  white-space: nowrap;
}
.at-chip--line { background: transparent; box-shadow: inset 0 0 0 var(--at-line) var(--at-hairline-2); }
.at-chip--accent { background: var(--at-accent); color: var(--at-on-accent); }

/* ---------- group: the pill that holds a cluster ---------- */

/* From the brand's info-pills sheet: a label and its icons live inside one
   generous rounded pill, as a single unit. Pills group, they never scatter.
   Taller and airier than a chip; the icons ride the ink color.

   A pill is symbol, space, text, and nothing else. It used to carry a hairline
   seam between the two, which was the one place this system drew a rule where
   air would do: the pill is already a container, so a divider inside it is a
   second boundary doing the first one's job. Space separates. */
.at-group {
  display: inline-flex;
  align-items: center;
  gap: var(--at-s3);
  height: var(--at-pill-h);
  padding: 0 var(--at-s4);
  background: var(--at-surface);
  border-radius: var(--at-r-pill);
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  white-space: nowrap;
}
.at-group--line { background: transparent; box-shadow: inset 0 0 0 var(--at-line) var(--at-hairline-2); }
.at-group--accent { background: var(--at-accent); color: var(--at-on-accent); }
.at-group .at-icon { flex: none; }

/* The lockup: the mark and the surface's name, standing on the field itself.
   Not a pill. A pill is a group of controls, and the brand is not a control;
   every inner page used to wear one, which put the mark in a white capsule
   the site's own lockup never had (Adi, 2026-09-05: the same logo everywhere,
   the site's way). Below the compact frame the name leaves and the mark
   stands alone, decided here once rather than on every page. */
.at-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--at-s3);
  height: var(--at-pill-h);
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--at-ink);
  text-decoration: none;
}
.at-brand .at-icon { flex: none; }

/* ---------- actions ---------- */

/* No rounded stroked buttons. An action is a typed word with a marker;
   affordance is the instant block inversion, the blend-mode move in solid
   paint. Primary is a solid ink block. */
.at-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--at-s2);
  text-decoration: none;
  height: var(--at-control-h);
  padding: 0 var(--at-pad-x-tight);
  font-family: var(--at-mono);
  font-size: var(--at-fs-label);
  line-height: 1;
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  color: var(--at-ink);
  background: transparent;
  border: none;
  border-radius: 0;
  cursor: pointer;
  user-select: none;
  transition: background var(--at-t1) var(--at-settle),
              color var(--at-t1) var(--at-settle);
}
.at-btn::before {
  content: '>';
  color: var(--at-ink-3);
  transition: color var(--at-t1) var(--at-settle);
}
/* Focus is the hover state: a fill, never a stroke. The block inverts for
   the keyboard exactly as it does for the pointer. A ring drawn around a
   control reads as an error or as somebody's annotation, and on the button
   of a checkout it read as both (2026-10-03). The rule holds for every
   control in the system: what you press inverts, what you open or switch
   takes the accent as type or as fill, and a field you type in is marked by
   its caret. */
.at-btn:hover, .at-btn:focus-visible { background: var(--at-ink); color: var(--at-paper); outline: none; }
/* The marker takes the word's colour on the fill, not the accent. Voltage on
   ink is two dark colours on top of each other: the one place in this system
   where the accent was put on a field it cannot survive. At rest the marker
   stays a step quieter than the word (--at-ink-3 above); the moment the block
   inverts they read as one thing, which is what they are. `inherit` rather
   than a literal white, so the night theme (light fill, dark word) is right by
   construction instead of by a second rule. */
.at-btn:hover::before, .at-btn:focus-visible::before { color: inherit; }
.at-btn:active { transform: translateY(1px); }

/* Disabled: the control stays in place but loses its voltage: dimmed and
   inert, no hover fill, no press, no marker color. One rule for the whole
   interactive set, so a dead control can never impersonate a live one. */
.at-btn:disabled, .at-iconbtn:disabled, .at-switch:disabled,
.at-input:disabled, .at-select:disabled,
.at-btn[aria-disabled='true'], .at-iconbtn[aria-disabled='true'], .at-switch[aria-disabled='true'] {
  opacity: 0.35;
  cursor: default;
  pointer-events: none;
}

/* Primary: a solid ink block, one step more horizontal padding for presence */
.at-btn--primary { background: var(--at-ink); color: var(--at-paper); padding: 0 var(--at-s4); }
.at-btn--primary::before { content: none; }
.at-btn--primary:hover, .at-btn--primary:focus-visible { background: var(--at-accent); color: var(--at-on-accent); }

/* Bracket action: [ word ]. At rest a quiet bracketed label. On hover the box
   fills (the base .at-btn fast fade) and the brackets are the edges that become
   it: they grow a bit taller and merge into the ink, so no [ ] is left, just a
   solid button. */
.at-btn--tag { overflow: hidden; }                     /* clip any bracket overshoot to the box */
.at-btn--tag::before, .at-btn--tag::after {
  color: var(--at-ink-3);
  transform-origin: center;
  transition: transform var(--at-t2) var(--at-settle), color var(--at-t1) var(--at-settle);
}
.at-btn--tag::before { content: '['; }
.at-btn--tag::after  { content: ']'; }
.at-btn--tag:hover::before, .at-btn--tag:hover::after,
.at-btn--tag:focus-visible::before, .at-btn--tag:focus-visible::after { transform: scaleY(1.4); color: var(--at-ink); }

@media (prefers-reduced-motion: reduce) {
  .at-btn--tag:hover::before, .at-btn--tag:hover::after,
  .at-btn--tag:focus-visible::before, .at-btn--tag:focus-visible::after { transform: none; }
}

/* Square instrument key with a hairline frame, the corner-arrow box.
   Same row height as every other control, so keys line up in a rack. */
.at-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--at-control-h);
  height: var(--at-control-h);
  color: var(--at-ink);
  background: transparent;
  border: var(--at-line) solid var(--at-hairline);
  border-radius: 0;
  cursor: pointer;
  transition: background var(--at-t2) var(--at-settle), color var(--at-t2) var(--at-settle);
}
.at-iconbtn:hover, .at-iconbtn:focus-visible { background: var(--at-ink); color: var(--at-paper); outline: none; }
.at-iconbtn:active { background: var(--at-accent); color: var(--at-on-accent); }

/* The frameless key: the glyph is the whole control. For a glyph that is
   already a container (the dock pair is a frame with its divider moved), the
   hairline box puts a box around a box and the drawing loses. With no shell
   there is nothing to invert, so the affordance is the ink stepping up to full
   and then to the accent, and the press stays the scale the whole set shares.
   The glyph runs one size larger to hold the same weight the frame did. */
.at-iconbtn--bare {
  width: auto;
  color: var(--at-ink-2);
  border-color: transparent;
}
.at-iconbtn--bare:hover { background: transparent; color: var(--at-ink); }
/* No shell to invert, so the keyboard takes the step the press takes. */
.at-iconbtn--bare:focus-visible { background: transparent; color: var(--at-accent-ink); }
.at-iconbtn--bare:active { background: transparent; color: var(--at-accent); }

/* The listener's sound key. A state, so it carries aria-pressed the way the
   switch does, but it says which state it is in with its own glyph instead of
   with the brand's slash. Two slash boxes side by side, one meaning night and
   one meaning sound, tell you nothing about either: the slash is a beautiful
   mark for a switch and a useless one for telling two switches apart, so the
   one whose subject has a picture uses the picture.
   Off is the muted glyph AND a quieter ink, because a muted control should
   look muted; the glyph swap lands with at-punch like every other swap here. */
.at-soundbtn[aria-pressed='false'] { color: var(--at-ink-3); }
.at-soundbtn[aria-pressed='false']:hover, .at-soundbtn[aria-pressed='false']:focus-visible { color: var(--at-paper); }

/* ---------- switch: the slash ---------- */

/* The brand's switch is a slash in a box, not a lever. A single diagonal
   morphs backslash (off) to slash (on) by rotating 90 degrees through the
   settle: the site's Switch.json state machine (frames 35..60) rebuilt in CSS.
   Square and hairline like every icon key; accent fills when on. */
.at-switch {
  position: relative;
  width: var(--at-control-h);
  height: var(--at-control-h);
  background: transparent;
  border: var(--at-line) solid var(--at-hairline);
  border-radius: 0;
  cursor: pointer;
  transition: background var(--at-t2) var(--at-settle), border-color var(--at-t2) var(--at-settle);
}
.at-switch-slash {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Match the at-slash-l/r icon: its diagonal runs 7.5..17.5 in a 24 box, a
     10-unit span per axis, so the rotated line is 10 * sqrt(2) = 0.589 of the
     box. Tied to control-h so it stays right at any height. */
  width: calc(var(--at-control-h) * 0.589);
  height: 1.25px;
  margin-top: -0.625px;
  margin-left: calc(var(--at-control-h) * -0.2946);
  background: currentColor;
  color: var(--at-ink);
  border-radius: var(--at-r-pill);
  transform-origin: 50% 50%;
  transform: rotate(45deg);        /* backslash */
  transition: transform var(--at-t3) var(--at-settle), background var(--at-t2) var(--at-settle);
}
/* On is a solid ink block with no stroke, the block-inversion idiom; off is
   an empty hairline box. State is the fill, never a tint. */
.at-switch[aria-pressed='true'] { background: var(--at-ink); border-color: transparent; }
.at-switch[aria-pressed='true'] .at-switch-slash { transform: rotate(-45deg); color: var(--at-paper); }  /* slash */
.at-switch:active .at-switch-slash { transform: rotate(45deg) scaleX(0.86); }
.at-switch[aria-pressed='true']:active .at-switch-slash { transform: rotate(-45deg) scaleX(0.86); }
/* Focus fills the box with voltage, on either state: a fill like every other
   state of this control, not a stroke around it. */
.at-switch:focus-visible { outline: none; background: var(--at-accent); border-color: transparent; }
.at-switch:focus-visible .at-switch-slash { color: var(--at-on-accent); }

/* ---------- fader: a row of dots that rise under the hand ---------- */

/* The track is a dotted line (the matrix primitive, ink on ghost). A dot
   sits flat at rest, and rises into a tick under the cursor: 2px at rest,
   4px reacting, 6px at max. The dot at the value stays raised, the visible
   handle. Filled dots run up to the value (accent while dragging), ghosted
   dots run past it, the way the matrix inks and ghosts its grid.
   anti-fader.js upgrades every .at-fader input into this; without the
   script the native input below stays usable. */

/* The gap here is not the distance you see. The label row is a control-height
   row (24) around 17px of text, and the rail is a control-height row (24)
   around 6px of dots, so both contribute slack of their own: 4 above and 9
   below the declared gap, which made 8 read as 20.
   The two rows give up their slack (the label row hugs its text; the rail
   keeps its 24 because that is the drag target, and a fader you cannot catch
   is worse than a fader set slightly loose), and the gap itself steps down to
   --at-s1, since it is now the whole distance rather than a third of it. */
/* 220 is the width a fader wants: below it the dots crowd and the rail stops
   being worth aiming at. It is a want, not a demand, and it used to be written
   as `min-width`, which is a demand: one fader in a labelled row set a hard
   405px floor under any surface that held one, and the page grew a horizontal
   scrollbar rather than a smaller control. Every app that hit this wrote
   `min-width: 0` back over it in its own sheet, which is the tell.
   It asks through the flex basis instead. Where the fader is a flex item it
   starts at 220 and gives way under pressure; where it is a grid item the
   track decides and `min-width: 0` lets the track actually decide. */
.at-fader {
  display: flex;
  flex-direction: column;
  gap: var(--at-s1);
  min-width: 0;
  flex: 1 1 220px;
}
.at-fader .at-leaders { min-height: 0; }
/* A fader's own row is a control label and a value, so it takes the same two
   steps as every other control row rather than the spec-sheet greys. */
.at-fader .at-leaders > span:first-child { color: var(--at-ink-2); }
.at-fader .at-leaders > b { color: var(--at-ink); }
/* Live: while the control is being moved (pointer down, or focused for the
   keyboard) the value it is reporting turns voltage, the same moment the
   inked dots do. The value is what is changing, so the value is what is
   marked; the label stays put, because which row you are on has not changed. */
.at-fader:has(.at-fader-rail.is-active) .at-leaders > b { color: var(--at-accent-ink); }

/* A fader's own label row drops the dotted leader. Everywhere else the leader
   is a spec-sheet device that carries the eye from a name to its value, but
   here it runs directly above a rail of dots and the two read as one dotted
   thing: the reader cannot tell the annotation from the control. The rail is
   already doing the connecting, so the leader has nothing left to do and the
   air alone separates label from value, which is the house rule anyway. */
.at-fader .at-leaders > .at-leaders-fill { border-bottom: 0; }

/* No-JS fallback: a minimal native track and thumb, on the house palette. */
.at-fader input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 24px;
  background: transparent;
  cursor: ew-resize;
}
.at-fader input[type='range']::-webkit-slider-runnable-track { height: var(--at-line); background: var(--at-hairline); }
.at-fader input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none; width: 10px; height: 20px; margin-top: -10px;
  border-radius: var(--at-r); background: var(--at-ink); border: none;
}
.at-fader input[type='range']::-moz-range-track { height: var(--at-line); background: var(--at-hairline); }
.at-fader input[type='range']::-moz-range-thumb { width: 10px; height: 20px; border-radius: var(--at-r); background: var(--at-ink); border: none; }
/* Focus without a ring: the plain fader's thumb takes the accent, and the
   dotted rail below marks it with voltage of its own. */
.at-fader input[type='range']:focus-visible { outline: none; }
.at-fader input[type='range']:focus-visible::-webkit-slider-thumb { background: var(--at-accent); }
.at-fader input[type='range']:focus-visible::-moz-range-thumb { background: var(--at-accent); }

/* Upgraded: the dot rail carries the visual, the input rides on top for
   pointer, keyboard, and form semantics, transparent but live. */
.at-fader--dots .at-fader-rail { position: relative; height: 24px; }
.at-fader--dots .at-fader-dots {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: space-between;
  pointer-events: none;
}
/* On a stepped rail each dot is a snap step (--major), one bold tall dot at
   the value; a continuous rail is a uniform fine row. Heights are set inline
   by anti-fader.js (2 continuous, 4 step, 6 handle, up to 8 under the cursor),
   colour by class here: inked up to the value, ghosted past it. */
.at-fader-dot {
  width: 2px; height: 2px; flex: none;
  border-radius: var(--at-r-pill);        /* dot at 2x2, tick as it grows */
  background: var(--at-hairline-2);        /* ghost: past the value */
  transition: height var(--at-t2) var(--at-settle), background var(--at-t2) var(--at-settle);
}
.at-fader-dot.is-on { background: var(--at-ink-3); }             /* continuous, inked: quiet fill */
.at-fader-dot--major { background: var(--at-ink-3); }            /* step, ghost: past the value */
.at-fader-dot--major.is-on { background: var(--at-ink); }        /* step, inked: up to the value */
.at-fader-rail.is-active .at-fader-dot--major.is-on { background: var(--at-accent); }  /* voltage while dragging */

/* A continuous fader has no steps, so its minor dots carry the value: their
   inked run reads in full ink, and turns to voltage while dragging. */
/* .at-fader--ruler, the stepped twin the component also toggles, carries no
   rules of its own on purpose: the at-fader-dot--major styling does that
   work, and the class stands as the state hook. */
.at-fader--smooth .at-fader-dot.is-on { background: var(--at-ink); }
.at-fader--smooth .at-fader-rail.is-active .at-fader-dot.is-on { background: var(--at-accent); }

.at-fader--dots .at-fader-rail input[type='range'] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: ew-resize;
  -webkit-appearance: none; appearance: none; background: transparent;
}
.at-fader--dots .at-fader-rail input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none; width: 18px; height: 24px; border: none; background: transparent;
}
.at-fader--dots .at-fader-rail input[type='range']::-moz-range-thumb {
  width: 18px; height: 24px; border: none; background: transparent;
}
/* No box around the rail: the row you are tuning is marked by its name, see
   .at-field:focus-within below. While dragging, the inked dots carry voltage. */

@media (prefers-reduced-motion: reduce) {
  .at-fader-dot { transition: background var(--at-t2) var(--at-settle); }
}

/* ---------- form controls ---------- */

/* Square, mono, no resting stroke: the fill is the container. Field boxes stay
   silent at rest and on hover, and turn to voltage only on focus. */
.at-input,
.at-select,
.at-menu-btn {
  font-family: var(--at-mono);
  font-size: var(--at-fs-body);
  line-height: 1;
  color: var(--at-ink);
  background: var(--at-surface);
  border: var(--at-line) solid transparent;
  border-radius: 0;
  height: var(--at-control-h);
  padding: 0 var(--at-pad-x-tight);
  min-width: 0;
  transition: border-color var(--at-t2) var(--at-settle);
}
/* No stroke on focus. A field you type in is marked by its caret; a field
   you open is marked by its value taking the accent as type, the way a
   focused row's readout does. A red frame around a field is also what every
   checkout uses to say the field is wrong. */
.at-input:focus, .at-select:focus, .at-menu-btn:focus,
.at-input:focus-visible, .at-select:focus-visible, .at-menu-btn:focus-visible {
  outline: none;
}
.at-select:focus-visible, .at-menu-btn:focus-visible { color: var(--at-accent-ink); }
.at-input::placeholder { color: var(--at-ink-3); }

/* The checkbox, and it is a real one. The box is a styled native
   input[type=checkbox], not a div wearing a role, because this control's whole
   job is to carry consent: it has to be focusable, space-toggleable, announced
   as a checkbox and reported in its actual state by every assistive technology
   without any of that being reimplemented here. `appearance: none` takes the
   platform paint and leaves the semantics.
   The mark rides the tokens and is not painted white. A checked box is an ink
   fill, so at night, when ink is the light value, a hardcoded white tick would
   be white on near-white and the control would read as empty in exactly the
   state that matters. It is drawn as a mask filled with --at-paper instead, so
   it inverts with the box the way every other filled control in this system
   already does. That is the same failure the conformance pass found in the ink
   containers, and it is worth not repeating on the one control that carries
   consent.
   The box is a SIBLING SPAN, not the input painted directly, and that is a
   portability decision rather than a stylistic one. Drawing the mark as
   `input::before` works in Chrome and nowhere else: an input is a replaced
   element, the spec gives replaced elements no pseudo-elements, and Safari and
   Firefox honour that. The tick simply never paints there, so the box looks
   permanently unticked in the one state the user has to be able to see, on the
   one control the commercial model rests on. The input stays real and stays
   the thing that is clicked, focused and announced; it is laid transparently
   over the drawn box in the same grid cell, so no label-for wiring, no
   pointer-event forwarding, and no script. */
.at-check {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  gap: var(--at-s3);
  align-items: start;
  cursor: pointer;
}
.at-check input[type='checkbox'],
.at-check .at-check-box {
  grid-column: 1;
  grid-row: 1;
  width: 16px;
  height: 16px;
  /* Optically seated against the first line of a caption rather than its box:
     a 16px square beside 1.5-line type sits high if it is simply top aligned. */
  margin: 1px 0 0;
}
/* Transparent, not hidden: it keeps its box, so it is what the pointer lands
   on and what focus goes to. display:none or visibility:hidden would take it
   out of the tab order, which is the usual way this pattern gets broken. */
.at-check input[type='checkbox'] {
  appearance: none;
  -webkit-appearance: none;
  opacity: 0;
  z-index: 1;
  cursor: pointer;
}
.at-check .at-check-box {
  display: grid;
  place-content: center;
  border: var(--at-line) solid var(--at-hairline-2);
  border-radius: var(--at-r);
  background: var(--at-paper);
  transition: background-color var(--at-t2) var(--at-settle), border-color var(--at-t2) var(--at-settle);
}
.at-check .at-check-box::before {
  content: '';
  width: 12px;
  height: 12px;
  background: var(--at-paper);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 6.2l2.6 2.6L10 2.6' fill='none' stroke='%23000' stroke-width='1.6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 6.2l2.6 2.6L10 2.6' fill='none' stroke='%23000' stroke-width='1.6'/%3E%3C/svg%3E") center / contain no-repeat;
  /* It settles in rather than appearing, like everything else that arrives. */
  transform: scale(0);
  transition: transform var(--at-t2) var(--at-settle);
}
.at-check input[type='checkbox']:hover + .at-check-box { border-color: var(--at-ink-2); }
.at-check input[type='checkbox']:checked + .at-check-box {
  background-color: var(--at-ink);
  border-color: var(--at-ink);
}
.at-check input[type='checkbox']:checked + .at-check-box::before { transform: scale(1); }
/* Focus marks the words beside the box, the way a focused row marks its
   readout. Filling the box would say it is ticked. */
.at-check:has(input[type='checkbox']:focus-visible) { color: var(--at-accent-ink); }
.at-check input[type='checkbox']:disabled { cursor: default; }
.at-check input[type='checkbox']:disabled + .at-check-box { opacity: 0.35; }
@media (prefers-reduced-motion: reduce) {
  .at-check .at-check-box::before { transition: none; }
}

/* ---------- segmented field ---------- */

/* Information at rest, editable on press. At rest the frame is a readout with
   a pen key; pressing it opens the cells (they gap apart, turn editable, the
   pen becomes the commit check) and the pressed cell highlights to voltage,
   the way a date field selects a segment. anti-segments.js wires the two
   states, the typing, stepping, and clamping. A cell is numeric or an in-place
   enum (the scrubbed dropdown). */
.at-seg {
  display: inline-flex;
  align-items: stretch;
  height: var(--at-control-h);
  padding-left: var(--at-pad-x-tight);
  background: var(--at-surface);
  border: var(--at-line) solid transparent;   /* the fill is the container; no persistent stroke */
  border-radius: 0;
  font-family: var(--at-mono);
  font-size: var(--at-fs-body);
  color: var(--at-ink);
  cursor: pointer;                            /* at rest the whole frame invites the press */
  transition: border-color var(--at-t2) var(--at-settle);
}
.at-seg.is-editing { cursor: default; }   /* the open slots, accent, tick, and label carry the state; no box */

/* At rest the parts are merged (no gap); on open they physically separate by
   a real gap and each cell fills faintly, so it reads as a set of slots. On
   confirm they merge back to the readout. */
.at-seg-body { display: inline-flex; align-items: center; gap: 0; transition: gap var(--at-t3) var(--at-settle); }
.at-seg.is-editing .at-seg-body { gap: 4px; }

.at-seg-slot {
  border: none;
  background: transparent;
  outline: none;
  font: inherit;
  color: inherit;
  text-align: center;
  padding: 0 2px;
  min-width: 0;
  cursor: inherit;
  border-radius: var(--at-r);
  caret-color: transparent;                   /* a machined readout, not a text box */
  font-variant-numeric: tabular-nums;
  -webkit-appearance: none;
  appearance: none;
  transition: background var(--at-t2) var(--at-settle), color var(--at-t1) var(--at-settle);
}
.at-seg-slot::-webkit-outer-spin-button,
.at-seg-slot::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.at-seg.is-editing .at-seg-slot { cursor: text; }
.at-seg.is-editing .at-seg-slot:not(:focus) { background: var(--at-paper); }   /* a slot you can press */
.at-seg.is-editing .at-seg-slot--enum { cursor: pointer; }
.at-seg.is-editing .at-seg-slot:focus { background: var(--at-accent); color: var(--at-on-accent); }
.at-seg-slot--enum { text-transform: uppercase; letter-spacing: 0.04em; }
.at-seg-sep { color: var(--at-ink-3); user-select: none; align-self: center; }

/* The pen key: sits at the right edge (no divider) and morphs pen to the
   commit check on open. */
.at-seg-edit {
  position: relative;
  flex: none;
  width: var(--at-control-h);
  align-self: stretch;
  margin-left: auto;                          /* push to the right edge */
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--at-ink-3);
  transition: color var(--at-t2) var(--at-settle);
}
.at-seg-edit:hover { color: var(--at-ink); }
.at-seg.is-editing .at-seg-edit { color: var(--at-accent-ink); }
.at-seg-edit:focus-visible { outline: none; color: var(--at-accent-ink); }
.at-seg-ico {
  position: absolute; inset: 0; margin: auto;
  width: var(--at-icon-s); height: var(--at-icon-s);
  transition: opacity var(--at-t2) var(--at-settle), transform var(--at-t2) var(--at-settle);
}
.at-seg-ico--tick { opacity: 0; transform: scale(0.6) rotate(-30deg); }
.at-seg.is-editing .at-seg-ico--pen { opacity: 0; transform: scale(0.6) rotate(30deg); }
.at-seg.is-editing .at-seg-ico--tick { opacity: 1; transform: none; }

/* A value past its max clamps and shakes: the reject tell, brief and settling. */
@keyframes at-seg-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-2px); }
  60% { transform: translateX(2px); }
  80% { transform: translateX(-1px); }
}
.at-seg-shake { animation: at-seg-shake var(--at-t2) var(--at-settle); }
@media (prefers-reduced-motion: reduce) { .at-seg-shake { animation: none; } }

.at-select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 28px;
  background-image: var(--at-caret);
  background-repeat: no-repeat;
  background-position: right var(--at-pad-x-tight) center;
  cursor: pointer;
}

/* Labeled row: label, control, readout. The theme panel's ctl idiom.
   The control lane is minmax(0, 1fr), not 1fr. A bare 1fr refuses to shrink
   below its content's min-content width, so a fader (which asks for 220) plus
   the label lane plus the readout set a hard floor of about 405px, and any
   surface narrower than that grew a horizontal scrollbar instead of a smaller
   control. minmax(0, ...) is what lets a row give way. */
.at-field {
  display: grid;
  grid-template-columns: var(--at-field-label) minmax(0, 1fr) auto;
  gap: var(--at-s3);
  align-items: center;
}

/* The stacked field: the tray row.
   A tray is a fixed narrow column, and in one a name lane, a real control and
   a readout are three things fighting over 300-odd pixels. Stacked, the name
   and its value take the top line and the control gets the whole width under
   them, which is what the studio's panel had been doing all along in its own
   sheet while the two machines did the inline thing in theirs. One component
   now, two arrangements, so a tray row is a decision rather than a local
   re-implementation.
   Columns are assigned by position, not by tag: the first child is the name,
   an output rides up beside it, and everything else spans. That way a row can
   be built from a span, a label, or a whole fader without the system caring
   which, and a caption at the end sits under the control it explains. */
.at-field--stack {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--at-s2) var(--at-s3);
  align-items: center;
}
.at-field--stack > * { grid-column: 1 / -1; min-width: 0; }
.at-field--stack > :first-child { grid-column: 1; }
.at-field--stack > output { grid-column: 2; grid-row: 1; }
.at-field > label {
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  color: var(--at-ink-2);
  transition: color var(--at-t2) var(--at-settle);
}
/* The row being tuned is marked by its VALUE turning to voltage, not by a box
   around the control. It used to mark the name as well, as a matched pair, but
   the name is a step back from the value now (see the label hierarchy above)
   and sending it to full voltage on focus threw that away at the one moment
   the value most needs to be the loud thing. Only one row can be live, so the
   accent stays scarce either way. */
.at-field:focus-within > output { color: var(--at-accent-ink); }
.at-field > output {
  font-size: var(--at-fs-caption);
  letter-spacing: 0.06em;
  color: var(--at-ink-3);
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 40px;
  transition: color var(--at-t2) var(--at-settle);
}
.at-field .at-duo { display: flex; gap: var(--at-s2); }
.at-field .at-duo > * { flex: 1; }

/* Stepper: two icon keys around a value */
.at-stepper { display: inline-flex; align-items: center; gap: var(--at-s4); }
.at-stepper > .at-value { min-width: 56px; text-align: center; }

/* Menu: a select with opinions. Button opens a hairline list; items
   invert on hover; a locked item refuses (and should sound the reject). */
.at-menu { position: relative; display: inline-block; min-width: 160px; }
/* The dropdown reads exactly like an input field: white fill, no stroke, the
   accent only on focus. Its caret is a real mark that flips when the list
   opens. */
/* The button is a value on one line, and it stays one line. A flex box lets a
   long value ('minor pent.') wrap and push the control past the one control
   height every row in this system shares, which breaks the baseline the whole
   panel is aligned to. Block layout with a reserved caret lane keeps the text
   on its line and truncates it instead: a clipped value is a legible fault, a
   two-line field is a broken layout. */
.at-menu-btn {
  display: block;
  position: relative;
  width: 100%;
  text-align: left;
  cursor: pointer;
  line-height: calc(var(--at-control-h) - 2 * var(--at-line));
  padding-right: calc(var(--at-pad-x-tight) + 10px + var(--at-s2));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.at-menu-btn::after {
  content: '';
  position: absolute;
  top: 50%;
  right: var(--at-pad-x-tight);
  margin-top: -3px;
  width: 10px;
  height: 6px;
  background: var(--at-caret) no-repeat center;
  transition: transform var(--at-t2) var(--at-settle);
  pointer-events: none;
}
.at-menu[data-open='true'] .at-menu-btn::after { transform: rotate(180deg); }
.at-menu-list {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 100%;
  background: var(--at-surface);          /* the fill is the container, no stroke */
  padding: var(--at-s1) 0;
  z-index: 10;
  display: none;
  animation: at-toast-in var(--at-t2) var(--at-settle);
}
.at-menu[data-open='true'] .at-menu-list { display: block; }
/* An option is a value, and it has to look like the value it will become. It
   used to be set as a label (uppercase, tracked, 11px) while the button showed
   the same option as body text, so the list and the field disagreed about the
   same word: you picked SINE and got sine. Options now match the button
   exactly, which also means the highlight moves down a list of real values
   rather than a list of headings. Long ones truncate for the reason the button
   does: one option, one line. */
.at-menu-item {
  display: block;
  width: 100%;
  height: var(--at-control-h);
  line-height: var(--at-control-h);
  text-align: left;
  padding: 0 var(--at-pad-x-tight);
  font-family: var(--at-mono);
  font-size: var(--at-fs-body);
  color: var(--at-ink);
  background: transparent;
  border: none;
  border-radius: 0;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Hover and keyboard-active share the highlight: the option you are previewing
   is a selection, so it fills with the accent, like a field's active cell,
   not the black action-invert. */
.at-menu-item:hover,
.at-menu-item.is-active { background: var(--at-accent); color: var(--at-on-accent); }
.at-menu-item[aria-disabled='true'] { color: var(--at-ink-3); cursor: not-allowed; }
.at-menu-item[aria-disabled='true']:hover,
.at-menu-item[aria-disabled='true'].is-active { background: transparent; color: var(--at-ink-3); }
/* No selected marker: the highlight on open shows which one is current. */

/* ---------- surfaces ---------- */

.at-card {
  background: var(--at-surface);
  border: var(--at-line) solid var(--at-hairline-2);
  border-radius: var(--at-r);
  padding: var(--at-s5);
}
/* at-card--flat is gone. Its whole job was to take the radius off a card, and
   with one corner value in the system and that value 0, it was asking for what
   it already had. A modifier that changes nothing is worse than no modifier:
   it implies a distinction the sheet cannot show. Removed 2026-07-25 with the
   radius collapse. */
/* On the panel. Two things change for whatever stands on one, and both give
   back contrast the panel's own step takes away.
   Captions take --at-panel-ink-3, which holds them at 4:1 on the panel in
   both themes; the plain --at-ink-3 would be 3.7 by day and 3.0 by night.
   Controls at night are cut through the panel rather than lifted off it: the
   fill that lifts a field off the paper is nearly the raised panel's own
   value, so a field there shows the paper instead, the darkest thing on the
   sheet with the whole ink ramp inside it. By day nothing changes: a field
   is white on the paper and white on the panel. */
.at-card--panel, .at-shell-edit { --at-ink-3: var(--at-panel-ink-3); }
[data-at-theme='night'] .at-card--panel,
[data-at-theme='night'] .at-shell-edit { --at-surface: var(--at-paper); }

/* The tray: a panel-filled container. No stroke and no radius on purpose, so
   it reads as a region of the field rather than as an object placed on it: the
   fill is doing the work a border and a corner used to do badly. */
.at-card--panel {
  background: var(--at-panel);
  border-color: transparent;
  border-radius: 0;
}
.at-card--ink {
  background: var(--at-ink);
  color: var(--at-paper);
  border-color: transparent;
}

/* Registration marks: small + in each corner, the print-proof sheet */
.at-marks { position: relative; }
.at-marks::before {
  content: '';
  position: absolute;
  inset: -14px;
  pointer-events: none;
  --m: var(--at-hairline-2);
  background-image:
    linear-gradient(var(--m), var(--m)), linear-gradient(var(--m), var(--m)),
    linear-gradient(var(--m), var(--m)), linear-gradient(var(--m), var(--m)),
    linear-gradient(var(--m), var(--m)), linear-gradient(var(--m), var(--m)),
    linear-gradient(var(--m), var(--m)), linear-gradient(var(--m), var(--m));
  background-size: 9px 1px, 1px 9px, 9px 1px, 1px 9px, 9px 1px, 1px 9px, 9px 1px, 1px 9px;
  background-position:
    left 0 top 4px, left 4px top 0,
    right 0 top 4px, right 4px top 0,
    left 0 bottom 4px, left 4px bottom 0,
    right 0 bottom 4px, right 4px bottom 0;
  background-repeat: no-repeat;
}

/* ---------- readout ---------- */

.at-readout { display: flex; flex-direction: column; gap: var(--at-s2); }
.at-readout .at-leaders { margin: 0; }

/* Seven-ish segment value: sans bold, tabular feel */
.at-value {
  font-family: var(--at-sans);
  font-weight: 700;
  font-size: var(--at-fs-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* ---------- status + toast ---------- */

.at-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.at-dot--ring { background: transparent; box-shadow: inset 0 0 0 var(--at-line) currentColor; }
.at-dot--accent { background: var(--at-accent); box-shadow: none; }

.at-toaststack {
  display: flex;
  flex-direction: column;
  gap: var(--at-s2);
  width: 300px;
}
.at-toast {
  display: flex;
  align-items: center;
  gap: var(--at-s3);
  min-height: var(--at-control-h);
  padding: var(--at-s2) var(--at-pad-x);
  background: var(--at-surface);
  border: var(--at-line) solid var(--at-hairline-2);
  border-radius: var(--at-r);
  font-size: var(--at-fs-label);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  animation: at-toast-in var(--at-t3) var(--at-settle);
}
.at-toast--leaving { opacity: 0; transition: opacity var(--at-t3) var(--at-settle); }
.at-toast--error { background: var(--at-ink); color: var(--at-paper); border-color: transparent; }
.at-toast--warning { border-color: var(--at-hairline); }
/* Success has no dot rule, and losing one is the point. It used to paint its
   dot with the accent, which was survivable while the accent was blue and is
   not now: a red dot on a success toast says the opposite of what the toast
   says. The tier was never carried by that dot anyway. It is carried by the
   commit glyph and by a filled dot where info takes a ring, which is how the
   rest of this system separates states: by geometry, not by hue. */
.at-toast time { margin-left: auto; color: var(--at-ink-3); font-size: var(--at-fs-caption); }
@keyframes at-toast-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* ---------- blend: inversion as contrast ---------- */

/* The site's core move: type never changes color, the field flips it.
   White type with difference reads as ink on paper and paper on ink,
   crossing any boundary mid-glyph. Exclusion is the soft, metallic cousin
   for mid-gray fields. */
.at-blend-text { color: #FFFFFF; mix-blend-mode: difference; }
.at-blend-difference { mix-blend-mode: difference; }
.at-blend-exclusion { mix-blend-mode: exclusion; }
.at-invert { filter: invert(1); }

/* A field cut by a hard diagonal of ink. The edge runs through the middle so
   centered type crosses it and flips mid-glyph, the whole point of the demo. */
.at-field-split {
  background-image: linear-gradient(100deg, transparent 0 49%, var(--at-ink) 49% 100%);
}
/* Cold metal: the lens-barrel gradient, a stage for exclusion type */
.at-field-steel {
  background-image: linear-gradient(105deg, #AEB3BB, #6E747E 38%, #C9CED6 70%, #8A9099);
}

/* Blend field frame: one height for both, type centered on the boundary,
   a mono label naming the mode in the corner. */
.at-blend-field {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 172px;
  text-align: center;
}
.at-blend-field[data-mode]::after {
  content: attr(data-mode);
  position: absolute;
  left: var(--at-pad-x);
  bottom: 14px;
  font-family: var(--at-mono);
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  text-transform: uppercase;
  color: #FFFFFF;
  mix-blend-mode: difference;
  pointer-events: none;
}

/* ---------- space ---------- */

/* Air is a component. An island is a small amount of type held by a large
   amount of nothing; annotations pin micrographics to its corners. */
.at-island {
  display: grid;
  place-items: center;
  min-height: 320px;
  padding: var(--at-s8);
  text-align: left;
}
.at-air { margin-top: var(--at-s9); }
.at-air--vast { margin-top: var(--at-s10); }

/* Screen: the brand's rectangular container. Chrome pins to the four corners
   (label, switch, mark, meta) around a vast empty centre, the way the site and
   the square-format layouts do. The layout primitive behind a product surface,
   not a card of stacked content. */
.at-screen {
  --at-screen-pad: clamp(20px, 3vw, 36px);
  position: relative;
  aspect-ratio: 16 / 10;
  min-height: 300px;
  padding: var(--at-screen-pad);
  background: var(--at-surface);
  border: var(--at-line) solid var(--at-hairline-2);
  border-radius: var(--at-r);
  overflow: hidden;
}
.at-screen--square { aspect-ratio: 1 / 1; }
.at-screen--ink { background: var(--at-ink); color: var(--at-paper); border-color: transparent; }
.at-screen-slot {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: var(--at-s2);
}
.at-screen-slot--tl { top: var(--at-screen-pad); left: var(--at-screen-pad); }
.at-screen-slot--tr { top: var(--at-screen-pad); right: var(--at-screen-pad); }
.at-screen-slot--bl { bottom: var(--at-screen-pad); left: var(--at-screen-pad); }
.at-screen-slot--br { bottom: var(--at-screen-pad); right: var(--at-screen-pad); }
/* The centre stays empty by default; opt a caption into it when needed. */
.at-screen-center { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; pointer-events: none; }

.at-annotated { position: relative; }
.at-annotated > .at-corner {
  position: absolute;
  font-size: var(--at-fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--at-ink-3);
  white-space: nowrap;
}
.at-corner--tl { top: 10px; left: 12px; }
.at-corner--tr { top: 10px; right: 12px; }
.at-corner--bl { bottom: 10px; left: 12px; }
.at-corner--br { bottom: 10px; right: 12px; }

/* ---------- page: the document frame ---------- */

/* A page scrolls where the shell holds still, but both meet the viewport at
   the same distance. `.at-page` is that frame for a scrolling document: the
   gutter on all four sides, and no max width of its own (measure is the page's
   business, the gutter is the system's), so a surface that wants a cap holds
   it on the blocks that need one rather than on the page, which would centre
   the whole thing and turn the gutter into a growing margin.
   The foot takes the gutter like every other edge. It carried macro air at
   first, on the argument that a page should not end flush; but 96 under a
   footer beside 24 down its side is not a considered amount, it just reads as
   the frame coming apart at the bottom. Air below the last block is that
   block's business (the landing page's footer holds its own top margin), not
   the frame's. */
.at-page {
  margin: 0 auto;
  padding: var(--at-gutter);
}

/* ---------- shell: the application frame ---------- */

/* A page scrolls; an app holds still. The shell is a bar, a room, and an edit
   column: the room is what you are auditioning, the edit column is the
   identity driving it, and because the two panes scroll independently the
   controls never scroll away from the thing they retune.
   The whole frame stays on paper. Surface is the fill of a control, not of a
   container (every field, menu, and chip in this system assumes paper behind
   it), so the panes are told apart by the air between them and by the chrome
   floating on them: no panel colour, no rules. */
.at-shell {
  display: grid;
  /* The bar takes what its controls need plus the gutter; it is not a fixed
     height that happens to centre them. That is what made the app's top edge
     disagree with every page's: a 48px bar around a 24px control seats it 12
     from the top, where a page seats its first line at the gutter.
     The tray runs the full height beside it rather than starting under it, so
     the bar belongs to the room it labels and stops at the tray's edge. The
     tray is a region of the app, not a drawer hung below the chrome, and one
     unbroken field from the top edge to the bottom says so; it also puts the
     bar's controls and the tray's first row on the same line, both a gutter
     from the top. */
  grid-template-columns: minmax(0, 1fr) var(--at-edit-w);
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas:
    'bar  edit'
    'room edit';
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}
/* The wrapper is taken out of the layout so bar, room and tray are all grid
   items of the shell and can be placed by name. Both docks are then one
   template swap, and no markup moves. */
.at-shell-body { display: contents; }
.at-shell-bar { grid-area: bar; }
.at-shell-room { grid-area: room; }
.at-shell-edit { grid-area: edit; }

.at-shell-bar {
  display: flex;
  align-items: center;
  gap: var(--at-s4);
  min-width: 0;
  padding: var(--at-gutter);
}
/* Bar chrome keeps its size; only the air between the two ends flexes, so no
   control can be squeezed into illegibility.
   One child is exempt, and has to be: the index. It is the only thing in the
   bar that grows with the app rather than with the system (six sections in the
   studio, three surfaces in the design system), and it is the only thing that
   already knows how to scroll inside itself. Held at `flex: none` it pushed
   the far end of the bar past the bar: at 1200 the studio's six entries and
   the arm hint ran 1015 wide in an 840 bar, which put the sound key and the
   night switch outside their own container and underneath the tray. The off
   switch this system requires of every sounding surface was unreachable at
   that width. Shrink-only (`0 1 auto`), so a wide bar is still laid out by its
   contents and a narrow one scrolls the index rather than losing its ends. */
.at-shell-bar > * { flex: none; }
.at-shell-bar > .at-shell-rooms { flex: 0 1 auto; }
.at-shell-bar-gap { flex: 1 1 auto; }

/* Both panes own their scroll, and neither hands it to the document. */
.at-shell-room,
.at-shell-edit {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The room takes the gutter on every edge it can, so its content sits the same
   distance from the screen as content on any page, and the bar above and the
   room below start on the same line.
   Its right edge is the gutter too, not a wider "air between the panes". The
   panel fill is what separates the room from the tray, the same way the tray's
   own comment below has it: the boundary is which field you are standing on,
   so paying for it a second time in space only made the room's own padding
   asymmetric, 48 on the right against 24 on the left.
   Only the top keeps a larger step, which is air under the bar rather than a
   distance from an edge.
   The floor matches the sides as they are seen: the content is capped and
   centred, so on a wide room the visible side inset is half the slack, and
   the bottom takes the same, (100% - room max) / 2, never less than the
   gutter and never more than s9, so an ultra-wide screen does not end on a
   screen of nothing. */
.at-shell-room {
  padding: var(--at-s7) var(--at-gutter)
    clamp(var(--at-gutter), calc((100% - var(--at-room-max)) / 2), var(--at-s9))
    var(--at-gutter);
}
.at-shell-room-inner { max-width: var(--at-room-max); margin: 0 auto; }
/* The edit column is the tray, so it takes the panel fill: a band running from
   under the bar to the foot, flush to the right edge, no stroke and no radius.
   The room stays on paper, and the two are told apart by which field they are
   rather than by a rule between them. The column's own padding becomes the
   inset of its controls from the panel's edges, so the gutter still measures
   the distance from the screen. */
/* The tray is a region, so its contents sit the gutter from its edges, the
   same distance content sits from the edges of the screen. It used to carry a
   taller top and a wider left as "inner air between the panes", but the panel
   fill draws that boundary now: the air belongs outside the tray, not inside
   it, and one distance to any edge is the rule everywhere else. */
.at-shell-edit {
  padding: var(--at-gutter);
  background: var(--at-panel);
  /* The tray is a fixed narrow column, so its name lane tightens. Set here,
     once, on the region: both machines used to carry the same 76 in their own
     sheets, which is a system rule written three times and owned by nobody. */
  --at-field-label: 76px;
}

/* The room switcher: word actions, the standing one filled with the accent.
   Only one room sounds at a time because only one room is ever on screen,
   which is the truth of the medium: you can hear one gesture, not four. */
.at-shell-rooms {
  display: flex;
  align-items: center;
  gap: var(--at-s2);
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.at-shell-rooms::-webkit-scrollbar { display: none; }
/* A pane that scrolls can take focus in some browsers. It is a region, not a
   control, and nothing is drawn around it. */
.at-shell-room:focus-visible, .at-shell-edit:focus-visible { outline: none; }
.at-shell-rooms .at-btn[aria-current='page'] { background: var(--at-accent); color: var(--at-on-accent); }
.at-shell-rooms .at-btn[aria-current='page']::before { color: var(--at-on-accent); }
/* The standing room is already filled with the accent, so its focus is the
   other fill: the ink block every other entry takes. */
.at-shell-rooms .at-btn[aria-current='page']:focus-visible { background: var(--at-ink); color: var(--at-paper); }
.at-shell-rooms .at-btn[aria-current='page']:focus-visible::before { color: inherit; }

/* Rooms lay their parts on an auto-fitting grid, so a wide display buys more
   columns instead of a longer scroll: air gets used, not left over. */
.at-shell-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  gap: var(--at-s7) var(--at-s6);
  align-items: start;
}

/* The sheet's chrome only exists in the compact frame: the bar button (kept
   for a page that runs without anti-shell.js) and the handle the script puts
   on the sheet itself. */
.at-shell-sheetbtn { display: none; }
.at-shell-sheet-handle { display: none; }
/* The index collapsed into a menu. anti-shell.js measures the chips on every
   resize and marks the bar when they overflow; the menu is the same entries as
   one control, and it takes the chips' place in the bar rather than a place
   of its own. Wide, it sizes like a field; compact, it takes the bar's middle. */
.at-shell-roomsmenu { display: none; flex: 0 1 auto; min-width: 0; width: 220px; }
.at-shell-bar[data-at-rooms='menu'] .at-shell-rooms { display: none; }
.at-shell-bar[data-at-rooms='menu'] .at-shell-roomsmenu { display: block; }

/* ---------- the dock: the edit column laid on its side ---------- */

/* `data-at-dock='bottom'` moves the edit column out of the right-hand slot and
   lays it across the foot, the way a mixer keeps its channel strip under the
   work rather than beside it (Ableton's device row, and Adi's Yugoviz control
   bar, are the same instrument idea). The room gets the full width; the tray
   gets the full width; neither is squeezed by the other.
   The wrapper is taken out of the layout with `display: contents` so the room
   and the tray become rows of the shell grid directly, which means the same
   markup serves both docks and nothing has to be moved in the DOM. */
/* Scoped above the compact frame on purpose. Below 720 the edit column is a
   sheet drawn over the room and there is no room left to dock anything to;
   without this guard the dock's higher specificity would beat the compact
   rules that follow it and the sheet would come apart. */
@media (min-width: 720px) {
  /* Laid along the foot, the tray gives up the full-height column and the bar
     runs the whole width again: there is nothing beside it to stop at. */
  .at-shell[data-at-dock='bottom'] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
      'bar'
      'room'
      'edit';
  }
  .at-shell[data-at-dock='bottom'] .at-shell-room {
    padding: var(--at-s7) var(--at-gutter)
      clamp(var(--at-s7), calc((100% - var(--at-room-max)) / 2), var(--at-s9));
  }
  .at-shell[data-at-dock='bottom'] .at-shell-edit {
    height: var(--at-dock-h);
    padding: var(--at-gutter);
    /* The dock scrolls sideways, never down: a strip that grows a vertical
       scrollbar has stopped being a strip. */
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    /* A dock is usually wider than the screen, and a strip that hides half of
       itself with no mark is a strip you do not know to scroll. The rail is a
       hairline on the panel, in the system's own weight. */
    scrollbar-width: thin;
    scrollbar-color: var(--at-hairline-2) transparent;
  }
  .at-shell[data-at-dock='bottom'] .at-shell-edit::-webkit-scrollbar { height: 8px; }
  .at-shell[data-at-dock='bottom'] .at-shell-edit::-webkit-scrollbar-track { background: transparent; }
  .at-shell[data-at-dock='bottom'] .at-shell-edit::-webkit-scrollbar-thumb {
    background: var(--at-hairline-2);
    border-radius: var(--at-r-pill);
  }
  /* Groups run along the dock as columns of ONE width. Sizing each to its own
     content read as a ragged row of panels with no common measure, and it let
     a single wide section (the signature's button and stepper on one line)
     decide how much of the strip it took. A front panel is ruled into equal
     bays; what does not fit a bay wraps inside it. */
  .at-shell[data-at-dock='bottom'] .at-shell-edit-flow {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: var(--at-dock-col);
    align-items: start;
    gap: var(--at-s7);
    height: 100%;
  }
}

/* wide, 1600 and up: the edit column earns its full width. The gutter does
   not move here: a bigger display buys columns and measure, not margin. */
@media (min-width: 1600px) {
  :root { --at-edit-w: 400px; }
}

/* medium, 720 to 1119: the split holds, tightened. The gutter is already 24
   here (it is the base value), so only the column narrows. */
@media (max-width: 1119px) {
  :root { --at-edit-w: 320px; }
  .at-shell-room { padding: var(--at-s6) var(--at-s6) var(--at-gutter) var(--at-gutter); }
  .at-shell-edit { padding: var(--at-s6) var(--at-gutter) var(--at-gutter) var(--at-s5); }
}

/* compact, below 720: the split cannot hold. The room takes the screen and
   the edit column becomes a sheet drawn over it, so a knob can still be
   turned with the room behind it and in earshot. */
@media (max-width: 719px) {
  :root { --at-gutter: var(--at-s4); }
  /* The screen's floor has to yield here. An aspect-ratio box with a minimum
     height demands the width that satisfies it (300 tall at 16:10 is 480
     wide), so on a phone it stops being a container and becomes a horizontal
     scrollbar. The ratio alone gives it height at this width; the floor was
     only ever there to stop it collapsing on a big layout. */
  .at-screen { min-height: 0; }
  .at-shell-bar { gap: var(--at-s3); }
  .at-brand-name { display: none; }
  /* The rooms take whatever the bar has left and scroll inside it, so no
     amount of chrome can push a room name off the screen. */
  .at-shell-rooms { flex: 1 1 auto; min-width: 0; }
  .at-shell-bar-gap { display: none; }
  /* One column, and no tray in the flow at all: the edit column is drawn over
     the room as a sheet below, so the frame is a bar and a room and nothing
     else. Declared for both docks, since a dock has no meaning at this width. */
  .at-shell,
  .at-shell[data-at-dock='bottom'] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
      'bar'
      'room';
  }
  .at-shell-room { padding: var(--at-s5) var(--at-gutter) var(--at-gutter); }
  .at-shell-grid { gap: var(--at-s6) var(--at-s5); }
  .at-shell-sheetbtn { display: inline-flex; }
  .at-shell-roomsmenu { flex: 1 1 auto; width: auto; }
  /* The room keeps clear of the sheet: anti-shell.js sets --at-sheet-now to
     whichever height the sheet stands at, and the room's floor follows. */
  .at-shell-room { padding-bottom: calc(var(--at-sheet-now, 0px) + var(--at-gutter)); }
  .at-shell-edit {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    height: var(--at-sheet-h);
    padding: 0 var(--at-gutter) var(--at-gutter);
    /* The same region it is on a desktop, in the same fill: the panel, one
       step off the paper (under it by day, over it by night), so the sheet
       reads as the tray drawn over the room rather than as more room. It used to be paper plus a hairline,
       which on a phone made the editor indistinguishable from the page
       behind it (Adi, 2026-09-05). The fill is the edge, as it is beside the
       room on a desktop; no stroke, no radius. */
    background: var(--at-panel);
    transition: height var(--at-t3) var(--at-settle);
  }
  /* Two contracts share this element. Without anti-shell.js the sheet is the
     old drawer: off screen until a bar button opens it. With the script it is
     persistent and stands at one of two heights, the handle's or its own. */
  .at-shell-edit:not([data-at-sheet]) {
    transform: translateY(100%);
    visibility: hidden;
    transition: transform var(--at-t3) var(--at-settle), visibility 0s linear var(--at-t3);
  }
  .at-shell-edit[data-at-open='true'] {
    transform: none;
    visibility: visible;
    transition: transform var(--at-t3) var(--at-settle), visibility 0s;
  }
  .at-shell-edit[data-at-sheet='closed'] { height: var(--at-sheet-handle); overflow: hidden; }
  /* The handle: one label row at the sheet's top edge, sticky so it stays
     while the contents scroll under it, spanning the sheet's full width from
     inside its padding. It is the label idiom, not a button shell: a row you
     read, that also answers a tap. The caret is the menu's own, turned when
     the sheet is open. */
  .at-shell-sheet-handle {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 1;
    align-items: center;
    justify-content: space-between;
    width: calc(100% + 2 * var(--at-gutter));
    height: var(--at-sheet-handle);
    margin: 0 calc(-1 * var(--at-gutter)) var(--at-s3);
    padding: 0 var(--at-gutter);
    background: var(--at-panel);
    border: 0;
    border-radius: 0;
    font-family: var(--at-mono);
    font-size: var(--at-fs-label);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--at-ink);
    cursor: pointer;
    text-align: left;
  }
  .at-shell-sheet-handle::after {
    content: '';
    flex: none;
    width: 10px;
    height: 6px;
    background: var(--at-caret) no-repeat center;
    transform: rotate(180deg);
    transition: transform var(--at-t2) var(--at-settle);
  }
  .at-shell-sheet-handle[aria-expanded='true']::after { transform: none; }
  .at-shell-sheet-handle:focus-visible { outline: none; color: var(--at-accent-ink); }
}

/* ---------- micrographics ---------- */

.at-micro {
  font-size: var(--at-fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--at-ink-3);
}
.at-micro b { font-weight: 400; color: var(--at-ink); }

/* key: value pair with the > marker, the status-row idiom */
.at-kv { display: inline-flex; align-items: baseline; gap: var(--at-s2); }
.at-kv .at-kv-key { color: var(--at-ink-3); }
.at-kv .at-kv-key::after { content: ':'; }
.at-kv .at-kv-mark { color: var(--at-ink-3); }

/* [ tag ] as a passive label */
.at-tag { white-space: nowrap; }
.at-tag::before { content: '[ '; color: var(--at-ink-3); }
.at-tag::after { content: ' ]'; color: var(--at-ink-3); }

/* numbered rows, the annotated-listing idiom */
.at-lineno { counter-reset: at-line; }
.at-lineno > * { display: flex; gap: var(--at-s3); }
.at-lineno > *::before {
  counter-increment: at-line;
  content: counter(at-line, decimal-leading-zero);
  color: var(--at-ink-3);
  font-size: var(--at-fs-micro);
  letter-spacing: 0.08em;
  padding-top: 2px;
  flex: none;
}

/* ---------- layout helpers ---------- */

.at-row { display: flex; align-items: center; gap: var(--at-s3); flex-wrap: wrap; }
.at-stack { display: flex; flex-direction: column; gap: var(--at-s4); }

/* There is no rule component. `at-rule` and `at-rule--dashed` were here and
   were never once used across five surfaces, which is what a system looks like
   when it disagrees with itself: space separates content here, never a line,
   and a divider primitive sitting in the sheet is an invitation to break that.
   The two jobs a line legitimately has are already taken: the dotted leader
   (at-leaders-fill) carries the eye from a name to its value, and at-marks
   draws the print-proof corners. Removed 2026-07-25 with the pill's seam.
   Section index, the numbered spec sheet. Its content column is minmax(0, 1fr)
   for the reason at-field's is: a bare 1fr cannot shrink under its content, so
   one wide control in one section put a horizontal scrollbar on the page. */
.at-section { display: grid; grid-template-columns: 128px minmax(0, 1fr); gap: var(--at-s5); align-items: start; }

/* ---------- motion ---------- */

/* Staged entrance, the site's choreography: elements start hidden and are
   revealed in sequence (JS toggles .at-entered with per-step delays). Each
   reveal settles, none bounce. */
.at-enter { opacity: 0; transform: translateY(7px); }
.at-enter.at-entered {
  opacity: 1;
  transform: none;
  transition: opacity var(--at-t4) var(--at-settle), transform var(--at-t4) var(--at-settle);
}

/* Precise icon motion: the corner-arrow nudges out along its diagonal on hover,
   the reel keeps turning while a drone sounds (see .at-spin). Icons transition
   through their states, never snap. */
.at-iconbtn .at-icon,
.at-group .at-icon { transition: transform var(--at-t2) var(--at-settle); }
.at-iconbtn--link:hover .at-icon { transform: translate(2px, -2px); }
.at-iconbtn:active .at-icon { transform: scale(0.9); }
/* An icon that swaps glyphs lands with a settle, not a snap. */
@keyframes at-punch { from { transform: scale(0.62); } to { transform: none; } }
.at-punch { animation: at-punch var(--at-t3) var(--at-settle); }

.at-section > .at-index {
  font-size: var(--at-fs-label);
  letter-spacing: var(--at-track);
  color: var(--at-ink-3);
  padding-top: 2px;
  white-space: nowrap;
}

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