vintage-frames 0.12.8 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/SPEC.md CHANGED
@@ -127,14 +127,14 @@ Every length in this doc is a **system pixel** value; components multiply it by
127
127
 
128
128
  **Tiled fills.** A repeating `background-image` is the one place where "one system pixel is a whole number of device pixels" is not enough on its own. That contract holds — but the CSS *length* expressing it need not be one the engine can store: Chromium lays out in 1/64 CSS px (Gecko in 1/60), and at `--vf-scale` 4/3 a 2-system-px tile is 2.6667 CSS px, held as 2.65625. A single edge survives that, because paint snaps each box to the device grid on its own — which is why every border, stepped corner and magnified icon rasterizes exactly at 4/3. A tiled fill does not: it is ONE snapped box holding N *unsnapped* repeats, each placed at `k × tileSize`, so the error compounds until the tile boundary has walked a whole device pixel. Unfixed, 75% of the desktop dither rasterizes to mid-gray at dpr 1.5.
129
129
 
130
- The span construction (`vfTileSize`, `tileImage`, `tileSpan`) makes the stored length exact for every scale the *density* ladder derives: a fill authored as a **motif** tiles at `lcm(motif, 15)` system px — a whole number of motifs, so the art is unchanged, and holdable because a scale `p/q` needs `q` to divide `64 × span`, the 64 absorbs every power of two, and every derived scale (1, 3/2, 4/3, 8/5, 6/5, 5/4) has an odd part of 1, 3 or 5. But **zoom mints scales the lattice cannot hold** — 20/17 at Safari's 85%, 30/23 at its 115%, arbitrary primes with no finite cover (ZOOM-TILE-DRIFT.md) — so the four convertible surfaces no longer repeat in CSS at all (`src/tile-grid.ts`, TILE-GRID-PLAN.md):
130
+ The kit's tiled surfaces — the desktop and container patterns, the windoid dots, the swatch checker, the barber stripes and the scroll trough — answer it with one tile (`src/tile-grid.ts`):
131
131
 
132
- - **Kit art renders as one whole-surface raster** — the motif, stated once as rect data (`TileRect[]`), encoded at one image px per system px (`tileRaster`) and magnified nearest-neighbor (`image-rendering: pixelated`, the `vf-img` mechanism). Nearest-neighbor can only produce source colors and one box has no interior seams, so the fill is 1-bit at every scale, holdable or not. (Raster, not SVG, because Chromium rasterizes an SVG image at the box's *stored* fractional size and ignores `image-rendering` for SVG — measured; the crisp path does not exist.)
133
- - **A consumer pattern token renders as a flat grid of placed tiles** at the token's documented 30/60-system-px tile geometry: each tile positioned by one single-multiplication `calc()` quantized once, every box paint-snapped independently — never laid out with CSS grid/flex/flow, whose summed track sizes would re-import the accumulation. The token still overrides the whole **tile**, not the motif; a token swapped at runtime without touching the component needs a `requestUpdate()`.
132
+ - **Kit art is a CSS repeat of one tile 120 system px square, drawn at the display's resolution** (`repeatTile`, kept current on a box by `RepeatTileController`, sized by `vfRepeatTileSize`). Each system px of the motif is an n × n block of image px, n = `--vf-scale × trueDpr`, so the engine copies the tile to the device grid 1:1 and has no filtering to choose — Safari smooths a repeated image it has to magnify, whatever `image-rendering` says. 120 is a whole number of every motif the kit draws (2, 4, 8 and 12), and 120n device px is a whole number of layout px in every engine at every density and zoom (40n at 3×, 60n at 2×), so each repeat lands exactly. The size is large on purpose: WebKit on a 3× iPhone now and then draws a small tile one device px narrow and resamples it, even at an exact length; tiles of 192 device px and up never showed it (TILE-REPEAT-PLAN.md, in git history at commit 5bc45c2). The tile depends only on the art and n: it is encoded once per pair for the whole page, drawn again when zoom or the display changes n, and never on a resize. Each surface keeps `image-rendering: pixelated`, which covers the frame between a zoom and the new tile. (Raster, not SVG, because Chromium rasterizes an SVG image at the box's *stored* fractional size and ignores `image-rendering` for SVG.)
133
+ - **A consumer pattern token renders as a flat grid of placed tiles** at the token's documented 30/60-system-px tile geometry: each tile positioned by one single-multiplication `calc()` quantized once, every box paint-snapped independently — never laid out with CSS grid/flex/flow, whose summed track sizes would re-import the accumulation. A token is any CSS image, SVG included, so it cannot be pre-scaled the way kit art is. The token still overrides the whole **tile**, not the motif; a token swapped at runtime without touching the component needs a `requestUpdate()`.
134
134
 
135
- The span construction stays load-bearing for the CSS-repeated underlays beneath the opaque kit fills and for the forced-colors mask branches (no mask pipeline rasterizes exactly at a zoom-minted scale, so forced-colors-plus-zoom remains the one accepted residual). The scroll trough — once the one surface that could not convert, a `::-webkit-scrollbar` pseudo hosting no children — is ordinary DOM now (§4 `vfScrollRail`) and renders through `tileRaster` like the desktop dither: 1-bit at every scale, zoom-minted ones included. `npm run verify:tile` asserts zero gray on the four converted surfaces at eight densities — the ladder plus 1.7 and 2.3, the emulated stand-ins for Safari's broken rungs; `npm run verify:scrollbars` asserts the same on the trough.
135
+ The span construction (`vfTileSize`, `tileImage`, `tileSpan`) still sizes what isn't a kit tile: a token's documented tile, and the forced-colors mask branches that repaint a surface's art in the ink token (no mask pipeline rasterizes exactly at a zoom-minted scale, so forced-colors-plus-zoom remains the one accepted residual). A fill authored as a **motif** spans `lcm(motif, 15)` system px — a whole number of motifs, and holdable at every scale the density ladder derives, because a scale `p/q` needs `q` to divide `64 × span`, the 64 absorbs every power of two, and every derived scale (1, 3/2, 4/3, 8/5, 6/5, 5/4) has an odd part of 1, 3 or 5. The scales zoom mints (20/17 at Safari's 85%, 30/23 at its 115%) no span holds. `npm run verify:tile` asserts each kit surface's tile geometry and zero gray at eight densities — the ladder plus 1.7 and 2.3, the emulated stand-ins for Safari's zoom rungs; `npm run verify:scrollbars` asserts the trough's lattice pixel by pixel. No headless engine reproduces Safari's smoothing; `tile-repeat-probe.html` is the check in real browsers (docs/DEVELOPING.md).
136
136
 
137
- **Patterns.** The 38 standard patterns (`src/patterns.ts`; the table is docs/PATTERNS.md) are 8-byte QuickDraw `Pattern`s in palette order and QuickDraw phase. `gray-50`, `gray-25` and `dots` are the desktop dither, the scroll trough and the windoid bar's motifs, and those surfaces draw them by reference — `patternMotif` on the minimal cell, so the 2×2 / 4×2 rect data and the 30/60-px token spans are the ones they always had. `vf-container pattern` and `vf-desktop pattern` take a name or sixteen hex digits and paint the pattern as the box's **own background** (`PatternFillController` + `vfPatternFill`, src/pattern-fill.ts): the same whole-surface raster, its `background-size` stated in system px — one length quantized once over the whole image — under `image-rendering: pixelated`, literal black ink on the `--vf-white` paper the recipe supplies. A background rather than a child element, so a patterned box is no stacking context (a slotted control's fixed panel is never trapped under a later sibling) and keeps `overflow: visible`; `image-rendering` inherits, so the recipe hands `auto` back to the slot. A declared axis sizes the raster exactly (plus one cell of overdraw the painting area crops); an undeclared one is measured through `TrackWidthController`. Phase is anchored at the box's own origin. Forced colors: flat Canvas, the desktop's posture. `npm run verify:pattern` asserts the library, the three motif equivalences and zero gray at the same eight densities.
137
+ **Patterns.** The 38 standard patterns (`src/patterns.ts`; the table is docs/PATTERNS.md) are 8-byte QuickDraw `Pattern`s in palette order and QuickDraw phase. `gray-50`, `gray-25` and `dots` are the desktop dither, the scroll trough and the windoid bar's motifs, and those surfaces draw them by reference — `patternMotif` on the minimal cell, so the 2×2 / 4×2 rect data and the 30/60-px token spans are the ones they always had. `vf-container pattern` and `vf-desktop pattern` take a name or sixteen hex digits and paint the pattern as the box's **own background** (`PatternFillController` + `vfPatternFill`, src/pattern-fill.ts): the pattern's motif as a kit tile (above), literal black ink on the `--vf-white` paper the recipe supplies. A background rather than a child element, so a patterned box is no stacking context (a slotted control's fixed panel is never trapped under a later sibling) and keeps `overflow: visible`; `image-rendering` inherits, so the recipe hands `auto` back to the slot. Phase is anchored at the box's padding-box origin. Forced colors: flat Canvas, the desktop's posture. `npm run verify:pattern` asserts the library, the three motif equivalences, the tile's geometry and zero gray at the same eight densities.
138
138
 
139
139
  **`--vf-cursor` (hiding the pointer for a page-drawn cursor).** A page that draws its own cursor — a JS-positioned image on the system-pixel grid, which is what `applyCursor()` (`src/cursor.ts`) sets up with the kit's embedded System 7 pointer set — needs the native pointer gone from every surface, and `html { cursor: none }` alone cannot get it there: inheritance stops wherever a shadow stylesheet states a cursor of its own, and the kit states one deliberately on its chrome — control hosts, the title-bar widgets, the number stepper, the scrollbar rails, the grow box, the editable wells (whose I-beam Firefox's UA sheet would otherwise pin), and a modal's top-layer backdrop, which no page rule reaches at all. Every one of those declarations reads this token first, so `--vf-cursor: none` on `:root` (custom properties inherit through every shadow root) covers all of them. The page's own side must be a blanket, not a root rule: the UA sheets put `cursor: default` on `<button>` and `cursor: text` on `<input>`, and an element's own declaration beats anything it would inherit — so a bare `html { cursor: none }` leaves the OS arrow alive over every native control in the light DOM. The pair that empties everything is `* { cursor: none !important }` plus the token — `applyCursor()` applies both itself, once its art has decoded; state the pair by hand only under a hand-rolled overlay. Unset, each spot keeps its classic pointer — the arrow on chrome, the I-beam in an enabled well.
140
140
 
@@ -144,8 +144,8 @@ The span construction stays load-bearing for the CSS-repeated underlays beneath
144
144
  - `vfBodyDecls` — the three declarations that put text on the Geneva body face (family, 16px, smoothing), for composing into one rule; the mirror of `vfDisplayDecls`, and the body face's single definition (`vfBase` applies it to every host; `face="body"` switches back to it).
145
145
  - `vfStaticText` — the `face` / `size` / `dim` host switches shared by `vf-label` and `vf-paragraph`. All `:host([attr])`, one specificity step above the plain `:host` rule each component sets its own default face in.
146
146
  - `vfStripes` — a `.vf-stripes` class for the racing-stripe layer. Position it absolutely inside the title bar, inset `3px 1px` (top/bottom 3px, left/right 1px) so exactly six 1px stripes show at the 18px bar height, their top and bottom edges aligned with the close box's and one system px of clear white between the stripes and the frame border — the same buffer the widgets' patch ring keeps. The paint is split by engine, each side the measured best (the full three-engine matrix is in `scripts/verify-tile.mjs`): Blink — and any engine failing both gate properties — draws a 12-unit SVG (the band's 11 rows plus an empty pad row, viewBox stretched onto a 12px-tall box, crispEdges). Twelve, not eleven, because Blink pixel-snaps painted boxes to whole CSS px and an 11-system-px box has no legal CSS height at scale 3/2; 12 divides by 2 and 3, so the box never rounds at any integer display's scale, and the SVG measured whole-rhythm, close-box-registered and zero-gray at dpr 1/1.5/2/3 — retiring the `repeating-linear-gradient`, which it strictly dominates. Gecko and WebKit (gated by `@supports (-moz-appearance: none) or (-webkit-backdrop-filter: blur(1px))`, each property parsing in its one engine only) render six placed solid rows (`chromeTitleBar` supplies the spans): both engines device-snap solid quads exactly, and each misrendered the gradient — Gecko's GPU pipeline softens a hard stop at default zoom, WebKit landed the sixth stripe a device row thin at dpr 3 and the zoom-minted scales.
147
- - `vfDots` — the windoid bar's counterpart to `vfStripes`: a `.vf-dots` layer inset `2px` top/bottom and **flush left/right** (the close-up reference runs the dots into the side borders; the `Windows/` sheet's 2px side inset is the artist's, not the bar's), a 2×2 motif carrying one black pixel at its origin (`--vf-dots-pattern` to retheme). A width-declaring window renders the exact fill into the layer (see *Tiled fills*); the layer's own CSS-repeated SVG tile is the fallback for a window with no declared width.
148
- - `vfTileSize` / `tileImage` — the span a CSS-REPEATING fill takes, and its art. Every metric in the kit is `calc(var(--vf-scale, 1) * Npx)`, and paint snaps each box to the device grid on its own — but a tiled fill is one snapped box holding N *unsnapped* repeats, each placed at `k × tileSize`, so a tile size the layout grid cannot hold drifts a fraction of a device pixel further with every repeat. Load-bearing for the underlays and the forced-colors masks; the converted surfaces (the scroll trough now among them) render through `vfTileGrid` / `tileGrid` / `tileRaster` instead. See *Tiled fills* below.
147
+ - `vfDots` — the windoid bar's counterpart to `vfStripes`: a `.vf-dots` layer inset `2px` top/bottom and **flush left/right** (the close-up reference runs the dots into the side borders; the `Windows/` sheet's 2px side inset is the artist's, not the bar's), a 2×2 motif carrying one black pixel at its origin (`--vf-dots-pattern` to retheme). The layer's background is the kit tile `vf-window` writes (see *Tiled fills*); a `--vf-dots-pattern` token renders as the placed tile grid in a window with a declared width, and as a CSS repeat on its 30-px span in one without.
148
+ - `vfTileSize` / `tileImage` — the span a CSS-REPEATING fill takes, and its art. Every metric in the kit is `calc(var(--vf-scale, 1) * Npx)`, and paint snaps each box to the device grid on its own — but a tiled fill is one snapped box holding N *unsnapped* repeats, each placed at `k × tileSize`, so a tile size the layout grid cannot hold drifts a fraction of a device pixel further with every repeat. Load-bearing for consumer tokens' documented tiles and the forced-colors masks; the kit's own art repeats a 120-system-px tile instead (`vfRepeatTileSize`, `repeatTile`). See *Tiled fills* below.
149
149
  - `vfHardShadowDecls` — the hard 1-bit drop shadow on its own, for composing into a surface that supplies its own border: `box-shadow: var(--vf-shadow-offset, 2px) var(--vf-shadow-offset, 2px) 0 0 var(--vf-black, #000)`. No blur, no spread — System 7's only depth cue. Every raised surface in the kit composes this one declaration (`vfPanel`, `vfChromeFrame`; `vf-swatch` composes it without either surface class).
150
150
  - `vfPanel` — a `.vf-panel` class for menus/popups: white bg, `border: 1px solid var(--vf-black, #000)`, `vfHardShadowDecls`.
151
151
  - `vfChromeFrame` — a `.vf-frame` class for the desktop window (`vf-window`): the same white face, 1px black border and hard offset shadow as `vfPanel`, no bevels. Skin only — the component supplies its own layout (a full-size flex column). `vf-dialog` does not use it: both of its chromes are `vfModalFrame`.
@@ -157,7 +157,7 @@ The span construction stays load-bearing for the CSS-repeated underlays beneath
157
157
  - The title's clearance for anything else in the bar is `--vf-title-inset` (default 16px); `vf-window` sets 60px so an ellipsized title can't run under its close/zoom widgets.
158
158
  - `touch-action` is deliberately NOT in the recipe: `vf-dialog`'s bar is always a drag handle, `vf-window`'s only when `[movable]`, and suppressing touch scrolling on a bar that can't be dragged would be a behavior change.
159
159
  - The markup and the four DragController pointer bindings that go with it are `chromeTitleBar()` in `src/chrome.ts` (internal — it bakes in the kit's own `part` names, so it is not part of the public toolkit). Its third argument picks the texture layer class: `'vf-stripes'` (default) or `'vf-dots'` (`vf-window variant="utility"`).
160
- - `vfScrollRail` — the System 7 scroll rail, drawn by the kit as ordinary shadow DOM (`src/scroll-rail.ts` renders the subtree and drives it; this is its skin). The native scrollbar is hidden — `scrollbar-width: none` plus a `::-webkit-scrollbar { display: none }` for older WebKit, the last scrollbar pseudo in the kit — never the native scrolling: wheel, trackpad momentum, keyboard, touch and AT scrolling stay the platform's — minus the rubber-band: the scroller sets `overscroll-behavior: none` (a bounce moves content the rail cannot express; System 7 stopped hard), which also cuts scroll chaining, so a document that scrolls restores it where it wants it through the scroller's part (`viewport`, `textarea`, `list`, `content`; the reference page does, dialogs excepted) — and `ScrollRailController` syncs the rail to `scrollTop`/`scrollLeft` while driving the classic interactions (thumb drag; trough paging by a viewport minus one line, repeating until the thumb reaches the pointer; arrow lines with auto-repeat and the hollow→solid pressed glyph). Geometry: the classic 16px cell whose outermost line is the component's own 1px frame border — the rail element is the 15 inside it, a 1px divider on the content side plus the 14px channel; 15px arrow cells whose glyphs are the 16-unit sprites windowed to their 14×14 interior; the **fixed** 16px thumb (System 7's box, never proportional; whole-system-px travel); the 25% dot-dither trough rendered as a whole-surface `tileRaster` (1-bit at every scale, zoom included) on `var(--vf-scrollbar-track)`. States key off the attributes `ScrollStateController` writes — an idle axis keeps its arrows (drawn but inert) and drops the dither and thumb, an inactive window blanks both axes to the bare white channel, arrows included, a degenerate track drops the thumb, then everything (the Control Manager's decision table). The rail subtree is `aria-hidden` and pointer-only; scrolling's keyboard/AT contract stays on the viewport. Every engine renders the same rail — no Firefox fallback — and it is pixel-asserted headless (`npm run verify:scrollbars`), which the `::-webkit-scrollbar` skin it replaced never could be.
160
+ - `vfScrollRail` — the System 7 scroll rail, drawn by the kit as ordinary shadow DOM (`src/scroll-rail.ts` renders the subtree and drives it; this is its skin). The native scrollbar is hidden — `scrollbar-width: none` plus a `::-webkit-scrollbar { display: none }` for older WebKit, the last scrollbar pseudo in the kit — never the native scrolling: wheel, trackpad momentum, keyboard, touch and AT scrolling stay the platform's — minus the rubber-band: the scroller sets `overscroll-behavior: none` (a bounce moves content the rail cannot express; System 7 stopped hard), which also cuts scroll chaining, so a document that scrolls restores it where it wants it through the scroller's part (`viewport`, `textarea`, `list`, `content`; the reference page does, dialogs excepted) — and `ScrollRailController` syncs the rail to `scrollTop`/`scrollLeft` while driving the classic interactions (thumb drag; trough paging by a viewport minus one line, repeating until the thumb reaches the pointer; arrow lines with auto-repeat and the hollow→solid pressed glyph). Geometry: the classic 16px cell whose outermost line is the component's own 1px frame border — the rail element is the 15 inside it, a 1px divider on the content side plus the 14px channel; 15px arrow cells whose glyphs are the 16-unit sprites windowed to their 14×14 interior; the **fixed** 16px thumb (System 7's box, never proportional; whole-system-px travel); the 25% dot-dither trough repeating the kit tile (*Tiled fills*; 1-bit at every scale, zoom included) on `var(--vf-scrollbar-track)`. States key off the attributes `ScrollStateController` writes — an idle axis keeps its arrows (drawn but inert) and drops the dither and thumb, an inactive window blanks both axes to the bare white channel, arrows included, a degenerate track drops the thumb, then everything (the Control Manager's decision table). The rail subtree is `aria-hidden` and pointer-only; scrolling's keyboard/AT contract stays on the viewport. Every engine renders the same rail — no Firefox fallback — and it is pixel-asserted headless (`npm run verify:scrollbars`), which the `::-webkit-scrollbar` skin it replaced never could be.
161
161
  - `vfFocusRing` / `vfFocus` / `vfFocusUnderline` — the two focus indicators. Neither is a System 7 reproduction: the machine drew no keyboard focus at all (see §1), so both are additions rendered in its vocabulary.
162
162
  - `vfFocusRing` is the dotted-outline declaration pair — `outline: var(--vf-focus-outline, 1px dotted currentColor)` plus a scaled `--vf-focus-offset` (default +2px) — interpolated into whatever selector a component focuses on; `vfFocus` wraps it as a `.vf-focus:focus-visible` class for the controls where focus and ring share one element. `currentColor`, not black, for the same reason the underline uses it: the ring must invert with the ink it sits on, or the multi-select keyboard cursor disappears riding a selected list row's black bar.
163
163
  - `vfFocusUnderline` is the alternative for a control that can carry the mark on its own face: an `::after` on that element, 1 system px tall, spanning its box, dashed 1px on / 1px off via a `repeating-linear-gradient` in `currentColor` — so it inverts to white with the label on a pressed face. The component suppresses the UA outline in the same rule set, and the underlined element needs `position: relative`. Two placements:
@@ -197,7 +197,7 @@ Every recipe also declares its size — `width`, and `height` where the window i
197
197
  Full-bleed classic desktop container.
198
198
  - **Attributes/props:** `width`, `height: number` (system px, defaults 512×342 — the compact Mac's raster) — the screen's own size, the way a WIND resource declared a window's. The host box renders at exactly `width + 2·bezel` per axis (a live `sysLength` written as host inline size on every update), always a whole number of system px. **Pure CSS sizing is not supported**: the inline size wins over any stylesheet, so the page sets the numbers — directly or via `fitWithin` — and positions the explicitly sized desktop with its own CSS, keeping any sub-system-pixel slack in the page's own layout. `bezel: number` (system px, default 0) — the black screen surround, the CRT's unlit margin between raster and case, added onto the declared screen on every side. The screen owns flow, an absolute window's containing block and the drag clip, so a dragged window crops at the raster's edge. Its four corners wear the `SCREEN_CORNER` mask (the bottom pair mirrored vertically — `steppedCornerClip`'s `edge`), painted above everything like the hardware mask — the top pair lands over a slotted menu bar's corners, so the bar needs no `rounded` of its own inside a bezeled desktop. The bezel width is written onto the host as `--vf-desktop-bezel` (self-set geometry, like `--vf-scale` — component-owned, not a theming token). `pattern: string` (default `'gray-50'`) — the desktop pattern, System 7's General Controls setting: a library name (docs/PATTERNS.md) or sixteen hex digits stating a custom 8×8; an unrecognized value warns once and keeps the dither. A `--vf-desktop-pattern` token override wins over it.
199
199
  - **Methods:** `fitWithin(maxWidth, maxHeight)` (CSS px) → sets `width`/ `height` to the largest whole-system-px raster whose host box — bezel included — fits the bound, per the current effective scale, and returns `{ width, height }`. The page's half of the sizing contract: call it on `resize` and `onScaleChange` (zoom and density moves change what a system px costs in CSS px), as a full-screen faux desktop does. `clearActive()` → deactivate the whole document tier (see *Deactivation* under Behavior). `activeWindow: HTMLElement | null` (read-only) → the active document-tier window, or `null` while the tier is deactivated or empty; utility windows are never the holder. `placementAt(clientX, clientY, child?)` → a viewport point as a placement on the screen, `{ left, top }` in whole system px on the placement lattice, measured from the raster's corner with the bezel excluded — the pair a child dropped onto the desktop is written with; with `child`, the pair to write to that element, its `origin` offset folded in (`placementIn`, src/position.ts; the anchor sits in shadow, which is why it is a method).
200
- - **Visual:** `display: block; position: relative;` — the paint lives on an inner screen surface (part `desktop`, `overflow: hidden` — the whole-system-px raster, inset by `bezel` when one is set). Screen surface = the `pattern` (the classic 50% dither, `gray-50`, by default), painted as the screen's own background by the pattern fill (*Patterns* under *Tiled fills*): black ink on the fill's opaque `--vf-white` paper, one whole-surface raster — the authentic black-on-white dither. A consumer `--vf-desktop-pattern` renders instead as a placed tile grid on the token's 30-system-px tile with no paper, so `var(--vf-desktop, #808080)` beneath shows only under a custom tile with transparent cells (or `none`). Forced colors: flat Canvas.
200
+ - **Visual:** `display: block; position: relative;` — the paint lives on an inner screen surface (part `desktop`, `overflow: hidden` — the whole-system-px raster, inset by `bezel` when one is set). Screen surface = the `pattern` (the classic 50% dither, `gray-50`, by default), painted as the screen's own background by the pattern fill (*Patterns* under *Tiled fills*): black ink on the fill's opaque `--vf-white` paper, a repeating tile — the authentic black-on-white dither. A consumer `--vf-desktop-pattern` renders instead as a placed tile grid on the token's 30-system-px tile with no paper, so `var(--vf-desktop, #808080)` beneath shows only under a custom tile with transparent cells (or `none`). Forced colors: flat Canvas.
201
201
  - **Slots:** default (menu bar, windows, anything).
202
202
  - **Behavior:** manages stacking of slotted `vf-window` children: `pointerdown` *or `focusin`* on a window brings it to front (incrementing z-index counter) and sets its `active` attribute, clearing `active` on the others — the `focusin` half is the keyboard route to activation: Tab landing anywhere in a background window (its undrawn-but-focusable widgets included) raises it. Listens via delegated pointerdown/focusin listeners + `slotchange`. Windows slotted before `vf-window` is defined are re-normalized once `customElements.whenDefined('vf-window')` settles, since the upgrade reflects each window's `active = true` default back out and upgrading a slotted node doesn't re-fire `slotchange`.
203
203
  - **DOM order follows z-order** (bottom-most first), so sequential focus order matches the visual stack and Shift+Tab mirrors Tab exactly. The sync runs in a task after a pointer gesture ends, or after a programmatic `bringToFront` — never mid-gesture (a node move would clear the pointer capture a title-bar drag or grow-box resize holds), never between a press's release and its `click` (Chromium drops a click whose mousedown node was re-inserted first, which used to cost a control in a background window its click), and never from a focus-driven raise (moving the window focus just entered would re-order the tab sequence mid-traversal); focus surviving its own window's move is restored without re-raising that window. Non-window children (a menu bar, page content) keep their positions. `npm run verify:window-a11y`.
@@ -396,7 +396,7 @@ Classic list box.
396
396
  #### `vf-scroll-area` (`VfScrollArea`, vf-scroll-area.ts)
397
397
  A container whose scrollbars look like System 7.
398
398
  - **Attributes/props:** `axis: 'vertical' | 'horizontal' | 'both'` (default `'vertical'`, reflected) — which scroll rails to reserve as permanent placeholders (see "always-a-rail" below); `corner: boolean` (default false, reflected) — reserves the bottom-right corner cell on a single-axis rail (the rail stops 15px short of the frame, the viewport spans the rest), for a grow box to land in; inert on `both`, which always has the cell; `label: string` — accessible name for the viewport (`aria-label` on the viewport, since an `aria-label` on the host cannot reach into the shadow DOM). The viewport is a **Tab stop only while its content actually overflows** — the same state `ScrollStateController` measures for the rails; a fitting scroll area used to be a focusable stop with `role: generic` and no name, a dead Tab press. Whenever it is a stop it carries a role: `role="region"` when `label` names it (a named landmark), `role="group"` when not (an unnamed region is inert, so that role is reserved for the labelled case).
399
- - **Visual:** `display: block`; the snapped wrapper carries a real 1px black frame and a grid reserving each rail as its own edge column/row — `[viewport | vertical rail]` over `[horizontal rail | corner]` — with the white inner viewport carrying no inset of its own, so slotted content and the (0,0) of placed children sit exactly 1 system px from the frame box at every scale; an inset is the content's (a `vf-stack pad`). Consumer sets width/height on host. The scrolled plane — the wrapper the slot renders into, and the positioned anchor for placed children — is `width: fit-content; min-width: 100%`: never narrower than the viewport, as wide as content that cannot wrap, so the controllers' ResizeObserver on it sees a row growing sideways the way it sees copy growing down (a block wrapper's auto width was the viewport's whatever the row did, and a row gaining a cell left the thumb and the overflow state stale until something else re-measured); copy wraps as before, and a `position: sticky; left: 0` child holds across the scroll, the plane being its containing block. `min-height: 100%` likewise: a `fixed` child (§2 Explicit placement) is a sticky box the plane contains, and a plane shorter than the child's stated `top` would clamp it to the plane's bottom edge; content that fits still fits. In the overflowing case a percentage-width or centered block child resolves against the grown plane, not the viewport. A `slotchange` re-measures; `measure()` covers a scroll range that changes with no box changing. The rails are the drawn `vfScrollRail` subtree (§4 — the classic 16px cell counting the frame line: divider, 14px channel, 15px arrow cells, the fixed 16px thumb, the `tileRaster` dot-dither trough), rendered as later siblings of the viewport and synced to its native scrolling by `ScrollRailController`; the viewport carries the `vf-scroll` class, which hides the native bar without touching the native scrolling. When both rails are reserved, the corner cell joins them, supplying the interior dividers the adjacent arrow cells leave to it; `corner` reserves the same 15×15 cell beside a single rail, its other divider continuing the rail's line across the cell — DrawGrowIcon's form, for the grow box. Every engine renders the same rail; the old `::-webkit-scrollbar` skin, its `.vf-scroll-frame` overlay contortion (WebKit quantized native scrollbar rects to whole CSS px) and the Firefox `scrollbar-color` fallback are all retired.
399
+ - **Visual:** `display: block`; the snapped wrapper carries a real 1px black frame and a grid reserving each rail as its own edge column/row — `[viewport | vertical rail]` over `[horizontal rail | corner]` — with the white inner viewport carrying no inset of its own, so slotted content and the (0,0) of placed children sit exactly 1 system px from the frame box at every scale; an inset is the content's (a `vf-stack pad`). Consumer sets width/height on host. The scrolled plane — the wrapper the slot renders into, and the positioned anchor for placed children — is `width: fit-content; min-width: 100%`: never narrower than the viewport, as wide as content that cannot wrap, so the controllers' ResizeObserver on it sees a row growing sideways the way it sees copy growing down (a block wrapper's auto width was the viewport's whatever the row did, and a row gaining a cell left the thumb and the overflow state stale until something else re-measured); copy wraps as before, and a `position: sticky; left: 0` child holds across the scroll, the plane being its containing block. `min-height: 100%` likewise: a `fixed` child (§2 Explicit placement) is a sticky box the plane contains, and a plane shorter than the child's stated `top` would clamp it to the plane's bottom edge; content that fits still fits. In the overflowing case a percentage-width or centered block child resolves against the grown plane, not the viewport. A `slotchange` re-measures; `measure()` covers a scroll range that changes with no box changing. The rails are the drawn `vfScrollRail` subtree (§4 — the classic 16px cell counting the frame line: divider, 14px channel, 15px arrow cells, the fixed 16px thumb, the dot-dither trough), rendered as later siblings of the viewport and synced to its native scrolling by `ScrollRailController`; the viewport carries the `vf-scroll` class, which hides the native bar without touching the native scrolling. When both rails are reserved, the corner cell joins them, supplying the interior dividers the adjacent arrow cells leave to it; `corner` reserves the same 15×15 cell beside a single rail, its other divider continuing the rail's line across the cell — DrawGrowIcon's form, for the grow box. Every engine renders the same rail; the old `::-webkit-scrollbar` skin, its `.vf-scroll-frame` overlay contortion (WebKit quantized native scrollbar rects to whole CSS px) and the Firefox `scrollbar-color` fallback are all retired.
400
400
  - **Always-a-rail behavior:** each *reserved* axis (per `axis`) renders its rail element as a permanent placeholder — arrow buttons on an empty white channel (dither off, no thumb; System 7 drew the arrows on any bar in an active window, and a scroller outside a window always counts as active) — until the content overflows that axis, when the dither and thumb fill in and the arrows go live (an idle axis's arrows are drawn but inert — the press guard in `ScrollRailController` skips them). `ScrollStateController` (`src/scroll-state.ts`) measures both axes and writes `data-overflow-x` / `data-overflow-y` (`"true"` / `"false"`) on the scroll element; the recipe keys the dither and thumb off those attributes. The unreserved axis still scrolls natively (wheel, keyboard) but draws no rail. Shared by vf-list and vf-text-area; a future `@container scroll-state(scrollable)` query could replace the JS for slotted-content components.
401
401
  - **Inactive-window blanking:** the HIG's non-frontmost window must not display interactive scroll UX, so the controller also finds the nearest `vf-window` up the composed tree (light-DOM ancestor for a slotted scroller, shadow ancestor for `vf-window[scrollbars]`'s own edge rails), watches its reflected `active` attribute, and toggles a presence-only `data-window-inactive` on the scroll element. While present, the recipe empties dither/thumb/arrows on BOTH axes regardless of overflow — the bare channel, arrows included (unlike the idle rail, which keeps them), exactly as System 7 blanked a deactivated window's bars (its List Manager/TextEdit deactivated in-window scrollbars too). No `vf-window` ancestor → the attribute never appears: dialogs have no inactive state and a bare scroll component always draws live. Like the overflow half, this signal could one day go declarative — a custom property cascaded under `vf-window:not([active])` gating the recipe via an `@container style()` query; the rails being ordinary DOM, that migration is a plain selector swap (see the FUTURE note in scroll-state.ts).
402
402
  - **Document-window (TeachText) composition:** to put the rails on a window's edge rather than inset in its body, slot the scroll area into a `vf-window` sized `calc(100% + 2px·scale)` with `margin: -1px·scale` — one system pixel under the window frame on every side. Its own frame border then repaints the window's border lines exactly (no doubled frame), and a resizable window's grow box lands exactly over the rail's corner cell, giving the classic System 7 document window. `vf-window[scrollbars]` renders exactly this composition from its own shadow tree (forwarding `corner` for a resizable window without a status strip), so the one-liner and the slotted form are geometrically identical; slot it yourself when the well should sit inside the body instead of on its edge (the installer's read-me well, placed with `top`/`left`).
@@ -441,7 +441,7 @@ The kit's layout primitive: a flexbox whose `gap`, `pad`, `width` and `height` a
441
441
  A plain sized box: `width`/`height` in whole system px around a bare slot — nothing drawn but what `pattern` and `rule` name, no layout opinion. Explicit placement (src/position.ts) leaves one line of CSS the kit can't write for a consumer: children placed with `top`/`left` need a positioned ancestor, so a region of the consumer's *own* needs `position: relative` in a stylesheet. This component is that region as an element — a DITL's enclosing rectangle with nothing drawn in it.
442
442
  - **Attributes/props:** `width` / `height`: number (whole system px, via `VfSized`); the `top` / `left` pair via `VfPositioned` like nearly every component; `pattern?: string` — a library pattern by name or sixteen hex digits (docs/PATTERNS.md), painted as the box's background: unset paints nothing, an unrecognized value paints nothing and warns once; `rule?: string` — edge names separated by spaces (`"bottom"`, `"top bottom"`, up to all four in any order), the 1px rule drawn on those edges as the box's own border inside the declared size: unset draws none, a value naming anything but an edge draws none and warns once. On a **child**: `fill-width` and `fill-height`, bare attributes as in `vf-stack`. That is the whole API.
443
443
  - **Not a `vf-stack`.** The stack is a flexbox with opinions — an axis, a cross-axis default, fills compiled into flex. The container has none: in-flow children get normal flow, placed children get a coordinate system. Reach for it when the stack's opinions are the thing in the way — a field of placed icons, a fixed stage for placed art, a composition that brings its own layout with it.
444
- - **Visual:** none unless `pattern` or `rule` is set — no role, keyboard behavior or selection; what it holds decides what it is. With `pattern`, the shadow box carries the fill as its own background (`vfPatternFill`: the `vf-pattern-fill` class always, `vf-patterned` while a pattern is resolved — *Patterns* under *Tiled fills*), riding the snap correction with the coordinate system; the raster is sized from the declared axes and measured on an undeclared one. With `rule`, the same box wears `vfRule`'s per-edge classes (§4): the rule is its border, inside the declared size (`box-sizing: border-box`), so a 24px `rule="bottom"` strip is 23 rows of box over one of ink — the menu bar's anatomy — and the padding box, which placed children anchor to and percentage fills resolve against, begins inside the rule. A pattern's paper reaches the rule and its phase anchors at the padding box, so it begins directly inside a top or left rule. `npm run verify:rule`. `:host` is a block with `width: fit-content`, so an undeclared axis shrink-wraps rather than claiming the parent's width (the stack's rule — a layout box must not hand out a size nobody declared; a declared dimension lands on the host's inline style and beats it). The slot sits in one shadow box coinciding with the host box (`.vf-snap`, `display: flow-root`, `height: 100%`): flow-root so a slotted margin cannot collapse through the top edge and push the coordinate origin off the host's corner, 100% so percentage fills resolve against a declared height. That box owns `position: relative` — the anchor for `top`/`left` children — the component's purpose. Content that outgrows the declared box overflows it rather than growing it.
444
+ - **Visual:** none unless `pattern` or `rule` is set — no role, keyboard behavior or selection; what it holds decides what it is. With `pattern`, the shadow box carries the fill as its own background (`vfPatternFill`: the `vf-pattern-fill` class always, `vf-patterned` while a pattern is resolved — *Patterns* under *Tiled fills*), riding the snap correction with the coordinate system; the tile repeats over whatever size the box has, declared or not. With `rule`, the same box wears `vfRule`'s per-edge classes (§4): the rule is its border, inside the declared size (`box-sizing: border-box`), so a 24px `rule="bottom"` strip is 23 rows of box over one of ink — the menu bar's anatomy — and the padding box, which placed children anchor to and percentage fills resolve against, begins inside the rule. A pattern's paper reaches the rule and its phase anchors at the padding box, so it begins directly inside a top or left rule. `npm run verify:rule`. `:host` is a block with `width: fit-content`, so an undeclared axis shrink-wraps rather than claiming the parent's width (the stack's rule — a layout box must not hand out a size nobody declared; a declared dimension lands on the host's inline style and beats it). The slot sits in one shadow box coinciding with the host box (`.vf-snap`, `display: flow-root`, `height: 100%`): flow-root so a slotted margin cannot collapse through the top edge and push the coordinate origin off the host's corner, 100% so percentage fills resolve against a declared height. That box owns `position: relative` — the anchor for `top`/`left` children — the component's purpose. Content that outgrows the declared box overflows it rather than growing it.
445
445
  - **Carries a `GridSnapController`.** A container's box is itself the consumer's coordinate system, including for non-`vf` content that cannot correct itself. The shadow box owns the positioning anchor and the `vf-snap` class *together*, so the correction moves the whole coordinate system and everything placed against it rides along, kit or not. A declared size and whole-px `top`/`left` are on the grid by construction; the controller covers the origin the page contributes. (This is the arrangement `vf-stack` has since adopted too — one shadow box owning anchor and correction together.)
446
446
  - **Typographically transparent**, exactly as `vf-stack` and for the same reason: `font`, `-webkit-font-smoothing`, `color`, `user-select` and `text-align` return to `inherit`, so wrapping content in a sized box changes nothing about how that content reads.
447
447
  - **`fill-width` / `fill-height`** are read about the host (`width`/`height: 100%`, for a parent that can give the box a size) and compiled for slotted children as the percentage form — normal flow has no flex axes to translate onto. `width: 100%` always binds against the box; `height: 100%` binds only against a declared `height` (percentage-against-auto computes to auto), so a fill with nothing to take is inert, not an error. A light-DOM declaration beats the `::slotted` rule, as everywhere.
@@ -204,7 +204,7 @@
204
204
  },
205
205
  {
206
206
  "name": "vf-container",
207
- "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing** — unless `pattern` or `rule` say\nwhat to paint. No role, keyboard behavior or selection; what it holds\ndecides what it is.\n\n**`rule` draws the 1px rule on the edges it names** — `rule=\"bottom\"` is\nthe menu bar's anatomy (the box's rows over one row of ink), `rule=\"top\"`\na status strip's, all four a framed box. The rule is the box's own border\n(vfRule), inside the declared size, so a 24px `rule=\"bottom\"`\nstrip is 23 rows of box over the line; content, `fill-width` children and\nplaced children begin inside it, as a rectangle's interior begins inside\nFrameRect's line.\n\n**`pattern` fills the box with a 1-bit pattern**: one of the 38 standard\nMacPaint patterns by name (`pattern=\"bricks\"`, `pattern=\"gray-50\"` —\ndocs/PATTERNS.md has the table), or sixteen hex digits stating a custom\n8×8 pattern row by row, the way a PAT resource did. It is painted as the\nbox's own background — black ink on a `--vf-white` ground, anchored at\nthe box's top-left corner, under the content — by the same whole-surface\nraster mechanism as the desktop dither, so it is 1-bit at every density\nand zoom (src/pattern-fill.ts). A declared `width`/`height` sizes the\nraster exactly; an undeclared axis (`fill-width`, a shrink-wrapped\nheight) is measured. Under forced colors the pattern goes flat Canvas.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The shadow box below owns the `position: relative` anchor\nand the `vf-snap` class together, so the correction\nmoves the whole coordinate system — everything placed against it rides\nalong instead of being re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.\n---\n\n\n### **Slots:**\n - _default_ - The content. In flow by default; `top`/`left` on a kit child places it against this box's origin. `fill-width` / `fill-height` on a child fills it to the declared box.",
207
+ "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing** — unless `pattern` or `rule` say\nwhat to paint. No role, keyboard behavior or selection; what it holds\ndecides what it is.\n\n**`rule` draws the 1px rule on the edges it names** — `rule=\"bottom\"` is\nthe menu bar's anatomy (the box's rows over one row of ink), `rule=\"top\"`\na status strip's, all four a framed box. The rule is the box's own border\n(vfRule), inside the declared size, so a 24px `rule=\"bottom\"`\nstrip is 23 rows of box over the line; content, `fill-width` children and\nplaced children begin inside it, as a rectangle's interior begins inside\nFrameRect's line.\n\n**`pattern` fills the box with a 1-bit pattern**: one of the 38 standard\nMacPaint patterns by name (`pattern=\"bricks\"`, `pattern=\"gray-50\"` —\ndocs/PATTERNS.md has the table), or sixteen hex digits stating a custom\n8×8 pattern row by row, the way a PAT resource did. It is painted as the\nbox's own background — black ink on a `--vf-white` ground, anchored at\nthe box's top-left corner, under the content — by the same repeating tile\nas the desktop dither, so it is 1-bit at every density and zoom and a box\nof any size, declared or not, costs nothing more (src/pattern-fill.ts).\nUnder forced colors the pattern goes flat Canvas.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The shadow box below owns the `position: relative` anchor\nand the `vf-snap` class together, so the correction\nmoves the whole coordinate system — everything placed against it rides\nalong instead of being re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.\n---\n\n\n### **Slots:**\n - _default_ - The content. In flow by default; `top`/`left` on a kit child places it against this box's origin. `fill-width` / `fill-height` on a child fills it to the declared box.",
208
208
  "attributes": [
209
209
  {
210
210
  "name": "pattern",
@@ -251,7 +251,7 @@
251
251
  },
252
252
  {
253
253
  "name": "vf-desktop",
254
- "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM with none behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
254
+ "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM with none behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
255
255
  "attributes": [
256
256
  {
257
257
  "name": "width",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "vintage-frames",
4
- "version": "0.12.8",
4
+ "version": "0.13.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -478,7 +478,7 @@
478
478
  },
479
479
  {
480
480
  "name": "vf-container",
481
- "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing** — unless `pattern` or `rule` say\nwhat to paint. No role, keyboard behavior or selection; what it holds\ndecides what it is.\n\n**`rule` draws the 1px rule on the edges it names** — `rule=\"bottom\"` is\nthe menu bar's anatomy (the box's rows over one row of ink), `rule=\"top\"`\na status strip's, all four a framed box. The rule is the box's own border\n(vfRule), inside the declared size, so a 24px `rule=\"bottom\"`\nstrip is 23 rows of box over the line; content, `fill-width` children and\nplaced children begin inside it, as a rectangle's interior begins inside\nFrameRect's line.\n\n**`pattern` fills the box with a 1-bit pattern**: one of the 38 standard\nMacPaint patterns by name (`pattern=\"bricks\"`, `pattern=\"gray-50\"` —\ndocs/PATTERNS.md has the table), or sixteen hex digits stating a custom\n8×8 pattern row by row, the way a PAT resource did. It is painted as the\nbox's own background — black ink on a `--vf-white` ground, anchored at\nthe box's top-left corner, under the content — by the same whole-surface\nraster mechanism as the desktop dither, so it is 1-bit at every density\nand zoom (src/pattern-fill.ts). A declared `width`/`height` sizes the\nraster exactly; an undeclared axis (`fill-width`, a shrink-wrapped\nheight) is measured. Under forced colors the pattern goes flat Canvas.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The shadow box below owns the `position: relative` anchor\nand the `vf-snap` class together, so the correction\nmoves the whole coordinate system — everything placed against it rides\nalong instead of being re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.\n---\n\n\n### **Slots:**\n - _default_ - The content. In flow by default; `top`/`left` on a kit child places it against this box's origin. `fill-width` / `fill-height` on a child fills it to the declared box.",
481
+ "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing** — unless `pattern` or `rule` say\nwhat to paint. No role, keyboard behavior or selection; what it holds\ndecides what it is.\n\n**`rule` draws the 1px rule on the edges it names** — `rule=\"bottom\"` is\nthe menu bar's anatomy (the box's rows over one row of ink), `rule=\"top\"`\na status strip's, all four a framed box. The rule is the box's own border\n(vfRule), inside the declared size, so a 24px `rule=\"bottom\"`\nstrip is 23 rows of box over the line; content, `fill-width` children and\nplaced children begin inside it, as a rectangle's interior begins inside\nFrameRect's line.\n\n**`pattern` fills the box with a 1-bit pattern**: one of the 38 standard\nMacPaint patterns by name (`pattern=\"bricks\"`, `pattern=\"gray-50\"` —\ndocs/PATTERNS.md has the table), or sixteen hex digits stating a custom\n8×8 pattern row by row, the way a PAT resource did. It is painted as the\nbox's own background — black ink on a `--vf-white` ground, anchored at\nthe box's top-left corner, under the content — by the same repeating tile\nas the desktop dither, so it is 1-bit at every density and zoom and a box\nof any size, declared or not, costs nothing more (src/pattern-fill.ts).\nUnder forced colors the pattern goes flat Canvas.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The shadow box below owns the `position: relative` anchor\nand the `vf-snap` class together, so the correction\nmoves the whole coordinate system — everything placed against it rides\nalong instead of being re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.\n---\n\n\n### **Slots:**\n - _default_ - The content. In flow by default; `top`/`left` on a kit child places it against this box's origin. `fill-width` / `fill-height` on a child fills it to the declared box.",
482
482
  "doc-url": "",
483
483
  "attributes": [
484
484
  {
@@ -577,7 +577,7 @@
577
577
  },
578
578
  {
579
579
  "name": "vf-desktop",
580
- "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM with none behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
580
+ "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM with none behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
581
581
  "doc-url": "",
582
582
  "attributes": [
583
583
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintage-frames",
3
- "version": "0.12.8",
3
+ "version": "0.13.0",
4
4
  "description": "Lit web components that emulate the look and feel of classic Mac OS System 7",
5
5
  "type": "module",
6
6
  "license": "MIT",