{"_id":"@amplib/ui","name":"@amplib/ui","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@amplib/ui","description":"A hypertoken theme for dense tool interfaces. Stark black and white, monospaced, zero radius — emphasis by polarity rather than hue.","version":"0.1.0","style":"ui.css","exports":{".":"./ui.css","./ui.css":"./ui.css","./docs.css":"./docs.css","./gallery.css":"./gallery.css","./package.json":"./package.json"},"sideEffects":["*.css"],"publishConfig":{"access":"public"},"homepage":"https://github.com/another-machine/public-library/tree/main/packages/amplib-ui","repository":{"type":"git","url":"git+https://github.com/another-machine/public-library.git","directory":"packages/amplib-ui"},"keywords":["css","design-system","monospace","design-tokens","dark-theme"],"author":{"name":"Another Machine"},"license":"Apache-2.0","type":"module","_id":"@amplib/ui@0.1.0","gitHead":"318b53b4ed068f3ccb2f298347e7b057f0bf204b","bugs":{"url":"https://github.com/another-machine/public-library/issues"},"_nodeVersion":"20.11.0","_npmVersion":"10.2.4","dist":{"integrity":"sha512-rtmaVv/KqDP4dQ9HSZF3lgHNGwQqB3xLyMn3L0+zzEKngIOeevGSyUZrXt4UFBdwyQMCE4cJTTEgNOz1CD5EeQ==","shasum":"349c4a887868c42df0cb983d2d7df370ff8e1ae9","tarball":"https://registry.npmjs.org/@amplib/ui/-/ui-0.1.0.tgz","fileCount":6,"unpackedSize":151638,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID1lSCl39k2mZcSxHQi7giY5WkbXSu4QOysrTX0mw9iJAiBh4+xJ+s0EC5ICxGZoiOLckj3WhEj6vGswySQuVtPHDg=="}]},"_npmUser":{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"},"directories":{},"maintainers":[{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ui_0.1.0_1785588926016_0.995580412287012"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-01T12:55:25.742Z","0.1.0":"2026-08-01T12:55:26.155Z","modified":"2026-08-01T12:55:26.362Z"},"maintainers":[{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"}],"description":"A hypertoken theme for dense tool interfaces. Stark black and white, monospaced, zero radius — emphasis by polarity rather than hue.","homepage":"https://github.com/another-machine/public-library/tree/main/packages/amplib-ui","keywords":["css","design-system","monospace","design-tokens","dark-theme"],"repository":{"type":"git","url":"git+https://github.com/another-machine/public-library.git","directory":"packages/amplib-ui"},"author":{"name":"Another Machine"},"bugs":{"url":"https://github.com/another-machine/public-library/issues"},"license":"Apache-2.0","readme":"# @amplib/ui\n\nA hypertoken theme for dense tool interfaces. It grew out of the stegassette\nencoder, player and jobs queue, and carries nothing specific to them — any\ntool that wants the same language can use it.\n\nStark black and white. Monospaced. Zero radius. No colour, no shadows, no\ngradients except one hatch texture. Emphasis is made with **polarity** (ink on\npaper / paper on ink), the weight of a hairline, and letter-spacing.\n\n```\nui.css       the tool language — tokens, hypertokens, base, components\ngallery.css  the exhibit language — see \"The gallery\" below\ndocs.css     the reference language — see \"The reference\" below\ndemo.html           kitchen sink; open it directly, no server needed\ngallery-demo.html   the same, for the gallery; also needs no server\nHYPERTOKENS.md      the portable guidance this system was built from\nREADME.md           this file\n```\n\nThree languages, one page each. Load exactly one: the tool file and the\nreference file define the same names at different scales, and the gallery is\na different vocabulary entirely.\n\nNo build step, no dependencies, no JavaScript.\n\n## Install\n\n```sh\nnpm i @amplib/ui\n```\n\n```css\n@import \"@amplib/ui\";            /* the tool language */\n@import \"@amplib/ui/docs.css\";   /* or the reference language */\n@import \"@amplib/ui/gallery.css\";\n```\n\n**Parcel needs its own scheme and the filename** — it reads a bare specifier in\nCSS as a relative path, and it does not consult `exports`:\n\n```css\n@import \"npm:@amplib/ui/ui.css\";\n```\n\nWith no build step at all, copy the file out of `node_modules` and link it —\nthere is nothing to compile:\n\n```html\n<link rel=\"stylesheet\" href=\"ui.css\" />\n```\n\n---\n\n## Modes\n\nTwo orthogonal modes, both plain attributes. Set them on `<html>` or `<body>`,\nor on any subtree.\n\n| attribute                 | effect                                         |\n| ------------------------- | ---------------------------------------------- |\n| _(none)_                  | dark — paper `#000`, ink `#fff`                |\n| `data-theme=\"light\"`      | light — paper `#fff`, ink `#000`               |\n| `data-theme=\"auto\"`       | follows the system preference, no JavaScript   |\n| `data-density=\"compact\"`  | 11px tool density: smaller type, tighter boxes |\n| `data-density=\"default\"`  | 12px, roomier — the default                    |\n\n```html\n<body data-density=\"compact\">\n  …\n</body>\n```\n\n`color-scheme` is set for you, so native scrollbars, date pickers, select\npopups and the caret follow the theme.\n\n### Inverting a region\n\n`.ht-surface-invert` crosses the ink/paper pair for its subtree and re-derives\nevery value from the crossed pair. Borders, links, inputs and buttons inside it\nstay in system automatically.\n\n```html\n<section class=\"panel ht-surface-invert\">…</section>\n```\n\nInverting inside an inverted region does not re-flip — in a two-value system\nthat is a smell, not a feature.\n\n---\n\n## Layers\n\n```css\n@layer amplib.reset, amplib.tokens, amplib.base, amplib.hypertokens,\n  amplib.components, amplib.utilities;\n```\n\nA later layer wins outright, whatever the specificity — so `base` sits before\n`hypertokens`, and `.ht-type-display` on an `<h1>` beats the element default.\n\nAnything you write outside a layer wins over all of it, so app CSS never needs\n`!important` or specificity games. To override _inside_ the system's order,\ndeclare your own layer after them:\n\n```css\n@layer amplib.app {\n  .waveform {\n    block-size: 5rem;\n  }\n}\n```\n\n---\n\n## 1. Tokens — what value is used\n\nOnly two literals exist: `--ht-black` and `--ht-white`. Everything else is a\n`color-mix` of the current pair, which is why a polarity flip recomputes the\nentire palette.\n\n```css\n--ht-fg-base / --ht-bg-base   theme pair, never swapped\n--ht-ink / --ht-paper         current polarity\n--ht-ink-dim                  secondary text        (82% toward ink)\n--ht-ink-faint                tertiary text         (58%)\n--ht-ink-ghost                lines and texture ONLY — never type (25%)\n--ht-line / --ht-line-strong  hairline, and its hover/active weight\n--ht-raised / --ht-sunken     button fill, input well\n```\n\nBoth text weights clear 5:1 against paper in either polarity. `--ht-ink-ghost`\nis decorative; if you set type in it, it will fail contrast.\n\nThree text sizes, deliberately — no ramp to pick from. Anything that isn't\nsecondary or the page title is `--ht-text-ui`. Hierarchy below that comes from\nink weight, tracking and case, which is what a monospaced two-value system\nactually reads; a 1px size step just looks like a mistake.\n\nThe one exception is not a fourth size but a floor: `<input>`, `<textarea>` and\n`<select>` resolve to `max(--ht-text-ui, --ht-text-control-floor)`, and that\nfloor becomes 16px under `(pointer: coarse)`. iOS Safari zooms the page when a\nfield computing under 16px takes focus and never zooms back out, and no viewport\nkey declines it. Controls that never take a caret — buttons, checkboxes, radios,\nranges, colour swatches — stay at the UI size on every pointer.\n\n```css\n--ht-font-mono                the only family\n--ht-text-small               secondary text        10px  (10 in compact)\n--ht-text-ui                  everything else       12px  (11 in compact)\n--ht-text-display             the one page title    24px  (18 in compact)\n--ht-text-control-floor       text-entry floor       0px  (16 on a coarse pointer)\n--ht-leading-flat/-ui/-prose  1 · 1.5 · 1.7\n--ht-track-none/-wide         0 · 0.14em\n\n--ht-space-1 … --ht-space-9   2 · 4 · 6 · 8 · 12 · 16 · 24 · 32 · 48 px\n--ht-gap                      inline rhythm   (row gaps)\n--ht-flow                     block rhythm    (stack gaps)\n--ht-flow-section             between document sections\n--ht-pad-panel / --ht-pad-control-inline / --ht-pad-control-block\n\n--ht-hairline: 1px            the only border width\n--ht-radius: 0                the only radius; do not raise it\n--ht-control-size(-sm)        control height\n--ht-page-max / --ht-measure / --ht-sidebar / --ht-grid-min\n--ht-code-max                 scroll cap on a code block\n--ht-anchor-offset            in-page anchor clearance under a sticky masthead\n--ht-duration / --ht-ease     90ms linear\n```\n\nLayout hypertokens read `--ht-gap` and `--ht-flow`, so retuning a whole region\nis one declaration:\n\n```html\n<div class=\"ht-arrange-stack\" style=\"--ht-flow: var(--ht-space-2)\">…</div>\n```\n\n---\n\n## 2. Hypertokens — what coordinated decision is applied\n\nGrouped, reusable presentation decisions. Compose them on plain HTML; no\ncomponent required.\n\n**Typography** — `.ht-type-ui` `.ht-type-small` `.ht-type-eyebrow`\n`.ht-type-display` `.ht-type-prose` `.ht-type-code`\n\n`.ht-type-small` covers every kind of secondary text — labels, captions, byte\ncounts, hints. Pair it with `.u-dim` or `.u-faint` when a line needs to sit\nabove or below its neighbours; that's the whole hierarchy mechanism.\n`.ht-type-eyebrow` is the same size, differing only in case and tracking. That\nuppercase is why an eyebrow cannot label a region with an identifier —\n`loadImageFromImageUrl` becomes `LOADIMAGEFROMIMAGEURL`, and camel case is the\nonly thing that made it readable. Set symbol names in `.ht-type-code`, which\nkeeps their case; the eyebrow is for words.\n\n**Arrangement** — `.ht-arrange-stack` `.ht-arrange-row` `.ht-arrange-split`\n`.ht-arrange-inline` `.ht-arrange-grid` `.ht-arrange-sidebar`\n`.ht-arrange-page` `.ht-arrange-layer`\n\n**Shape** — `.ht-shape-control` `.ht-shape-control-square` `.ht-shape-panel`\n`.ht-shape-flush` `.ht-shape-divide`\n\n**Surface** — `.ht-surface-paper` `.ht-surface-raised` `.ht-surface-sunken`\n`.ht-surface-invert` `.ht-surface-outline` `.ht-surface-dashed`\n`.ht-surface-hatch`\n\n**Media** — `.ht-media-raw` `.ht-media-square` `.ht-media-flush`\n\n**Participation** — `.ht-participation-action` `.ht-participation-input`\n`.ht-participation-focus` `.ht-participation-scroll`\n\n**Time** — `.ht-time-ui` (colour/border only, 90ms, off under reduced motion)\n\n`.ht-media-raw` is not decorative: it sets `image-rendering: pixelated`, and\nencoded imagery must never be smoothed — an interpolated pixel misreports the\npayload. When that is the only thing you mean, and the element is already sized\nand framed, reach for the `.u-pixelated` utility instead; the hypertoken is a\nwhole framing decision that happens to include it.\n\n`image-rendering` inherits, which is the utility's real use: put `.u-pixelated`\non a `figure` and it covers a canvas a script appends into it later. A class on\nthe element itself cannot, because nothing gives an injected canvas a class —\nand a smoothed one is not a cosmetic bug, it is the payload rendered wrong.\n\n`.ht-arrange-layer` stacks children in one grid cell (reconstruction under\nencoded overlay, waveform under playhead) so the tallest still sizes the box.\n`.ht-media-flush` opts a canvas or image out of the base framing — no border,\nno surface — for imagery that is pure subject matter, like an overlay canvas or\na reveal.\n\nThere is no effects category. This system has no shadows, blurs or overlays;\n`.ht-surface-hatch` is its only texture, for inert or unavailable regions.\n\n```html\n<a\n  class=\"ht-arrange-inline ht-type-ui ht-shape-control\n         ht-participation-action ht-time-ui\"\n  href=\"/decode\"\n  >decode ↗</a\n>\n```\n\n`.ht-arrange-sidebar` is the main-column-plus-rail shell — the stegassette\nencoder's layout: first child is the main column, last child is the rail, and\nit wraps to one column when the container is narrow.\n\n```html\n<div class=\"ht-arrange-sidebar\">\n  <div class=\"ht-arrange-stack\">…main…</div>\n  <aside class=\"ht-arrange-stack\">…settings…</aside>\n</div>\n```\n\n---\n\n## 3. Base — plain HTML is already in system\n\nEvery native control is styled from the same decisions, so markup with no\nclasses at all looks correct: headings, links, `hr`, lists, `table`, `details`,\n`dialog`, `pre`/`code`/`kbd`, `figure`/`figcaption`, `canvas`, `audio`,\n`video`, `progress`, and the full form set — text inputs (a bare `<input>`\nwith no `type` counts as one), `select` (with a redrawn square caret),\n`textarea`, `range`, `color`, `file`, and square `checkbox`/`radio`.\n\nA checked checkbox is a solid block; a checked radio is a block inside a ring.\nNo glyphs, no icon font, nothing to recolour.\n\nHeadings come in two levels, not six: `h1`/`h2` at display size, `h3`–`h6` at\neyebrow. Inline `code` (outside `<pre>`) gets a hairline box and keeps its\nsurrounding size — in an all-monospaced system it has nothing else to set it\napart. Anything with an `id` gets `scroll-margin-block-start`, so in-page\nanchors clear a sticky masthead; set `--ht-anchor-offset` to its height.\n\nNative `audio`/`video` controls live in shadow DOM: their radius and internal\nchrome cannot be restyled, only the box around them. They get full width, a\nhairline frame and the theme's `color-scheme` — that is the ceiling, and a\nplayer pill will still look rounder than everything else on the page.\n\n`aria-selected=\"true\"` and `aria-pressed=\"true\"` invert a button. State lives in\nARIA, not in a class, so the styling and the accessibility tree cannot drift.\n\n---\n\n## 4. Components — recipes that earned a name\n\n| recipe                                                    | for                                            |\n| --------------------------------------------------------- | ---------------------------------------------- |\n| `.app`                                                     | centred page column                            |\n| `.panel` `.panel__header/__body/__footer`                  | bordered region with header and footer bars     |\n| `.field` `.field--check` `.field__hint` `.fieldset-stack`  | label + control rows (the settings rail)        |\n| `.toolbar`                                                 | action row with a rule under it                 |\n| `.tabs`                                                    | capture-mode bar; selection is polarity         |\n| `.list` `.list__item/__index/__name/__meta`                | jobs queue, chunk list, entries                 |\n| `.drop` `.drop__label` `.drop__sub`                        | file target                                     |\n| `.badge`                                                   | boxed metadata token                            |\n| `.status`                                                  | one line of machine output                      |\n| `.meter` `.meter__fill`                                    | capacity / payload fit                          |\n| `.frame` `.frame__caption`                                 | canvas or preview with a caption                |\n| `.masthead`                                                | sticky page banner with a hairline under it     |\n| `.doc`                                                     | prose page: section rhythm + measure            |\n| `.code`                                                    | scrollable code block with colourless emphasis  |\n\nStates are data attributes and ARIA, never extra classes:\n\n```html\n<div class=\"drop\" data-ready>…</div>\n<!-- transient data-state: empty | over · persistent boolean: data-ready.\n     Two attributes because a drag can pass over a drop that is already\n     loaded — the states coexist. -->\n<p class=\"status\" data-state=\"error\">payload exceeds capacity</p>\n<!-- busy | done | error -->\n<span class=\"badge\" data-emphasis=\"strong\">on every chunk</span>\n<!-- strong | quiet -->\n<div class=\"meter\" style=\"--ht-meter-value: 71%\"><span class=\"meter__fill\"></span></div>\n<section class=\"panel\" data-flush>…</section>\n<!-- body padding off -->\n```\n\nA `.drop` spans its container; put drops in a `.ht-arrange-row` and they share\nthe line equally instead.\n\nInside `.code`, emphasis is made without colour: body text sits at\n`--ht-ink-dim`, a value bound to a live control is full ink and underlined, and\na region written at runtime is set off by a gutter rule. An empty output slot\nhides itself until the example fills it.\n\n```html\n<pre class=\"code\">\nStegassette.encode({ traversal: \"<span data-value=\"traversal\">serpentine</span>\" });\n<span data-output=\"decode\"></span></pre>\n```\n\nA settings row, end to end:\n\n```html\n<div class=\"field\">\n  <label for=\"trav\">traversal</label>\n  <select id=\"trav\">\n    <option>serpentine</option>\n  </select>\n  <span class=\"field__hint\">order data pixels are visited</span>\n</div>\n```\n\nOverride the label column locally with `--ht-field-label`, and the list height\nwith `--ht-list-max`.\n\n---\n\n## 5. Utilities\n\n`.u-grow` `.u-fixed` `.u-full` `.u-inline` `.u-truncate` `.u-nowrap` `.u-dim`\n`.u-faint` `.u-end` `.u-pixelated` `.u-sr-only`\n\n`.u-inline` is the escape from full-width defaults: `progress`, `range` and\n`.status` fill their container on their own line by design, and this lets one\nsit inline in a transport row at its natural size instead.\n\nThat is the whole set. If you find yourself wanting a sixth spacing utility,\nthe answer is a hypertoken or a recipe, not more utilities.\n\n---\n\n## Rules of the system\n\n1. **No colour.** Not for success, not for failure. Failure is full-contrast\n   type with a `!!` prefix, so it survives greyscale, forced colours and print.\n2. **No radius.** `--ht-radius` is `0` and every control — including range\n   thumbs, checkboxes and radios — respects it.\n3. **One family, one border width.** `--ht-font-mono`, `--ht-hairline`.\n4. **Selection is inversion.** Selected, pressed and current all read as\n   paper-on-ink. Nothing else may.\n5. **Semantics first.** A link navigates with `<a href>`, an action is a\n   `<button>`. Styling never substitutes for the right element.\n\n## Accessibility\n\n- One focus treatment system-wide: `2px solid var(--ht-ink)` at a hairline\n  offset, applied on `:focus-visible` only.\n- `prefers-contrast: more` collapses the grey ramp toward full ink.\n- `prefers-reduced-motion: reduce` disables transitions globally.\n- `forced-colors: active` hands surfaces to the system palette and keeps the\n  hairlines, which are the entire structure of this theme.\n- `@media print` forces black-on-white and unclips scrolling lists.\n- Contrast: `--ht-ink-dim` and `--ht-ink-faint` clear 5:1 on paper in both\n  polarities; `--ht-ink-ghost` is decorative only.\n\n## Tradeoffs, stated plainly\n\n- **Recipes restate their hypertokens.** Plain CSS cannot make one class\n  inherit declaration blocks from others, and this library has no build step,\n  so `.panel` and friends duplicate the declarations of the hypertokens named\n  in a comment above each recipe. Change a hypertoken, change its recipes. If\n  that pairing ever gets expensive, that is the signal to add a compiler — not\n  before.\n- **`color-mix()` is required.** Baseline in Chrome/Edge 111+, Safari 16.2+,\n  Firefox 113+. Without it the derived greys fall back to nothing and the\n  theme loses its ramp; there is no polyfill and none is planned. `:has()`\n  (same era) is used once, to mirror a theme set on `<body>` up to the page\n  canvas — without it only the overscroll area is wrong.\n- **Density is a mode, not a variant.** It only retunes shared values. If a\n  screen needs its own geometry, override the tokens on that screen.\n- **The scale stops where the tools stop.** No cards, modal system, tooltips,\n  menus, or icon set — those are unearned here. Add one when a second screen\n  needs it, and add it as a recipe composed from existing hypertokens.\n\n## The gallery\n\n`gallery.css` is a second, standalone stylesheet for the pages where\nfinished work hangs — stega.now/live and stega.now/geese are its origin. It is\ndeliberately the opposite pole from the tool language: serif and letter-spaced\nwhere the tools are monospaced, dark rooms where the tools are paper and ink,\nbrightness where the tools use polarity. The two files do not depend on each\nother and are not meant to be mixed on one page.\n\nIts vocabulary is the exhibit:\n\n| recipe       | for                                                          |\n| ------------ | ------------------------------------------------------------ |\n| `.gallery`   | a deck of rooms, one per viewport, snap-scrolled — vertical, or a promenade with `data-axis=\"x\"` |\n| `.room`      | one full-viewport stop; `--gal-backdrop: url(…)` hangs a veiled enlargement behind the work |\n| `.placard`   | an intro or colophon — serif text on solid black             |\n| `.work`      | the chromeless toggle button a piece hangs in; state is `aria-pressed` |\n| `.rail`      | a snap-scrolling strip of works sharing one wall             |\n| `.gal-ghost` | withdrawn from view but still holding its place              |\n| `.gal-glow`  | the halo a work casts on the room                            |\n\nAttention is brightness: a work rests at `--gal-dim` and comes to full light\nunder hover or `aria-pressed=\"true\"`; a rail rests deeper so the lit work\nreads as current. Encoded pixels are the subject, so imagery is never\nsmoothed. The works themselves are in colour — the no-colour rule is about\nthe room, not the art.\n\nWhile a piece is decoding, mark `data-loading` and its stand-in breathes. Put\nit on the work itself — the precise case, and the only one that reads right\nfor a single work inside a rail — or on the room, when the room holds one\nstand-in of its own. Either way a rail of other works keeps still.\n\n`--gal-backdrop` needs an *absolute* URL (`img.src`, not the `src` attribute):\na relative `url()` inside a custom property resolves against the stylesheet\nthat consumes it, not the page that set it.\n\n`gallery-demo.html` is the kitchen sink for all of this — both axes, a room\nwith its own backdrop and one sharing the page's, a rail, and the decode\npulse. It paints every work with a canvas at load, so it needs no assets and\nopens straight off disk.\n\n## The reference\n\n`docs.css` is the third file: the same language as the tool, spoken at\nreading distance. It exists because the amplib package documentation is not a\ntool screen — it is read rather than operated, and the examples on it are live\ntool UI that has to keep working.\n\nThat last point is why it is a fork and not a new vocabulary. Every class name\nis identical to the tool file's, so a `.field`, a `.panel` or a `.code` block\ncan be pasted straight into its own documentation and simply comes out bigger.\nThe gallery could afford new nouns because a room shares nothing with an\nencoder; a reference page shares almost everything.\n\n| | tool | reference |\n| ---------- | -------------- | ---------------- |\n| secondary  | 10px           | 12px             |\n| body       | 12px           | 14px             |\n| code       | 10px (chrome)  | 14px (the subject) |\n| section    | —              | 20px             |\n| page title | 24px, upper    | 32px, case kept  |\n| density    | `compact` mode | none             |\n\nFour rungs instead of three, because a document has page, section and step\nwhere a tool has a banner and then flat rows. Nothing is uppercased but the\neyebrow: what a reference names is usually an identifier, and case is the only\nthing that makes `loadImageFromImageUrl` or `@amplib/cosmos` readable — the one\nrule this file breaks with the tool language, and the reason it is a separate\nfile rather than a set of overrides.\n\nThree recipes exist only here:\n\n| recipe       | for                                                        |\n| ------------ | ---------------------------------------------------------- |\n| `.doc`       | the reference page; each `> section` is one entry, with a rule above it |\n| `.signature` | the type contract an entry documents — unframed dim code   |\n| `.entry`     | a name and the line about it, on an index where many share one section |\n\n`.entry` exists because the two page shapes disagree about what a section is.\nOn a reference page a section _is_ an entry, and needs no wrapper. On an index,\none section holds nineteen of them, and the section's own rhythm puts a\ndescription exactly as far from its name as from the next name — belonging to\nneither. `.entry` is the tighter inner group.\n\n`.signature` is the one place code is not `pre-wrap`: a signature is a single\nlogical line that should wrap to the column, and authoring it across several\nsource lines must not put that indentation on the page.\n\n**The cost, stated plainly.** This is a fork, so the two files drift. A fix to\nthe base layer or a new recipe has to be made twice, and nothing enforces that.\nIt is the same trade this library already takes with recipes restating their\nhypertokens: duplication is cheaper than a build step until it isn't. The\nsignal to reconsider is a third consumer, not a second one.\n\n## Extending it\n\nRead [HYPERTOKENS.md](HYPERTOKENS.md) first — it is the reasoning this system\nwas built from. Then, in order:\n\n1. Can the change be a token override in a local scope? Do that.\n2. Does a coordinated group of declarations now appear three times? Name it as\n   a `.ht-*` hypertoken.\n3. Does that whole composition repeat with the same meaning? Give it a recipe\n   in `amplib.components`, and note the hypertokens it restates.\n4. Verify in both polarities, both densities, at narrow widths, and with\n   keyboard focus visible. `demo.html` exists for exactly this.\n","readmeFilename":"README.md","_rev":"1-edf1b980caa99a3dede8be962d375609"}