vintage-frames 0.10.1 → 0.12.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
@@ -83,6 +83,7 @@ Every length in this doc is a **system pixel** value; components multiply it by
83
83
  | `--vf-button-height` | `20px` | `vf-button` face (the default ring's inner box is 80×20) |
84
84
  | `--vf-button-group-gap` | `12px` | gap between buttons in a `vf-button-group` — the default clears the default ring's 4px reach on both sides, so rings never collide; retheming it below **8px** is the one value that puts two adjacent `variant="default"` rings into each other |
85
85
  | `--vf-popup-height` | `18px` | `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) |
86
+ | `--vf-popup-height-small` | `12px` | `vf-select[size="small"]` pill (border box). The small select restates it as `--vf-popup-height` on its host, so its rows (10px) and arrow slots derive from it the same way; a `--vf-popup-height` set on the small element itself still wins |
86
87
  | `--vf-popup-inset-top` | `4px` | room a clipped popup panel keeps clear at the **top** screen edge. Environmental rather than per-instance — a page has one menu bar and every popup on it should respect the same reserve — so it is declared once on `:root` or the `vf-desktop`: `24px` clears a `vf-menu-bar` (the 20px bar plus the default 4). Authored (unscaled) system px, like `--vf-popup-height`; `positionPanel` reads it back off the panel and converts with `sys()` |
87
88
  | `--vf-popup-inset-bottom` | `4px` | the same reserve at the **bottom** screen edge |
88
89
  | `--vf-menu-row-height` | `16px` | `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows); `vf-menu` also spends one full row on every slotted `vf-separator` — the MDEF's divider-as-item, rule 8px in (H/2 above, H/2−1 below) |
@@ -120,7 +121,7 @@ Every length in this doc is a **system pixel** value; components multiply it by
120
121
 
121
122
  **Line boxes are lengths too.** Every line-height in the kit is stated in whole system px (`--vf-menu-row-height`, `--vf-label-line-height`, `--vf-paragraph-line-height`, …), never as a ratio. A ratio resolves to whatever it resolves to — `1.65 × 17px` is `28.05px` — and each line pushes everything after it further off the device-pixel grid, which is the single most common way a page fringes an otherwise-correct component (see the layout contract in README). Re-theme these tokens with whole numbers.
122
123
 
123
- **One size.** Both embedded faces are single 16-design-px masters and render at exactly that size — one design px = one system px, always. "Smaller" is a *family* switch: System 7's fine print was Geneva 9 — the collection's smallest strike, which **is** the body face — so a dialog's disk-space caption is `face="body"` (usually `dim`), the way `vf-icon size="small"` swaps to the 16×16 art. A genuinely different size is a different strike, registered like the embedded ones and themed in through the font family/size tokens with its own whole-pixel metrics.
124
+ **One size.** Both embedded faces are single 16-design-px masters and render at exactly that size — one design px = one system px, always. "Smaller" is a *family* switch: System 7's fine print was Geneva 9 — the collection's smallest strike, which **is** the body face — so a dialog's disk-space caption is `face="body"` (usually `dim`) and a small popup menu is `vf-select size="small"`, the way `vf-icon size="small"` swaps to the 16×16 art. A genuinely different size is a different strike, registered like the embedded ones and themed in through the font family/size tokens with its own whole-pixel metrics.
124
125
 
125
126
  **Grid snapping.** Whole system pixels only put an edge on the device grid *relative to the component's own origin*; a page that lands that origin on a fractional device pixel smears the whole 1-bit interior. Every component measures its own paint and cancels the fractional remainder, always (`src/grid-snap.ts`), so the origin is the component's responsibility rather than the page's. The correction is a `left`/`top` offset on the component's own paint root — the shadow element carrying the `.vf-snap` class (`vfBase`), relatively positioned, driven by two reserved custom properties (`--vf-snap-dx`/`-dy`) the controller writes on the host. A *layout*-stage shift, because a compositing-stage `transform` leaves the subtree rastered at its old position and only removes ~80% of the fringe. Nothing outside the shadow root is written but those two properties, so the correction cannot collide with a consumer's positioning or with `vf-window`'s drag coordinates. Absolutely positioned satellites that anchor to the host (`vf-menu`'s panel, the default button's ring) compose the same properties into their insets; rows and options inside a corrected container (`vf-list-item`, `vf-menu-item`, `vf-option`) and layout-only hosts (`vf-button-group`, `vf-radio-group`) carry no target and ride their surroundings. The two properties are reset to `0` per host (`vfBase`): custom properties inherit, and without the reset a host needing no correction of its own would resolve an ancestor component's values in its shadow root and paint off the grid by exactly that amount. It corrects the origin only: a fractional *size*, and a `--vf-scale × trueDpr` that isn't whole, are still the page's to get right.
126
127
 
@@ -194,7 +195,7 @@ Every recipe also declares its size — `width`, and `height` where the window i
194
195
 
195
196
  #### `vf-desktop` (`VfDesktop`, vf-desktop.ts)
196
197
  Full-bleed classic desktop container.
197
- - **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 two *top* corners wear the `SCREEN_CORNER` mask (the classic framebuffer rounded only the top pair; the raster's bottom corners ran square), painted above everything like the hardware mask — the masks land 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.
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.
198
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).
199
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
201
  - **Slots:** default (menu bar, windows, anything).
@@ -327,8 +328,8 @@ A numeric text field paired with the classic "little arrows" stepper.
327
328
 
328
329
  #### `vf-select` (`VfSelect`, vf-option.ts children) (vf-select.ts)
329
330
  The classic popup menu control ("Macintosh HD ▼").
330
- - **Attributes/props:** `value: string`, `disabled`, `name`.
331
- - **Children:** `<vf-option value="...">Label</vf-option>` elements (default slot). `vf-option` (`VfOption`, vf-option.ts): props `value`, `disabled`, `selected` (managed by parent); renders its slot; `role="option"`.
331
+ - **Attributes/props:** `value: string`, `disabled`, `name`, `size: 'regular' | 'small'` (reflected; any other value renders as `regular`).
332
+ - **Children:** `<vf-option value="...">Label</vf-option>` elements (default slot). `vf-option` (`VfOption`, vf-option.ts): props `value`, `disabled`, `selected` and `size` (both managed by parent — the select stamps its `size` on every option, including ones added later); renders its slot; `role="option"`.
332
333
  - **Visual (closed control):** height `var(--vf-popup-height, 18px)`, white bg, `1px solid black`, NO radius, `box-shadow: 1px 1px 0 0 var(--vf-black, #000)` (the small hard shadow visible in the screenshot), `padding: 0 8px 0 var(--vf-select-gutter, 16px)` — the left inset equals the option checkmark gutter so the selected label sits at the same x closed or open. *One documented 1px deviation:* the reference sheets put a closed pill's label ink 16px from its border box but an open menu's at 17 (three pill instances and four menu panels, all consistent, in the reference art). System 7 reconciled that by drawing the open panel 1px left of the pill; the kit instead uses one shared gutter with no panel offset, which keeps the closed↔open alignment exact and puts the closed pill's ink at 17. Bold label left, the black `CARET_DOWN` ▼ pixel glyph (inline SVG) right with 8px gap. The ▼ stays black even when the control is disabled (only the label dims). **Width:** the control hugs the *widest* option — no intrinsic min-width — via an invisible, height-collapsed stack of every option's text sharing the label's grid cell; so the closed pill and the open panel are always exactly the same width and the value never shifts as the selection changes. Authors wanting a floor set `min-width` on the host (or grow it in their layout, e.g. `flex: 1`). **Keyboard focus: no ring** — `vfFocusUnderline` (§4) at `--vf-focus-underline-offset: -4px`, a dashed rule under the *whole* pill (border + hard shadow + blank row), not inside the face the way vf-button underlines its label: the pill's one line already holds the label and the ▼. Spans the border box (±1px off the padding box the pseudo-element sizes to). **Closed only**, as `vf-menu`'s is: the open list already shows where focus is, and a panel short enough not to cover the rule — a one-option menu overlays the pill exactly — would leave a stray dashed line below it. Gated on a `.vf-focus-rule` class from the page's input modality, not `:focus-visible` — see §4 for why this control can't use the selector either. `npm run verify:focus`.
333
334
  - **Visual (open):** panel uses the `.vf-panel` recipe but overrides `--vf-shadow-offset: 1px` so its hard shadow matches the pill's (not the 2px menu shadow). Positioned `position: fixed` to the control's exact width and left (unsnapped, so the panel's left/right edges and shadow coincide with the pill's). Item rows are height 16px — the pill's *content* height, derived as `calc(var(--vf-popup-height, 18px) - 2px)` so a re-themed pill moves its rows with it — `padding: 0 20px 0 var(--vf-select-gutter, 16px)`; the panel opens with the selected row's cell laid directly over the closed pill (its top border on the pill's top border, extending downward), so the selected label's position and surrounding whitespace are identical closed and open. The currently-selected item shows a ✓ checkmark in the left `--vf-select-gutter` column; hovered/active item inverts (black bg, white text); disabled options gray.
334
335
  - **Visual (open, clipped):** a list taller than the screen is **clipped, never scrolled** — System 7 put no scrollbar on a menu. The panel is `overflow: hidden` and drawn once at a whole number of row slots; the rows ride a `.rows` strip rolled by `transform`, and the edge slot with items beyond it is covered by an opaque white **arrow slot** (`part="scroll-arrow"`, `aria-hidden`) carrying `CARET_UP` / `CARET_DOWN`. Traced from a real System 7 popup clipped at the screen edge (Find File's criteria menu under Infinite Mac, 2×): the 11×6 triangle sits 13px in from the panel's content edge and 5px down its 16px row, and the arrow occupies a **full** row slot. The pointer resting on an arrow rolls the list one row per `MENU_SCROLL_INTERVAL_MS` (66ms, motion.ts — the interaction itself, so *not* reduced-motion gated); the arrow retires when its direction runs out. Both can show at once. Four invariants, all in `src/popup-overflow.ts`:
@@ -336,6 +337,7 @@ The classic popup menu control ("Macintosh HD ▼").
336
337
  - **The panel is as tall as the *list* asked for, not as tall as the rows it can currently show** — capped only by the screen band. A list that fits the band but not where the pill would put it keeps every slot and slides whole rows, while the item strip stays welded to the pill; the slots the strip no longer reaches are drawn as **empty white**. The blank is the exact travel the list rolls through: scrolling to that end lands the strip flush with the panel, precisely full, both arrows retired. This is what System 7 drew — 5 empty rows above `name` in Find File's criteria popup, 2 above `Athens` in Character Set's font menu. **The two directions are one rule:** a panel pushed up (the pill is low) reserves its blank at the top and rolls down into it; a panel pushed down (the pill is high with a late item selected) reserves it at the bottom and rolls up into it. Only the sign of the scroll integer distinguishes them — it is free to leave the `[0, rowCount − visibleSlots]` range that would fill every slot, and that departure *is* the blank. It follows that the blank is a one-way starting position: an arrow means "rows are really hidden this way", so whichever end the blank is on, the arrow pointing back at it is off.
337
338
  - **The panel box never moves or resizes while open:** scrolling rolls the items inside it.
338
339
  - **Three slots minimum** when overflowing, even if that intrudes on the insets below — with both arrows shown, fewer leaves nothing to pick. The screen-edge band comes from `--vf-popup-inset-top` / `--vf-popup-inset-bottom` (§3), whose 4px defaults reproduce the old viewport margins exactly, so a list that fits is untouched by any of this. `npm run verify:select-overflow`.
340
+ - **Visual (small):** `size="small"` sets the pill and its rows in the body face (`vfBodyDecls`): a `var(--vf-popup-height-small, 12px)` pill over 10px rows. Content rows: 1 blank, 7 for the capitals, 2 for the descenders, which end on the last content row. The label's line box is 8px (`--vf-popup-height − 4px`) with the descender rows below it as padding — a 10px line box centers the 16px em one row lower and puts the second descender row on the border. The pill label pads the em's full 4px descent and takes 2px back with a negative margin, so its clip never leaves scrollable overflow; each option clips its own row (`overflow: clip`). The ▼ is `CARET_DOWN_SMALL` — `CARET_DOWN` without its widest row, 9×5 — pinned 3 rows down the content (3 blank rows above, 2 below), its middle row level with the x-height's middle row; the panel's scroll arrows are `CARET_UP_SMALL` / `CARET_DOWN_SMALL` at (14, 2) in their 10px slots, 14 keeping the regular caret's center column. The ✓ sits at (3, 1), centered in the row. The ✓ glyph, the gutter, the insets, the hard shadow and the focus rule are the regular pill's. Glyphs reaching more than 8 rows above the baseline are clipped at the top: the accented capitals (É, Å, Ñ …), `å ƒ ∫ ⌘ ⇧ ▶ ◀`, and the Apple logo. Centered in a row beside a `vf-label` of either face, the small pill's baseline sits 1px above the label's; placed, a body-face label lines up at the pill's `top` − 1 and a display-face label at `top` − 3. A live `size` change closes an open panel. `npm run verify:control-heights`, `npm run verify:baseline`.
339
341
  - **Behavior:** form-associated. Opens on pointerdown (mouse/touch), Space/Enter/ArrowDown, or a synthesised click (assistive tech). Two pointer styles coexist, disambiguated by the gesture and resolved at the first release: **System 7 press-drag-release** (press the pill, drag onto an item, release to pick — releasing on the current item or off the list cancels) and **modern click-to-open** (a quick in-place click leaves the list open for a second click). The only timed distinction is an in-place release — a tap (<200ms) keeps the list open, a held press closes it; any press that travels to another item is a drag-pick regardless of duration. Highlight tracking during a press hit-tests by coordinates (so it works under touch's implicit pointer capture) and never moves DOM focus off the control. Panel is positioned `position: fixed` from `getBoundingClientRect()` so it escapes clipping containers; closes on outside pointerdown, Escape, blur, scroll. Keyboard while open: arrows move active item, Enter/Space select, Escape cancels, Home/End jump, and printable keys run the shared Finder first-letter type-ahead (`src/type-ahead.ts` — the same buffer `vf-list` and the menus use), moving the highlight to the match. On select: classic blink (invert toggles ~3 times in ~250ms) then close + `vf-change`. A press that *begins* on an arrow is neither a pick nor a dismissal (so a click on one leaves the list up); a press-drag *released* on one closes with no change, like any release on a non-item — and while a drag is in an arrow's zone the highlight is dropped, because the arrow row is not an item. That zone reaches past the panel edge in its own direction, the classic ergonomics of slamming the pointer to the screen edge.
340
342
  - **Parts:** `control`, `label`, `arrow`, `panel`, `scroll-arrow`.
341
343
  - **Events:** `vf-change` detail `{ value }`, plus the native `input`/`change` pair per committed pick (§2).
@@ -231,7 +231,7 @@
231
231
  },
232
232
  {
233
233
  "name": "vf-desktop",
234
- "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 top 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. Not the top 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).",
234
+ "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. Not the top 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).",
235
235
  "attributes": [
236
236
  {
237
237
  "name": "width",
@@ -245,7 +245,7 @@
245
245
  },
246
246
  {
247
247
  "name": "bezel",
248
- "description": "Width of the black screen bezel, in system px (`0` = none), added onto\nthe declared screen on every side — a `width=\"502\" bezel=\"5\"` desktop\nrenders a 512-system-px host box. The compact Mac's CRT showed an\nunlit black margin between the desktop's raster and the case; `bezel`\ndraws it around the screen and puts the classic screen-corner mask on\nthe screen's two *top* corners — only the top pair was rounded in the\nframebuffer. Flow, window coordinates and the drag clip all belong to\nthe screen, so windows crop at its edge. Inside a bezeled desktop a\nmenu bar needs no `rounded` of its own — the desktop's mask lands on\nthe same pixels.",
248
+ "description": "Width of the black screen bezel, in system px (`0` = none), added onto\nthe declared screen on every side — a `width=\"502\" bezel=\"5\"` desktop\nrenders a 512-system-px host box. The compact Mac's CRT showed an\nunlit black margin between the desktop's raster and the case; `bezel`\ndraws it around the screen and puts the classic screen-corner mask on\nall four of the screen's corners. Flow, window coordinates and the drag\nclip all belong to the screen, so windows crop at its edge. Inside a\nbezeled desktop a menu bar needs no `rounded` of its own — the desktop's\ntop pair lands on the same pixels.",
249
249
  "values": []
250
250
  },
251
251
  {
@@ -959,7 +959,7 @@
959
959
  },
960
960
  {
961
961
  "name": "vf-option",
962
- "description": "`<vf-option>` — a single choice inside a `<vf-select>` popup menu.\n\nA light-DOM child of `<vf-select>` (slotted into the popup panel). Renders\nits slotted label at menu-item metrics (16px row — the pill's content height,\nso a selected option overlays the closed pill exactly; the left checkmark gutter is\n`--vf-select-gutter`, shared with the closed control's left inset so the value\ndoesn't shift on open). The parent select manages `selected` and the transient\n`active` highlight, and slots this element into its popup panel.\n\nThe host carries `role=\"option\"` with `aria-selected`/`aria-disabled`.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants one somewhere other than a popup row. Inside its parent\n`<vf-select>` it is a row the panel measures and scrolls in flow, so stating\nan origin there takes it out of that measurement: the panel stops counting it\ntoward its own width and its scroll clamp, and the rows below close the gap.\nThat is the placement doing exactly what it says — not a bug — but it is not\nhow a popup is laid out.\n---\n\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **check** - The ✓ checkmark shown in the left gutter when selected.",
962
+ "description": "`<vf-option>` — a single choice inside a `<vf-select>` popup menu.\n\nA light-DOM child of `<vf-select>` (slotted into the popup panel). Renders\nits slotted label at menu-item metrics (16px row — the pill's content height,\nso a selected option overlays the closed pill exactly; the left checkmark gutter is\n`--vf-select-gutter`, shared with the closed control's left inset so the value\ndoesn't shift on open). The parent select manages `selected`, the transient\n`active` highlight and `size`, and slots this element into its popup panel.\nUnder `size=\"small\"` the row is the small pill's 10px content height, in the\nbody face, and clips its own box.\n\nThe host carries `role=\"option\"` with `aria-selected`/`aria-disabled`.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants one somewhere other than a popup row. Inside its parent\n`<vf-select>` it is a row the panel measures and scrolls in flow, so stating\nan origin there takes it out of that measurement: the panel stops counting it\ntoward its own width and its scroll clamp, and the rows below close the gap.\nThat is the placement doing exactly what it says — not a bug — but it is not\nhow a popup is laid out.\n---\n\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **check** - The ✓ checkmark shown in the left gutter when selected.",
963
963
  "attributes": [
964
964
  {
965
965
  "name": "value",
@@ -981,6 +981,11 @@
981
981
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
982
982
  "values": []
983
983
  },
984
+ {
985
+ "name": "size",
986
+ "description": "The parent select's `size`. Managed by the parent.",
987
+ "values": [{ "name": "VfSelectSize" }]
988
+ },
984
989
  {
985
990
  "name": "top",
986
991
  "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
@@ -1240,7 +1245,7 @@
1240
1245
  },
1241
1246
  {
1242
1247
  "name": "vf-select",
1243
- "description": "`<vf-select>` — the classic System 7 popup menu control (\"Macintosh HD ▼\").\n\nChildren are `<vf-option>` elements in the default slot. The closed control\nis a white box with a 1px black border, the small 1px hard shadow, the\nselected option's label on the left and a solid black ▼ triangle on the\nright. The open panel uses the shared `.vf-panel` recipe and is positioned\n`position: fixed` (computed from `getBoundingClientRect()`) so it escapes\nclipping containers; when possible the currently-selected item opens\ndirectly over the control, like the real popup menu.\n\nPointer — two interaction styles are supported and disambiguated by the\ngesture itself, resolved at the first pointer release:\n - System 7 press-drag-release: press the pill (the list appears under the\n pointer), drag onto an item, release over it to pick — one continuous\n press. Releasing over the current item or off the list closes with no\n change.\n - Modern click-to-open: a quick in-place click (no drag, released within\n PRESS_HOLD_MS) leaves the list open; a second, independent click\n then picks an item.\nThe two share one opening trigger (pointerdown) and diverge only on how the\npress ends — whether the pointer travelled to another item, and (for an\nin-place release) whether it was a quick tap or a held press. The pull-down\nmenus drive themselves the same way, on the same threshold; that half lives\nin src/menu-press.ts, because one menu press may travel across a whole bar.\n\nA list too tall for the screen is **clipped**, never scrolled: the edge slot\nwith items beyond it shows a solid arrow instead of a row, which rolls the\nlist one row at a time while the pointer rests on it. The clamp is quantized\nto the pill lattice, so a clipped popup still opens with its selected row\nover the closed pill — and the panel keeps every slot the list asked for, so\na box that had to slide to fit the screen opens with *blank rows* at whichever\nend the list no longer reaches: the exact travel it will roll through, in\neither direction. See src/popup-overflow.ts.\n\nKeyboard: Space/Enter/ArrowDown open; while open ArrowUp/ArrowDown move the\nhighlight, Home/End jump, Enter/Space select, Escape cancels. Selecting an\nitem plays the classic inversion blink (~250 ms) before closing. Keyboard\nfocus is marked with the kit's 1px dashed rule under the closed pill\n(`vfFocusUnderline`) rather than a ring around it — keyboard only, which\nhere means the page's input modality rather than `:focus-visible` (the pill\ndrives its own focus, and Blink reads that as visible either way).\n\nForm-associated: submits `value` under `name`.\n---\n\n\n### **Events:**\n - **vf-change** - After a selection commits. `detail: { value: string }`.\n- **input** - Native event, dispatched from the host per committed pick (with `change`, the pair a native `<select>` fires). A programmatic `value` set fires nothing.\n- **change** - Native event, dispatched from the host per committed pick so form delegation and framework bindings hear it.\n\n### **Slots:**\n - _default_ - `<vf-option>` elements.\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-popup-inset-top** - room a clipped popup panel keeps clear at the TOP screen edge — set it once on `:root` (or the `vf-desktop`) to clear a `vf-menu-bar`: `24px` is the 20px bar plus the default 4 _(default: 4px)_\n- **--vf-popup-inset-bottom** - room a clipped popup panel keeps clear at the BOTTOM screen edge _(default: 4px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **control** - The closed popup control box.\n- **label** - The selected-option label inside the control.\n- **arrow** - The black ▼ triangle.\n- **panel** - The popup panel (listbox).\n- **scroll-arrow** - Either of the two scroll arrows a clipped panel shows in its edge row slots.",
1248
+ "description": "`<vf-select>` — the classic System 7 popup menu control (\"Macintosh HD ▼\").\n\nChildren are `<vf-option>` elements in the default slot. The closed control\nis a white box with a 1px black border, the small 1px hard shadow, the\nselected option's label on the left and a solid black ▼ triangle on the\nright. The open panel uses the shared `.vf-panel` recipe and is positioned\n`position: fixed` (computed from `getBoundingClientRect()`) so it escapes\nclipping containers; when possible the currently-selected item opens\ndirectly over the control, like the real popup menu.\n\nPointer — two interaction styles are supported and disambiguated by the\ngesture itself, resolved at the first pointer release:\n - System 7 press-drag-release: press the pill (the list appears under the\n pointer), drag onto an item, release over it to pick — one continuous\n press. Releasing over the current item or off the list closes with no\n change.\n - Modern click-to-open: a quick in-place click (no drag, released within\n PRESS_HOLD_MS) leaves the list open; a second, independent click\n then picks an item.\nThe two share one opening trigger (pointerdown) and diverge only on how the\npress ends — whether the pointer travelled to another item, and (for an\nin-place release) whether it was a quick tap or a held press. The pull-down\nmenus drive themselves the same way, on the same threshold; that half lives\nin src/menu-press.ts, because one menu press may travel across a whole bar.\n\nA list too tall for the screen is **clipped**, never scrolled: the edge slot\nwith items beyond it shows a solid arrow instead of a row, which rolls the\nlist one row at a time while the pointer rests on it. The clamp is quantized\nto the pill lattice, so a clipped popup still opens with its selected row\nover the closed pill — and the panel keeps every slot the list asked for, so\na box that had to slide to fit the screen opens with *blank rows* at whichever\nend the list no longer reaches: the exact travel it will roll through, in\neither direction. See src/popup-overflow.ts.\n\n`size=\"small\"` sets the pill and its rows in the body face: a 12px pill\n(`--vf-popup-height-small`) over 10px rows, the baseline 8 rows down so the\nface's 2-row descenders end on the last content row. Its ▼ and scroll\narrows are the 9×5 `CARET_DOWN_SMALL` / `CARET_UP_SMALL`; the ✓ and the\nhorizontal insets are the regular pill's. Marks above a capital (É, Å, Ñ)\nreach past the 8 rows above the baseline and are clipped.\n\nKeyboard: Space/Enter/ArrowDown open; while open ArrowUp/ArrowDown move the\nhighlight, Home/End jump, Enter/Space select, Escape cancels. Selecting an\nitem plays the classic inversion blink (~250 ms) before closing. Keyboard\nfocus is marked with the kit's 1px dashed rule under the closed pill\n(`vfFocusUnderline`) rather than a ring around it — keyboard only, which\nhere means the page's input modality rather than `:focus-visible` (the pill\ndrives its own focus, and Blink reads that as visible either way).\n\nForm-associated: submits `value` under `name`.\n---\n\n\n### **Events:**\n - **vf-change** - After a selection commits. `detail: { value: string }`.\n- **input** - Native event, dispatched from the host per committed pick (with `change`, the pair a native `<select>` fires). A programmatic `value` set fires nothing.\n- **change** - Native event, dispatched from the host per committed pick so form delegation and framework bindings hear it.\n\n### **Slots:**\n - _default_ - `<vf-option>` elements.\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-popup-height-small** - `vf-select[size=\"small\"]` pill (border box; its rows are this less the two borders) _(default: 12px)_\n- **--vf-popup-inset-top** - room a clipped popup panel keeps clear at the TOP screen edge — set it once on `:root` (or the `vf-desktop`) to clear a `vf-menu-bar`: `24px` is the 20px bar plus the default 4 _(default: 4px)_\n- **--vf-popup-inset-bottom** - room a clipped popup panel keeps clear at the BOTTOM screen edge _(default: 4px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **control** - The closed popup control box.\n- **label** - The selected-option label inside the control.\n- **arrow** - The black ▼ triangle.\n- **panel** - The popup panel (listbox).\n- **scroll-arrow** - Either of the two scroll arrows a clipped panel shows in its edge row slots.",
1244
1249
  "attributes": [
1245
1250
  {
1246
1251
  "name": "value",
@@ -1252,6 +1257,11 @@
1252
1257
  "description": "Form field name used when submitting the associated form.",
1253
1258
  "values": []
1254
1259
  },
1260
+ {
1261
+ "name": "size",
1262
+ "description": "`regular` (the 18px pill in the display face) or `small` (12px, in the\nbody face). Any other value renders as `regular`. The select passes it to\nits options.",
1263
+ "values": [{ "name": "VfSelectSize" }]
1264
+ },
1255
1265
  {
1256
1266
  "name": "label",
1257
1267
  "description": "Accessible name for the combobox control (`aria-label`). Left empty, the\nname falls back to whatever the host carries — `aria-labelledby`,\n`aria-label` or an associated `<label for>` — via\nVfShadowRoleControl.hostLabel; with neither, the control is announced by\nits contents, i.e. the selected value.",
@@ -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.10.1",
4
+ "version": "0.12.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -537,7 +537,7 @@
537
537
  },
538
538
  {
539
539
  "name": "vf-desktop",
540
- "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 top 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. Not the top 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).",
540
+ "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. Not the top 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).",
541
541
  "doc-url": "",
542
542
  "attributes": [
543
543
  {
@@ -552,7 +552,7 @@
552
552
  },
553
553
  {
554
554
  "name": "bezel",
555
- "description": "Width of the black screen bezel, in system px (`0` = none), added onto\nthe declared screen on every side — a `width=\"502\" bezel=\"5\"` desktop\nrenders a 512-system-px host box. The compact Mac's CRT showed an\nunlit black margin between the desktop's raster and the case; `bezel`\ndraws it around the screen and puts the classic screen-corner mask on\nthe screen's two *top* corners — only the top pair was rounded in the\nframebuffer. Flow, window coordinates and the drag clip all belong to\nthe screen, so windows crop at its edge. Inside a bezeled desktop a\nmenu bar needs no `rounded` of its own — the desktop's mask lands on\nthe same pixels.",
555
+ "description": "Width of the black screen bezel, in system px (`0` = none), added onto\nthe declared screen on every side — a `width=\"502\" bezel=\"5\"` desktop\nrenders a 512-system-px host box. The compact Mac's CRT showed an\nunlit black margin between the desktop's raster and the case; `bezel`\ndraws it around the screen and puts the classic screen-corner mask on\nall four of the screen's corners. Flow, window coordinates and the drag\nclip all belong to the screen, so windows crop at its edge. Inside a\nbezeled desktop a menu bar needs no `rounded` of its own — the desktop's\ntop pair lands on the same pixels.",
556
556
  "value": { "type": "number", "default": "0" }
557
557
  },
558
558
  {
@@ -610,7 +610,7 @@
610
610
  },
611
611
  {
612
612
  "name": "bezel",
613
- "description": "Width of the black screen bezel, in system px (`0` = none), added onto\nthe declared screen on every side — a `width=\"502\" bezel=\"5\"` desktop\nrenders a 512-system-px host box. The compact Mac's CRT showed an\nunlit black margin between the desktop's raster and the case; `bezel`\ndraws it around the screen and puts the classic screen-corner mask on\nthe screen's two *top* corners — only the top pair was rounded in the\nframebuffer. Flow, window coordinates and the drag clip all belong to\nthe screen, so windows crop at its edge. Inside a bezeled desktop a\nmenu bar needs no `rounded` of its own — the desktop's mask lands on\nthe same pixels.",
613
+ "description": "Width of the black screen bezel, in system px (`0` = none), added onto\nthe declared screen on every side — a `width=\"502\" bezel=\"5\"` desktop\nrenders a 512-system-px host box. The compact Mac's CRT showed an\nunlit black margin between the desktop's raster and the case; `bezel`\ndraws it around the screen and puts the classic screen-corner mask on\nall four of the screen's corners. Flow, window coordinates and the drag\nclip all belong to the screen, so windows crop at its edge. Inside a\nbezeled desktop a menu bar needs no `rounded` of its own — the desktop's\ntop pair lands on the same pixels.",
614
614
  "type": "number"
615
615
  },
616
616
  {
@@ -2249,7 +2249,7 @@
2249
2249
  },
2250
2250
  {
2251
2251
  "name": "vf-option",
2252
- "description": "`<vf-option>` — a single choice inside a `<vf-select>` popup menu.\n\nA light-DOM child of `<vf-select>` (slotted into the popup panel). Renders\nits slotted label at menu-item metrics (16px row — the pill's content height,\nso a selected option overlays the closed pill exactly; the left checkmark gutter is\n`--vf-select-gutter`, shared with the closed control's left inset so the value\ndoesn't shift on open). The parent select manages `selected` and the transient\n`active` highlight, and slots this element into its popup panel.\n\nThe host carries `role=\"option\"` with `aria-selected`/`aria-disabled`.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants one somewhere other than a popup row. Inside its parent\n`<vf-select>` it is a row the panel measures and scrolls in flow, so stating\nan origin there takes it out of that measurement: the panel stops counting it\ntoward its own width and its scroll clamp, and the rows below close the gap.\nThat is the placement doing exactly what it says — not a bug — but it is not\nhow a popup is laid out.\n---\n\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **check** - The ✓ checkmark shown in the left gutter when selected.",
2252
+ "description": "`<vf-option>` — a single choice inside a `<vf-select>` popup menu.\n\nA light-DOM child of `<vf-select>` (slotted into the popup panel). Renders\nits slotted label at menu-item metrics (16px row — the pill's content height,\nso a selected option overlays the closed pill exactly; the left checkmark gutter is\n`--vf-select-gutter`, shared with the closed control's left inset so the value\ndoesn't shift on open). The parent select manages `selected`, the transient\n`active` highlight and `size`, and slots this element into its popup panel.\nUnder `size=\"small\"` the row is the small pill's 10px content height, in the\nbody face, and clips its own box.\n\nThe host carries `role=\"option\"` with `aria-selected`/`aria-disabled`.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants one somewhere other than a popup row. Inside its parent\n`<vf-select>` it is a row the panel measures and scrolls in flow, so stating\nan origin there takes it out of that measurement: the panel stops counting it\ntoward its own width and its scroll clamp, and the rows below close the gap.\nThat is the placement doing exactly what it says — not a bug — but it is not\nhow a popup is laid out.\n---\n\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **check** - The ✓ checkmark shown in the left gutter when selected.",
2253
2253
  "doc-url": "",
2254
2254
  "attributes": [
2255
2255
  {
@@ -2272,6 +2272,11 @@
2272
2272
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
2273
2273
  "value": { "type": "boolean", "default": "false" }
2274
2274
  },
2275
+ {
2276
+ "name": "size",
2277
+ "description": "The parent select's `size`. Managed by the parent.",
2278
+ "value": { "type": "VfSelectSize", "default": "'regular'" }
2279
+ },
2275
2280
  {
2276
2281
  "name": "top",
2277
2282
  "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
@@ -2316,6 +2321,11 @@
2316
2321
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
2317
2322
  "type": "boolean"
2318
2323
  },
2324
+ {
2325
+ "name": "size",
2326
+ "description": "The parent select's `size`. Managed by the parent.",
2327
+ "type": "VfSelectSize"
2328
+ },
2319
2329
  { "name": "role", "type": "string" },
2320
2330
  {
2321
2331
  "name": "top",
@@ -2880,7 +2890,7 @@
2880
2890
  },
2881
2891
  {
2882
2892
  "name": "vf-select",
2883
- "description": "`<vf-select>` — the classic System 7 popup menu control (\"Macintosh HD ▼\").\n\nChildren are `<vf-option>` elements in the default slot. The closed control\nis a white box with a 1px black border, the small 1px hard shadow, the\nselected option's label on the left and a solid black ▼ triangle on the\nright. The open panel uses the shared `.vf-panel` recipe and is positioned\n`position: fixed` (computed from `getBoundingClientRect()`) so it escapes\nclipping containers; when possible the currently-selected item opens\ndirectly over the control, like the real popup menu.\n\nPointer — two interaction styles are supported and disambiguated by the\ngesture itself, resolved at the first pointer release:\n - System 7 press-drag-release: press the pill (the list appears under the\n pointer), drag onto an item, release over it to pick — one continuous\n press. Releasing over the current item or off the list closes with no\n change.\n - Modern click-to-open: a quick in-place click (no drag, released within\n PRESS_HOLD_MS) leaves the list open; a second, independent click\n then picks an item.\nThe two share one opening trigger (pointerdown) and diverge only on how the\npress ends — whether the pointer travelled to another item, and (for an\nin-place release) whether it was a quick tap or a held press. The pull-down\nmenus drive themselves the same way, on the same threshold; that half lives\nin src/menu-press.ts, because one menu press may travel across a whole bar.\n\nA list too tall for the screen is **clipped**, never scrolled: the edge slot\nwith items beyond it shows a solid arrow instead of a row, which rolls the\nlist one row at a time while the pointer rests on it. The clamp is quantized\nto the pill lattice, so a clipped popup still opens with its selected row\nover the closed pill — and the panel keeps every slot the list asked for, so\na box that had to slide to fit the screen opens with *blank rows* at whichever\nend the list no longer reaches: the exact travel it will roll through, in\neither direction. See src/popup-overflow.ts.\n\nKeyboard: Space/Enter/ArrowDown open; while open ArrowUp/ArrowDown move the\nhighlight, Home/End jump, Enter/Space select, Escape cancels. Selecting an\nitem plays the classic inversion blink (~250 ms) before closing. Keyboard\nfocus is marked with the kit's 1px dashed rule under the closed pill\n(`vfFocusUnderline`) rather than a ring around it — keyboard only, which\nhere means the page's input modality rather than `:focus-visible` (the pill\ndrives its own focus, and Blink reads that as visible either way).\n\nForm-associated: submits `value` under `name`.\n---\n\n\n### **Events:**\n - **vf-change** - After a selection commits. `detail: { value: string }`.\n- **input** - Native event, dispatched from the host per committed pick (with `change`, the pair a native `<select>` fires). A programmatic `value` set fires nothing.\n- **change** - Native event, dispatched from the host per committed pick so form delegation and framework bindings hear it.\n\n### **Slots:**\n - _default_ - `<vf-option>` elements.\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-popup-inset-top** - room a clipped popup panel keeps clear at the TOP screen edge — set it once on `:root` (or the `vf-desktop`) to clear a `vf-menu-bar`: `24px` is the 20px bar plus the default 4 _(default: 4px)_\n- **--vf-popup-inset-bottom** - room a clipped popup panel keeps clear at the BOTTOM screen edge _(default: 4px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **control** - The closed popup control box.\n- **label** - The selected-option label inside the control.\n- **arrow** - The black ▼ triangle.\n- **panel** - The popup panel (listbox).\n- **scroll-arrow** - Either of the two scroll arrows a clipped panel shows in its edge row slots.",
2893
+ "description": "`<vf-select>` — the classic System 7 popup menu control (\"Macintosh HD ▼\").\n\nChildren are `<vf-option>` elements in the default slot. The closed control\nis a white box with a 1px black border, the small 1px hard shadow, the\nselected option's label on the left and a solid black ▼ triangle on the\nright. The open panel uses the shared `.vf-panel` recipe and is positioned\n`position: fixed` (computed from `getBoundingClientRect()`) so it escapes\nclipping containers; when possible the currently-selected item opens\ndirectly over the control, like the real popup menu.\n\nPointer — two interaction styles are supported and disambiguated by the\ngesture itself, resolved at the first pointer release:\n - System 7 press-drag-release: press the pill (the list appears under the\n pointer), drag onto an item, release over it to pick — one continuous\n press. Releasing over the current item or off the list closes with no\n change.\n - Modern click-to-open: a quick in-place click (no drag, released within\n PRESS_HOLD_MS) leaves the list open; a second, independent click\n then picks an item.\nThe two share one opening trigger (pointerdown) and diverge only on how the\npress ends — whether the pointer travelled to another item, and (for an\nin-place release) whether it was a quick tap or a held press. The pull-down\nmenus drive themselves the same way, on the same threshold; that half lives\nin src/menu-press.ts, because one menu press may travel across a whole bar.\n\nA list too tall for the screen is **clipped**, never scrolled: the edge slot\nwith items beyond it shows a solid arrow instead of a row, which rolls the\nlist one row at a time while the pointer rests on it. The clamp is quantized\nto the pill lattice, so a clipped popup still opens with its selected row\nover the closed pill — and the panel keeps every slot the list asked for, so\na box that had to slide to fit the screen opens with *blank rows* at whichever\nend the list no longer reaches: the exact travel it will roll through, in\neither direction. See src/popup-overflow.ts.\n\n`size=\"small\"` sets the pill and its rows in the body face: a 12px pill\n(`--vf-popup-height-small`) over 10px rows, the baseline 8 rows down so the\nface's 2-row descenders end on the last content row. Its ▼ and scroll\narrows are the 9×5 `CARET_DOWN_SMALL` / `CARET_UP_SMALL`; the ✓ and the\nhorizontal insets are the regular pill's. Marks above a capital (É, Å, Ñ)\nreach past the 8 rows above the baseline and are clipped.\n\nKeyboard: Space/Enter/ArrowDown open; while open ArrowUp/ArrowDown move the\nhighlight, Home/End jump, Enter/Space select, Escape cancels. Selecting an\nitem plays the classic inversion blink (~250 ms) before closing. Keyboard\nfocus is marked with the kit's 1px dashed rule under the closed pill\n(`vfFocusUnderline`) rather than a ring around it — keyboard only, which\nhere means the page's input modality rather than `:focus-visible` (the pill\ndrives its own focus, and Blink reads that as visible either way).\n\nForm-associated: submits `value` under `name`.\n---\n\n\n### **Events:**\n - **vf-change** - After a selection commits. `detail: { value: string }`.\n- **input** - Native event, dispatched from the host per committed pick (with `change`, the pair a native `<select>` fires). A programmatic `value` set fires nothing.\n- **change** - Native event, dispatched from the host per committed pick so form delegation and framework bindings hear it.\n\n### **Slots:**\n - _default_ - `<vf-option>` elements.\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-popup-height-small** - `vf-select[size=\"small\"]` pill (border box; its rows are this less the two borders) _(default: 12px)_\n- **--vf-popup-inset-top** - room a clipped popup panel keeps clear at the TOP screen edge — set it once on `:root` (or the `vf-desktop`) to clear a `vf-menu-bar`: `24px` is the 20px bar plus the default 4 _(default: 4px)_\n- **--vf-popup-inset-bottom** - room a clipped popup panel keeps clear at the BOTTOM screen edge _(default: 4px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **control** - The closed popup control box.\n- **label** - The selected-option label inside the control.\n- **arrow** - The black ▼ triangle.\n- **panel** - The popup panel (listbox).\n- **scroll-arrow** - Either of the two scroll arrows a clipped panel shows in its edge row slots.",
2884
2894
  "doc-url": "",
2885
2895
  "attributes": [
2886
2896
  {
@@ -2893,6 +2903,11 @@
2893
2903
  "description": "Form field name used when submitting the associated form.",
2894
2904
  "value": { "type": "string", "default": "''" }
2895
2905
  },
2906
+ {
2907
+ "name": "size",
2908
+ "description": "`regular` (the 18px pill in the display face) or `small` (12px, in the\nbody face). Any other value renders as `regular`. The select passes it to\nits options.",
2909
+ "value": { "type": "VfSelectSize", "default": "'regular'" }
2910
+ },
2896
2911
  {
2897
2912
  "name": "label",
2898
2913
  "description": "Accessible name for the combobox control (`aria-label`). Left empty, the\nname falls back to whatever the host carries — `aria-labelledby`,\n`aria-label` or an associated `<label for>` — via\nVfShadowRoleControl.hostLabel; with neither, the control is announced by\nits contents, i.e. the selected value.",
@@ -2961,6 +2976,11 @@
2961
2976
  "description": "Form field name used when submitting the associated form.",
2962
2977
  "type": "string"
2963
2978
  },
2979
+ {
2980
+ "name": "size",
2981
+ "description": "`regular` (the 18px pill in the display face) or `small` (12px, in the\nbody face). Any other value renders as `regular`. The select passes it to\nits options.",
2982
+ "type": "VfSelectSize"
2983
+ },
2964
2984
  {
2965
2985
  "name": "label",
2966
2986
  "description": "Accessible name for the combobox control (`aria-label`). Left empty, the\nname falls back to whatever the host carries — `aria-labelledby`,\n`aria-label` or an associated `<label for>` — via\nVfShadowRoleControl.hostLabel; with neither, the control is announced by\nits contents, i.e. the selected value.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintage-frames",
3
- "version": "0.10.1",
3
+ "version": "0.12.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",