vintage-frames 0.1.0 → 0.2.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.
Files changed (47) hide show
  1. package/README.md +12 -233
  2. package/custom-elements.json +290 -38
  3. package/dist/components/vf-button.d.ts +1 -1
  4. package/dist/components/vf-container.d.ts +2 -2
  5. package/dist/components/vf-desktop.d.ts +1 -1
  6. package/dist/components/vf-fieldset.d.ts +1 -1
  7. package/dist/components/vf-fieldset.js +6 -1
  8. package/dist/components/vf-grid.d.ts +1 -1
  9. package/dist/components/vf-icon.d.ts +1 -1
  10. package/dist/components/vf-img.d.ts +1 -1
  11. package/dist/components/vf-label.d.ts +1 -1
  12. package/dist/components/vf-list-item.d.ts +9 -1
  13. package/dist/components/vf-list-item.js +15 -14
  14. package/dist/components/vf-list.d.ts +1 -1
  15. package/dist/components/vf-menu-bar.d.ts +1 -1
  16. package/dist/components/vf-menu-item.d.ts +10 -1
  17. package/dist/components/vf-menu-item.js +35 -34
  18. package/dist/components/vf-menu.d.ts +11 -2
  19. package/dist/components/vf-menu.js +33 -32
  20. package/dist/components/vf-option.d.ts +11 -1
  21. package/dist/components/vf-option.js +16 -15
  22. package/dist/components/vf-paragraph.d.ts +3 -3
  23. package/dist/components/vf-progress-bar.d.ts +1 -1
  24. package/dist/components/vf-scroll-area.d.ts +1 -1
  25. package/dist/components/vf-select.d.ts +1 -1
  26. package/dist/components/vf-separator.d.ts +1 -1
  27. package/dist/components/vf-slider.d.ts +1 -1
  28. package/dist/components/vf-stack.d.ts +27 -9
  29. package/dist/components/vf-stack.js +30 -8
  30. package/dist/components/vf-swatch.d.ts +1 -1
  31. package/dist/components/vf-window.d.ts +1 -1
  32. package/dist/cross-center.d.ts +76 -0
  33. package/dist/cross-center.js +69 -0
  34. package/dist/define.d.ts +2 -2
  35. package/dist/define.js +1 -1
  36. package/dist/grid-snap.d.ts +2 -24
  37. package/dist/grid-snap.js +7 -33
  38. package/dist/index.d.ts +5 -5
  39. package/dist/index.js +50 -50
  40. package/dist/position.d.ts +11 -2
  41. package/dist/styles/recipes/host.js +14 -1
  42. package/dist/styles/recipes/toggle.js +16 -0
  43. package/dist/text-control.d.ts +1 -1
  44. package/docs/SPEC.md +287 -2249
  45. package/editor/vscode.html-custom-data.json +48 -8
  46. package/editor/web-types.json +92 -12
  47. package/package.json +2 -4
@@ -154,7 +154,7 @@
154
154
  },
155
155
  {
156
156
  "name": "vf-container",
157
- "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.** No border, background, role,\nkeyboard behavior or selection — what it holds decides what it is.\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 under `applyGridSnap()` 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.",
157
+ "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.** No border, background, role,\nkeyboard behavior or selection — what it holds decides what it is.\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.",
158
158
  "attributes": [
159
159
  {
160
160
  "name": "width",
@@ -470,7 +470,7 @@
470
470
  },
471
471
  {
472
472
  "name": "vf-list-item",
473
- "description": "`<vf-list-item>` — a row inside a `<vf-list>` list box.\n\nA 20px-tall single-line row; when selected the entire row inverts\n(white-on-black), the classic System 7 selection style. Selection and\nkeyboard focus are managed by the parent `<vf-list>`.\n---\n\n\n### **Slots:**\n - _default_ - The row's text/content.\n- **icon** - A leading graphic — usually a `vf-img` holding a 16×16 System 7 small icon, but any element rides here. The row lays it out as the icon gutter: flex-centered vertically (keep the difference between the row height and the icon height even, so the centering offset stays a whole pixel — 16 in a 20px row is a whole 2px), with the reference art's 4px gap to the text. Contributes no text, so first-letter type-ahead still reads the row's words. On a selected row the graphic rides the inverted bar as-is — System 7 left color icons unfiltered on the highlight.\n\n### **CSS Parts:**\n - **text** - The text span beside the icon gutter (ellipsizes).",
473
+ "description": "`<vf-list-item>` — a row inside a `<vf-list>` list box.\n\nA 20px-tall single-line row; when selected the entire row inverts\n(white-on-black), the classic System 7 selection style. Selection and\nkeyboard focus are managed by the parent `<vf-list>`.\n---\n\n\n### **Slots:**\n - _default_ - The row's text/content.\n- **icon** - A leading graphic — usually a `vf-img` holding a 16×16 System 7 small icon, but any element rides here. The row lays it out as the icon gutter: flex-centered vertically (keep the difference between the row height and the icon height even, so the centering offset stays a whole pixel — 16 in a 20px row is a whole 2px), with the reference art's 4px gap to the text. Contributes no text, so first-letter type-ahead still reads the row's words. On a selected row the graphic rides the inverted bar as-is — System 7 left color icons unfiltered on the highlight.\n\n### **CSS Parts:**\n - **text** - The text span beside the icon gutter (ellipsizes). Takes `top`/`left` like every other element ({@link VfPositioned}), for the consumer who wants a row somewhere other than a list. Inside its parent `<vf-list>` the rows stack in flow, so stating an origin lifts that row out of the stack and the ones below it close the gap — the placement doing what it says, but not how a list box is laid out.",
474
474
  "attributes": [
475
475
  {
476
476
  "name": "value",
@@ -486,6 +486,16 @@
486
486
  "name": "disabled",
487
487
  "description": "Disables the row: dimmed text, not selectable or focusable.",
488
488
  "values": []
489
+ },
490
+ {
491
+ "name": "top",
492
+ "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.",
493
+ "values": []
494
+ },
495
+ {
496
+ "name": "left",
497
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
498
+ "values": []
489
499
  }
490
500
  ],
491
501
  "references": []
@@ -556,7 +566,7 @@
556
566
  },
557
567
  {
558
568
  "name": "vf-menu-item",
559
- "description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n---\n\n\n### **Events:**\n - **vf-menu-select** - After the blink completes. `detail: { value, item }`. Named for the menu rather than plain `vf-select`, which would collide with the `<vf-select>` popup on any delegated ancestor listener (that component commits with `vf-change`).\n\n### **Slots:**\n - _default_ - The item label.\n\n### **CSS Properties:**\n - **--vf-menu-row-height** - `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows) _(default: 16px)_\n- **--vf-menu-shortcut-column** - `vf-menu-item` shortcut slot, right-anchored with the text left-aligned in it so every ⌘ lands at the same x (`Menus.png`) — the MDEF reserve, ⌘'s 11px advance + the face's widest letter (M/W, 12px); widen it to line up longer shortcuts (\"⌘⇧S\") _(default: 23px)_\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 - **item** - The row container.\n- **check** - The ✓ checkmark glyph (rendered when `checked`).\n- **label** - The label wrapper around the default slot.\n- **shortcut** - The shortcut text, left-aligned in the shared column.",
569
+ "description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants a row somewhere other than a pulldown. Inside its parent\n`<vf-menu>` the panel is as wide as its widest row and stacks them in flow,\nso stating an origin takes that row out of both: it no longer contributes to\nthe panel's width and the rows below close the gap. The placement doing what\nit says — but not how a pulldown is laid out.\n---\n\n\n### **Events:**\n - **vf-menu-select** - After the blink completes. `detail: { value, item }`. Named for the menu rather than plain `vf-select`, which would collide with the `<vf-select>` popup on any delegated ancestor listener (that component commits with `vf-change`).\n\n### **Slots:**\n - _default_ - The item label.\n\n### **CSS Properties:**\n - **--vf-menu-row-height** - `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows) _(default: 16px)_\n- **--vf-menu-shortcut-column** - `vf-menu-item` shortcut slot, right-anchored with the text left-aligned in it so every ⌘ lands at the same x (`Menus.png`) — the MDEF reserve, ⌘'s 11px advance + the face's widest letter (M/W, 12px); widen it to line up longer shortcuts (\"⌘⇧S\") _(default: 23px)_\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 - **item** - The row container.\n- **check** - The ✓ checkmark glyph (rendered when `checked`).\n- **label** - The label wrapper around the default slot.\n- **shortcut** - The shortcut text, left-aligned in the shared column.",
560
570
  "attributes": [
561
571
  {
562
572
  "name": "disabled",
@@ -587,13 +597,23 @@
587
597
  "name": "value",
588
598
  "description": "Value reported in the `vf-menu-select` event detail. Defaults to the item's\ntrimmed text content when unset.",
589
599
  "values": []
600
+ },
601
+ {
602
+ "name": "top",
603
+ "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.",
604
+ "values": []
605
+ },
606
+ {
607
+ "name": "left",
608
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
609
+ "values": []
590
610
  }
591
611
  ],
592
612
  "references": []
593
613
  },
594
614
  {
595
615
  "name": "vf-menu",
596
- "description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n---\n\n\n### **Slots:**\n - _default_ - Menu contents: `vf-menu-item` and `vf-separator` elements.\n- **label** - Replaces the `label` text in the bar — e.g. a `vf-img` apple icon for the Apple menu. Keep the `label` attribute set too: it stays the menu's accessible name (the bar item's `aria-label` and the panel's) when the visible title is an image.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n- **--vf-menu-row-height** - `vf-menu-item` row pitch; the panel also spends one of these on every slotted `vf-separator`, the full row the MDEF gave a divider (rule 8px into it — see the panel CSS) _(default: 16px)_\n\n### **CSS Parts:**\n - **label** - The menu title in the bar (inverts while open).\n- **panel** - The dropped `.vf-panel` containing the items.",
616
+ "description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n\nTakes `top`/`left` like every other element (VfPositioned) — the\nnatural fit being a standalone menu, which is a free-standing menu button and\nplaces like any other control. The panel is anchored to the host's own box\n(`top: 100%`), so it follows a placed menu down without any further work.\nInside a `<vf-menu-bar>`, the bar lays its titles out in flow — placing one\nlifts it off the bar and the titles beside it close the gap.\n---\n\n\n### **Slots:**\n - _default_ - Menu contents: `vf-menu-item` and `vf-separator` elements.\n- **label** - Replaces the `label` text in the bar — e.g. a `vf-img` apple icon for the Apple menu. Keep the `label` attribute set too: it stays the menu's accessible name (the bar item's `aria-label` and the panel's) when the visible title is an image.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n- **--vf-menu-row-height** - `vf-menu-item` row pitch; the panel also spends one of these on every slotted `vf-separator`, the full row the MDEF gave a divider (rule 8px into it — see the panel CSS) _(default: 16px)_\n\n### **CSS Parts:**\n - **label** - The menu title in the bar (inverts while open).\n- **panel** - The dropped `.vf-panel` containing the items.",
597
617
  "attributes": [
598
618
  {
599
619
  "name": "label",
@@ -604,6 +624,16 @@
604
624
  "name": "open",
605
625
  "description": "Whether the panel is dropped. Reflected. Managed by the parent\n`vf-menu-bar` when present, otherwise by the menu itself.",
606
626
  "values": []
627
+ },
628
+ {
629
+ "name": "top",
630
+ "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.",
631
+ "values": []
632
+ },
633
+ {
634
+ "name": "left",
635
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
636
+ "values": []
607
637
  }
608
638
  ],
609
639
  "references": []
@@ -682,7 +712,7 @@
682
712
  },
683
713
  {
684
714
  "name": "vf-option",
685
- "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---\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.",
715
+ "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.",
686
716
  "attributes": [
687
717
  {
688
718
  "name": "value",
@@ -703,13 +733,23 @@
703
733
  "name": "active",
704
734
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
705
735
  "values": []
736
+ },
737
+ {
738
+ "name": "top",
739
+ "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.",
740
+ "values": []
741
+ },
742
+ {
743
+ "name": "left",
744
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
745
+ "values": []
706
746
  }
707
747
  ],
708
748
  "references": []
709
749
  },
710
750
  {
711
751
  "name": "vf-paragraph",
712
- "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin once the\n page opts in with `applyGridSnap()`;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).\n---\n\n\n### **Slots:**\n - _default_ - The paragraph copy.\n\n### **CSS Properties:**\n - **--vf-paragraph-line-height** - This paragraph kind's own line box, in system px. Unset (the default) the box follows the face tokens below; set, it overrides both faces for paragraphs alone. Keep any override a whole *even* number — a ratio is what puts a page off the grid in the first place, and half the difference to the 16px em is half-leading, so an odd value lands the baseline on a half pixel. _(default: undefined)_\n- **--vf-line-height** - The body face's native line (default `12px`, Geneva 9's) — the face-level knob: retheming the body face to another strike states this alongside `--vf-font-family` / `--vf-font-size`. _(default: undefined)_\n- **--vf-line-height-display** - The display face's native line (default `16px`, Chicago 12's) — the display retheme's third number. _(default: undefined)_\n\n### **CSS Parts:**\n - **paragraph** - The inner `<p>`.",
752
+ "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin\n wherever the page puts it;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).\n---\n\n\n### **Slots:**\n - _default_ - The paragraph copy.\n\n### **CSS Properties:**\n - **--vf-paragraph-line-height** - This paragraph kind's own line box, in system px. Unset (the default) the box follows the face tokens below; set, it overrides both faces for paragraphs alone. Keep any override a whole *even* number — a ratio is what puts a page off the grid in the first place, and half the difference to the 16px em is half-leading, so an odd value lands the baseline on a half pixel. _(default: undefined)_\n- **--vf-line-height** - The body face's native line (default `12px`, Geneva 9's) — the face-level knob: retheming the body face to another strike states this alongside `--vf-font-family` / `--vf-font-size`. _(default: undefined)_\n- **--vf-line-height-display** - The display face's native line (default `16px`, Chicago 12's) — the display retheme's third number. _(default: undefined)_\n\n### **CSS Parts:**\n - **paragraph** - The inner `<p>`.",
713
753
  "attributes": [
714
754
  {
715
755
  "name": "face",
@@ -1014,7 +1054,7 @@
1014
1054
  },
1015
1055
  {
1016
1056
  "name": "vf-stack",
1017
- "description": "`<vf-stack>` — arrange things inside a window, in system pixels.\n\nA window body is the one place the kit stopped short: every control inside it\nis authored in system px, but the *spaces between* them were the consumer's\nproblem, written by hand as `calc(var(--vf-scale, 1) * 12px)`. This is that\ncalculation, as a component — a flexbox whose `gap`, `pad`, `width` and\n`height` are declared in whole system px and converted internally:\n\n```html\n<vf-stack gap=\"12\"> <!-- a column -->\n <vf-stack fill-width direction=\"row\" gap=\"8\"> <!-- a labeled field -->\n <vf-label width=\"80\" for=\"name\">Name:</vf-label>\n <vf-text-field id=\"name\" fill-width></vf-text-field>\n </vf-stack>\n <vf-stack fill-width place=\"end\"> <!-- an action row -->\n <vf-button-group>\n <vf-button>Cancel</vf-button>\n <vf-button variant=\"default\">Save</vf-button>\n </vf-button-group>\n </vf-stack>\n</vf-stack>\n```\n\n**Why this can't just be page CSS.** Scaling is default-on and *per\ncomponent*: `ScaleController` sets `--vf-scale` on the component's own host,\nnot on the document. So `var(--vf-scale, 1)` in a consumer's stylesheet\nresolves only where the rule's element happens to sit inside a `vf-*`\nancestor and inherit it — true inside a window body, false for a plain\n`<div>` holding two buttons on an ordinary page, where the fallback `1` wins\nand the gap renders at 8px around 3×-sized buttons with no warning. A page\nthat hasn't called `applyScale()` has no way to write \"8 system px\" at all.\nA component always can, because it *is* the scope — which is also why this\none carries a ScaleController of its own: without it a lone stack\nwould resolve its own gap against that same fallback while its children each\nself-scaled around it.\n\n**Whole system px is the only expressible value**, so the gap half of the\nlayout contract (docs/SIZING.md rule 2) stops being a rule to remember. Declaring\n`width`/`height` covers the size half of rule 3 as well.\n\n**The geometry is governed by the content.** A column is as wide as its\nwidest child and a row as tall as its tallest; children keep the size they\ndrew themselves at (`flex: 0 0 auto` — no growing, no shrinking). System 7\nboxes are the size they are: a push button is as wide as its label, a popup\nmenu hugs its widest option, and a window is a fixed box whose overflow is\nclipped at the frame, not a layout that squeezes its controls to fit. The\nstack distributes; it does not resize. That is why the box shrink-wraps\n(`fit-content`) rather than claiming its parent's whole width — a layout box\nthat did would be inventing a size nobody declared.\n\n**`fill-width` / `fill-height` are how a child asks for more**, as bare\nattributes on consumer DOM the way `nosnap` opts an element out of snapping:\n\n```html\n<vf-text-field fill-width></vf-text-field>\n```\n\nEach names the *outcome*, not the axis, so the markup means the same thing\nwherever it lands; the stack does the flexbox translation, which is the whole\nreason to have a component. One rule to learn, about geometry rather than\nvocabulary: **the cross axis always has a size, the main axis only has slack\nif you declared one.** So `fill-width` always works in a column (the width is\nthe widest child's) and needs a declared `width` in a row; `fill-height` is\nexactly the other way round. A fill with nothing to take is inert, not an\nerror. Two children filling along the main axis end up *equal* — the zeroed\nflex basis is what lets them divide the slack rather than keep their natural\nsizes — and a child that declares its own size shouldn't also ask to fill it.\n\nA stack reads the same two attributes about *itself*, for the parents that\naren't stacks: a window body, a fieldset, a scroll well, a grid cell. That is\nwhere a panel's width enters the tree, and from there `fill-width` hands it\ndown a level at a time.\n\n**It paints nothing and means nothing.** No border, no background, no role,\nno keyboard behavior — what it holds decides what it is, as with\nVfGrid.\n\n**It holds its box on the device-pixel grid** — the `vf-container`\narrangement, adopted here after first shipping without it. The original\nreasoning (\"no ink of its own, and slotted `vf-*` children correct their own\norigins\") accounted only for kit children: a stack is a positioned ancestor\nand a layout box for *anything*, and consumer content inside it — a div, an\n`<img>`, a run of text — has no controller of its own. So the flex\ncontainer, the placed-child anchor and the `vf-snap` class live on one\nshadow box, and under `applyGridSnap()` the correction moves the stack's\nwhole coordinate system; children (kit or not) ride it, and the sweep's\noutermost-first order means the kit children then find nothing left to\ncorrect. What this deliberately does not fix: a text-governed child width\nmid-row still shifts later siblings fractionally (their own controllers\ncover that), and centering still can't land on a whole pixel by itself.\n\nIt is also the kit's one **typographically transparent** component: `vfBase`'s\nchrome face, ratio line box, color and non-selectability are all reset to\n`inherit` on the host. Wrapping content in a layout box must not change how\nthat content reads — inside a window it goes on inheriting the window's face\nexactly as before, and on a plain page it leaves the page's typography (and\nits whole-pixel line boxes) alone.\n\nWhat it deliberately does **not** do: equalize its children's widths. A row of\nbuttons still belongs in a `vf-button-group`, which sizes them all to the\nwidest and aligns their *faces* rather than the `variant=\"default\"` ring\nboxes a plain flex row would line up.\n---\n\n\n### **Slots:**\n - _default_ - The children to arrange. `fill-width` / `fill-height` on any of them.",
1057
+ "description": "`<vf-stack>` — arrange things inside a window, in system pixels.\n\nA window body is the one place the kit stopped short: every control inside it\nis authored in system px, but the *spaces between* them were the consumer's\nproblem, written by hand as `calc(var(--vf-scale, 1) * 12px)`. This is that\ncalculation, as a component — a flexbox whose `gap`, `pad`, `width` and\n`height` are declared in whole system px and converted internally:\n\n```html\n<vf-stack gap=\"12\"> <!-- a column -->\n <vf-stack fill-width direction=\"row\" gap=\"8\"> <!-- a labeled field -->\n <vf-label width=\"80\" for=\"name\">Name:</vf-label>\n <vf-text-field id=\"name\" fill-width></vf-text-field>\n </vf-stack>\n <vf-stack fill-width place=\"end\"> <!-- an action row -->\n <vf-button-group>\n <vf-button>Cancel</vf-button>\n <vf-button variant=\"default\">Save</vf-button>\n </vf-button-group>\n </vf-stack>\n</vf-stack>\n```\n\n**Why this can't just be page CSS.** Scaling is default-on and *per\ncomponent*: `ScaleController` sets `--vf-scale` on the component's own host,\nnot on the document. So `var(--vf-scale, 1)` in a consumer's stylesheet\nresolves only where the rule's element happens to sit inside a `vf-*`\nancestor and inherit it — true inside a window body, false for a plain\n`<div>` holding two buttons on an ordinary page, where the fallback `1` wins\nand the gap renders at 8px around 3×-sized buttons with no warning. A page\nthat hasn't called `applyScale()` has no way to write \"8 system px\" at all.\nA component always can, because it *is* the scope — which is also why this\none carries a ScaleController of its own: without it a lone stack\nwould resolve its own gap against that same fallback while its children each\nself-scaled around it.\n\n**Whole system px is the only expressible value**, so the gap half of the\nlayout contract (docs/SIZING.md rule 2) stops being a rule to remember. Declaring\n`width`/`height` covers the size half of rule 3 as well.\n\n**The geometry is governed by the content.** A column is as wide as its\nwidest child and a row as tall as its tallest; children keep the size they\ndrew themselves at (`flex: 0 0 auto` — no growing, no shrinking). System 7\nboxes are the size they are: a push button is as wide as its label, a popup\nmenu hugs its widest option, and a window is a fixed box whose overflow is\nclipped at the frame, not a layout that squeezes its controls to fit. The\nstack distributes; it does not resize. That is why the box shrink-wraps\n(`fit-content`) rather than claiming its parent's whole width — a layout box\nthat did would be inventing a size nobody declared.\n\n**`fill-width` / `fill-height` are how a child asks for more**, as bare\nattributes on consumer DOM the way `nosnap` opts an element out of snapping:\n\n```html\n<vf-text-field fill-width></vf-text-field>\n```\n\nEach names the *outcome*, not the axis, so the markup means the same thing\nwherever it lands; the stack does the flexbox translation, which is the whole\nreason to have a component. One rule to learn, about geometry rather than\nvocabulary: **the cross axis always has a size, the main axis only has slack\nif you declared one.** So `fill-width` always works in a column (the width is\nthe widest child's) and needs a declared `width` in a row; `fill-height` is\nexactly the other way round. A fill with nothing to take is inert, not an\nerror. Two children filling along the main axis end up *equal* — the zeroed\nflex basis is what lets them divide the slack rather than keep their natural\nsizes — and a child that declares its own size shouldn't also ask to fill it.\n\nA stack reads the same two attributes about *itself*, for the parents that\naren't stacks: a window body, a fieldset, a scroll well, a grid cell. That is\nwhere a panel's width enters the tree, and from there `fill-width` hands it\ndown a level at a time.\n\n**It paints nothing and means nothing.** No border, no background, no role,\nno keyboard behavior — what it holds decides what it is, as with\nVfGrid.\n\n**It holds its box on the device-pixel grid** — the `vf-container`\narrangement, adopted here after first shipping without it. The original\nreasoning (\"no ink of its own, and slotted `vf-*` children correct their own\norigins\") accounted only for kit children: a stack is a positioned ancestor\nand a layout box for *anything*, and consumer content inside it — a div, an\n`<img>`, a run of text — has no controller of its own. So the flex\ncontainer, the placed-child anchor and the `vf-snap` class live on one\nshadow box, and the correction moves the stack's\nwhole coordinate system; children (kit or not) ride it, and the sweep's\noutermost-first order means the kit children then find nothing left to\ncorrect. What this deliberately does not fix: a text-governed child width\nmid-row still shifts later siblings fractionally (their own controllers\ncover that).\n\n**Centering lands on whole system px as well** — halving an odd count of free\nsystem px gives a half, which no container can round in CSS. See\nCrossCenterController.\n\nIt is also the kit's one **typographically transparent** component: `vfBase`'s\nchrome face, ratio line box, color and non-selectability are all reset to\n`inherit` on the host. Wrapping content in a layout box must not change how\nthat content reads — inside a window it goes on inheriting the window's face\nexactly as before, and on a plain page it leaves the page's typography (and\nits whole-pixel line boxes) alone.\n\nWhat it deliberately does **not** do: equalize its children's widths. A row of\nbuttons still belongs in a `vf-button-group`, which sizes them all to the\nwidest and aligns their *faces* rather than the `variant=\"default\"` ring\nboxes a plain flex row would line up.\n---\n\n\n### **Slots:**\n - _default_ - The children to arrange. `fill-width` / `fill-height` on any of them.",
1018
1058
  "attributes": [
1019
1059
  {
1020
1060
  "name": "direction",
@@ -1033,7 +1073,7 @@
1033
1073
  },
1034
1074
  {
1035
1075
  "name": "place",
1036
- "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nNote the one thing centering cannot do: land on a whole pixel by itself. A\n16px caption centered in a row set by the 25-system-px `vf-number-field`\nsits at 4.5 system px, and no container can round that it would have to\nread each child's height. `applyGridSnap()` keeps the caption's own ink\ncrisp regardless (it corrects the origin inside the child's shadow root);\n`place=\"start\"` is the deterministic escape.",
1076
+ "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nCentering divides the free space in two, so an odd count of system px would\nland a child on a half a 16px caption centered in a row set by the\n25-system-px `vf-number-field` sits at 4.5. CrossCenterController\nsteps that back onto whole system px, the exact half going toward the start\nthe way QuickDraw's `div 2` did; `place=\"start\"` is still the way to ask\nfor no centering at all.",
1037
1077
  "values": [{ "name": "VfStackPlace" }]
1038
1078
  },
1039
1079
  {
@@ -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.1.0",
4
+ "version": "0.2.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -378,7 +378,7 @@
378
378
  },
379
379
  {
380
380
  "name": "vf-container",
381
- "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.** No border, background, role,\nkeyboard behavior or selection — what it holds decides what it is.\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 under `applyGridSnap()` 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.",
381
+ "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.** No border, background, role,\nkeyboard behavior or selection — what it holds decides what it is.\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.",
382
382
  "doc-url": "",
383
383
  "attributes": [
384
384
  {
@@ -1103,7 +1103,7 @@
1103
1103
  },
1104
1104
  {
1105
1105
  "name": "vf-list-item",
1106
- "description": "`<vf-list-item>` — a row inside a `<vf-list>` list box.\n\nA 20px-tall single-line row; when selected the entire row inverts\n(white-on-black), the classic System 7 selection style. Selection and\nkeyboard focus are managed by the parent `<vf-list>`.\n---\n\n\n### **Slots:**\n - _default_ - The row's text/content.\n- **icon** - A leading graphic — usually a `vf-img` holding a 16×16 System 7 small icon, but any element rides here. The row lays it out as the icon gutter: flex-centered vertically (keep the difference between the row height and the icon height even, so the centering offset stays a whole pixel — 16 in a 20px row is a whole 2px), with the reference art's 4px gap to the text. Contributes no text, so first-letter type-ahead still reads the row's words. On a selected row the graphic rides the inverted bar as-is — System 7 left color icons unfiltered on the highlight.\n\n### **CSS Parts:**\n - **text** - The text span beside the icon gutter (ellipsizes).",
1106
+ "description": "`<vf-list-item>` — a row inside a `<vf-list>` list box.\n\nA 20px-tall single-line row; when selected the entire row inverts\n(white-on-black), the classic System 7 selection style. Selection and\nkeyboard focus are managed by the parent `<vf-list>`.\n---\n\n\n### **Slots:**\n - _default_ - The row's text/content.\n- **icon** - A leading graphic — usually a `vf-img` holding a 16×16 System 7 small icon, but any element rides here. The row lays it out as the icon gutter: flex-centered vertically (keep the difference between the row height and the icon height even, so the centering offset stays a whole pixel — 16 in a 20px row is a whole 2px), with the reference art's 4px gap to the text. Contributes no text, so first-letter type-ahead still reads the row's words. On a selected row the graphic rides the inverted bar as-is — System 7 left color icons unfiltered on the highlight.\n\n### **CSS Parts:**\n - **text** - The text span beside the icon gutter (ellipsizes). Takes `top`/`left` like every other element ({@link VfPositioned}), for the consumer who wants a row somewhere other than a list. Inside its parent `<vf-list>` the rows stack in flow, so stating an origin lifts that row out of the stack and the ones below it close the gap — the placement doing what it says, but not how a list box is laid out.",
1107
1107
  "doc-url": "",
1108
1108
  "attributes": [
1109
1109
  {
@@ -1120,6 +1120,16 @@
1120
1120
  "name": "disabled",
1121
1121
  "description": "Disables the row: dimmed text, not selectable or focusable.",
1122
1122
  "value": { "type": "boolean", "default": "false" }
1123
+ },
1124
+ {
1125
+ "name": "top",
1126
+ "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.",
1127
+ "value": { "type": "number | null | undefined" }
1128
+ },
1129
+ {
1130
+ "name": "left",
1131
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1132
+ "value": { "type": "number | null | undefined" }
1123
1133
  }
1124
1134
  ],
1125
1135
  "slots": [
@@ -1152,7 +1162,17 @@
1152
1162
  "description": "True while the containing `<vf-list>` is disabled. Managed by the list —\nnot intended to be set by consumers. Kept distinct from `disabled` so\nre-enabling the list doesn't clear rows that are disabled in their own\nright. (The dimming already arrives by inheritance: the list host sets\n`color: var(--vf-disabled)` and rows inherit it.) Mirrors\n`vf-radio.groupDisabled`.",
1153
1163
  "type": "boolean"
1154
1164
  },
1155
- { "name": "role", "type": "string" }
1165
+ { "name": "role", "type": "string" },
1166
+ {
1167
+ "name": "top",
1168
+ "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.",
1169
+ "type": "number | null | undefined"
1170
+ },
1171
+ {
1172
+ "name": "left",
1173
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1174
+ "type": "number | null | undefined"
1175
+ }
1156
1176
  ],
1157
1177
  "events": []
1158
1178
  }
@@ -1303,7 +1323,7 @@
1303
1323
  },
1304
1324
  {
1305
1325
  "name": "vf-menu-item",
1306
- "description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n---\n\n\n### **Events:**\n - **vf-menu-select** - After the blink completes. `detail: { value, item }`. Named for the menu rather than plain `vf-select`, which would collide with the `<vf-select>` popup on any delegated ancestor listener (that component commits with `vf-change`).\n\n### **Slots:**\n - _default_ - The item label.\n\n### **CSS Properties:**\n - **--vf-menu-row-height** - `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows) _(default: 16px)_\n- **--vf-menu-shortcut-column** - `vf-menu-item` shortcut slot, right-anchored with the text left-aligned in it so every ⌘ lands at the same x (`Menus.png`) — the MDEF reserve, ⌘'s 11px advance + the face's widest letter (M/W, 12px); widen it to line up longer shortcuts (\"⌘⇧S\") _(default: 23px)_\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 - **item** - The row container.\n- **check** - The ✓ checkmark glyph (rendered when `checked`).\n- **label** - The label wrapper around the default slot.\n- **shortcut** - The shortcut text, left-aligned in the shared column.",
1326
+ "description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants a row somewhere other than a pulldown. Inside its parent\n`<vf-menu>` the panel is as wide as its widest row and stacks them in flow,\nso stating an origin takes that row out of both: it no longer contributes to\nthe panel's width and the rows below close the gap. The placement doing what\nit says — but not how a pulldown is laid out.\n---\n\n\n### **Events:**\n - **vf-menu-select** - After the blink completes. `detail: { value, item }`. Named for the menu rather than plain `vf-select`, which would collide with the `<vf-select>` popup on any delegated ancestor listener (that component commits with `vf-change`).\n\n### **Slots:**\n - _default_ - The item label.\n\n### **CSS Properties:**\n - **--vf-menu-row-height** - `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows) _(default: 16px)_\n- **--vf-menu-shortcut-column** - `vf-menu-item` shortcut slot, right-anchored with the text left-aligned in it so every ⌘ lands at the same x (`Menus.png`) — the MDEF reserve, ⌘'s 11px advance + the face's widest letter (M/W, 12px); widen it to line up longer shortcuts (\"⌘⇧S\") _(default: 23px)_\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 - **item** - The row container.\n- **check** - The ✓ checkmark glyph (rendered when `checked`).\n- **label** - The label wrapper around the default slot.\n- **shortcut** - The shortcut text, left-aligned in the shared column.",
1307
1327
  "doc-url": "",
1308
1328
  "attributes": [
1309
1329
  {
@@ -1335,6 +1355,16 @@
1335
1355
  "name": "value",
1336
1356
  "description": "Value reported in the `vf-menu-select` event detail. Defaults to the item's\ntrimmed text content when unset.",
1337
1357
  "value": { "type": "string | undefined" }
1358
+ },
1359
+ {
1360
+ "name": "top",
1361
+ "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.",
1362
+ "value": { "type": "number | null | undefined" }
1363
+ },
1364
+ {
1365
+ "name": "left",
1366
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1367
+ "value": { "type": "number | null | undefined" }
1338
1368
  }
1339
1369
  ],
1340
1370
  "slots": [{ "name": "", "description": "The item label." }],
@@ -1375,6 +1405,16 @@
1375
1405
  "name": "value",
1376
1406
  "description": "Value reported in the `vf-menu-select` event detail. Defaults to the item's\ntrimmed text content when unset.",
1377
1407
  "type": "string | undefined"
1408
+ },
1409
+ {
1410
+ "name": "top",
1411
+ "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.",
1412
+ "type": "number | null | undefined"
1413
+ },
1414
+ {
1415
+ "name": "left",
1416
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1417
+ "type": "number | null | undefined"
1378
1418
  }
1379
1419
  ],
1380
1420
  "events": [
@@ -1387,7 +1427,7 @@
1387
1427
  },
1388
1428
  {
1389
1429
  "name": "vf-menu",
1390
- "description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n---\n\n\n### **Slots:**\n - _default_ - Menu contents: `vf-menu-item` and `vf-separator` elements.\n- **label** - Replaces the `label` text in the bar — e.g. a `vf-img` apple icon for the Apple menu. Keep the `label` attribute set too: it stays the menu's accessible name (the bar item's `aria-label` and the panel's) when the visible title is an image.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n- **--vf-menu-row-height** - `vf-menu-item` row pitch; the panel also spends one of these on every slotted `vf-separator`, the full row the MDEF gave a divider (rule 8px into it — see the panel CSS) _(default: 16px)_\n\n### **CSS Parts:**\n - **label** - The menu title in the bar (inverts while open).\n- **panel** - The dropped `.vf-panel` containing the items.",
1430
+ "description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n\nTakes `top`/`left` like every other element (VfPositioned) — the\nnatural fit being a standalone menu, which is a free-standing menu button and\nplaces like any other control. The panel is anchored to the host's own box\n(`top: 100%`), so it follows a placed menu down without any further work.\nInside a `<vf-menu-bar>`, the bar lays its titles out in flow — placing one\nlifts it off the bar and the titles beside it close the gap.\n---\n\n\n### **Slots:**\n - _default_ - Menu contents: `vf-menu-item` and `vf-separator` elements.\n- **label** - Replaces the `label` text in the bar — e.g. a `vf-img` apple icon for the Apple menu. Keep the `label` attribute set too: it stays the menu's accessible name (the bar item's `aria-label` and the panel's) when the visible title is an image.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n- **--vf-menu-row-height** - `vf-menu-item` row pitch; the panel also spends one of these on every slotted `vf-separator`, the full row the MDEF gave a divider (rule 8px into it — see the panel CSS) _(default: 16px)_\n\n### **CSS Parts:**\n - **label** - The menu title in the bar (inverts while open).\n- **panel** - The dropped `.vf-panel` containing the items.",
1391
1431
  "doc-url": "",
1392
1432
  "attributes": [
1393
1433
  {
@@ -1399,6 +1439,16 @@
1399
1439
  "name": "open",
1400
1440
  "description": "Whether the panel is dropped. Reflected. Managed by the parent\n`vf-menu-bar` when present, otherwise by the menu itself.",
1401
1441
  "value": { "type": "boolean", "default": "false" }
1442
+ },
1443
+ {
1444
+ "name": "top",
1445
+ "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.",
1446
+ "value": { "type": "number | null | undefined" }
1447
+ },
1448
+ {
1449
+ "name": "left",
1450
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1451
+ "value": { "type": "number | null | undefined" }
1402
1452
  }
1403
1453
  ],
1404
1454
  "slots": [
@@ -1443,6 +1493,16 @@
1443
1493
  "name": "labelRect",
1444
1494
  "description": "Viewport rect of the bar title, or `null` before the first render. The\npress gesture hit-tests by coordinates rather than by event target (see\nsrc/menu-press.ts), so it wants the box, not the element.",
1445
1495
  "type": "DOMRect | null"
1496
+ },
1497
+ {
1498
+ "name": "top",
1499
+ "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.",
1500
+ "type": "number | null | undefined"
1501
+ },
1502
+ {
1503
+ "name": "left",
1504
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1505
+ "type": "number | null | undefined"
1446
1506
  }
1447
1507
  ],
1448
1508
  "events": []
@@ -1647,7 +1707,7 @@
1647
1707
  },
1648
1708
  {
1649
1709
  "name": "vf-option",
1650
- "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---\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.",
1710
+ "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.",
1651
1711
  "doc-url": "",
1652
1712
  "attributes": [
1653
1713
  {
@@ -1669,6 +1729,16 @@
1669
1729
  "name": "active",
1670
1730
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
1671
1731
  "value": { "type": "boolean", "default": "false" }
1732
+ },
1733
+ {
1734
+ "name": "top",
1735
+ "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.",
1736
+ "value": { "type": "number | null | undefined" }
1737
+ },
1738
+ {
1739
+ "name": "left",
1740
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1741
+ "value": { "type": "number | null | undefined" }
1672
1742
  }
1673
1743
  ],
1674
1744
  "events": [],
@@ -1694,14 +1764,24 @@
1694
1764
  "description": "Transient highlight (hover / keyboard cursor) — full-row inversion.\nManaged by the parent `<vf-select>`; not part of the authoring API.",
1695
1765
  "type": "boolean"
1696
1766
  },
1697
- { "name": "role", "type": "string" }
1767
+ { "name": "role", "type": "string" },
1768
+ {
1769
+ "name": "top",
1770
+ "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.",
1771
+ "type": "number | null | undefined"
1772
+ },
1773
+ {
1774
+ "name": "left",
1775
+ "description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
1776
+ "type": "number | null | undefined"
1777
+ }
1698
1778
  ],
1699
1779
  "events": []
1700
1780
  }
1701
1781
  },
1702
1782
  {
1703
1783
  "name": "vf-paragraph",
1704
- "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin once the\n page opts in with `applyGridSnap()`;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).\n---\n\n\n### **Slots:**\n - _default_ - The paragraph copy.\n\n### **CSS Properties:**\n - **--vf-paragraph-line-height** - This paragraph kind's own line box, in system px. Unset (the default) the box follows the face tokens below; set, it overrides both faces for paragraphs alone. Keep any override a whole *even* number — a ratio is what puts a page off the grid in the first place, and half the difference to the 16px em is half-leading, so an odd value lands the baseline on a half pixel. _(default: undefined)_\n- **--vf-line-height** - The body face's native line (default `12px`, Geneva 9's) — the face-level knob: retheming the body face to another strike states this alongside `--vf-font-family` / `--vf-font-size`. _(default: undefined)_\n- **--vf-line-height-display** - The display face's native line (default `16px`, Chicago 12's) — the display retheme's third number. _(default: undefined)_\n\n### **CSS Parts:**\n - **paragraph** - The inner `<p>`.",
1784
+ "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin\n wherever the page puts it;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).\n---\n\n\n### **Slots:**\n - _default_ - The paragraph copy.\n\n### **CSS Properties:**\n - **--vf-paragraph-line-height** - This paragraph kind's own line box, in system px. Unset (the default) the box follows the face tokens below; set, it overrides both faces for paragraphs alone. Keep any override a whole *even* number — a ratio is what puts a page off the grid in the first place, and half the difference to the 16px em is half-leading, so an odd value lands the baseline on a half pixel. _(default: undefined)_\n- **--vf-line-height** - The body face's native line (default `12px`, Geneva 9's) — the face-level knob: retheming the body face to another strike states this alongside `--vf-font-family` / `--vf-font-size`. _(default: undefined)_\n- **--vf-line-height-display** - The display face's native line (default `16px`, Chicago 12's) — the display retheme's third number. _(default: undefined)_\n\n### **CSS Parts:**\n - **paragraph** - The inner `<p>`.",
1705
1785
  "doc-url": "",
1706
1786
  "attributes": [
1707
1787
  {
@@ -2481,7 +2561,7 @@
2481
2561
  },
2482
2562
  {
2483
2563
  "name": "vf-stack",
2484
- "description": "`<vf-stack>` — arrange things inside a window, in system pixels.\n\nA window body is the one place the kit stopped short: every control inside it\nis authored in system px, but the *spaces between* them were the consumer's\nproblem, written by hand as `calc(var(--vf-scale, 1) * 12px)`. This is that\ncalculation, as a component — a flexbox whose `gap`, `pad`, `width` and\n`height` are declared in whole system px and converted internally:\n\n```html\n<vf-stack gap=\"12\"> <!-- a column -->\n <vf-stack fill-width direction=\"row\" gap=\"8\"> <!-- a labeled field -->\n <vf-label width=\"80\" for=\"name\">Name:</vf-label>\n <vf-text-field id=\"name\" fill-width></vf-text-field>\n </vf-stack>\n <vf-stack fill-width place=\"end\"> <!-- an action row -->\n <vf-button-group>\n <vf-button>Cancel</vf-button>\n <vf-button variant=\"default\">Save</vf-button>\n </vf-button-group>\n </vf-stack>\n</vf-stack>\n```\n\n**Why this can't just be page CSS.** Scaling is default-on and *per\ncomponent*: `ScaleController` sets `--vf-scale` on the component's own host,\nnot on the document. So `var(--vf-scale, 1)` in a consumer's stylesheet\nresolves only where the rule's element happens to sit inside a `vf-*`\nancestor and inherit it — true inside a window body, false for a plain\n`<div>` holding two buttons on an ordinary page, where the fallback `1` wins\nand the gap renders at 8px around 3×-sized buttons with no warning. A page\nthat hasn't called `applyScale()` has no way to write \"8 system px\" at all.\nA component always can, because it *is* the scope — which is also why this\none carries a ScaleController of its own: without it a lone stack\nwould resolve its own gap against that same fallback while its children each\nself-scaled around it.\n\n**Whole system px is the only expressible value**, so the gap half of the\nlayout contract (docs/SIZING.md rule 2) stops being a rule to remember. Declaring\n`width`/`height` covers the size half of rule 3 as well.\n\n**The geometry is governed by the content.** A column is as wide as its\nwidest child and a row as tall as its tallest; children keep the size they\ndrew themselves at (`flex: 0 0 auto` — no growing, no shrinking). System 7\nboxes are the size they are: a push button is as wide as its label, a popup\nmenu hugs its widest option, and a window is a fixed box whose overflow is\nclipped at the frame, not a layout that squeezes its controls to fit. The\nstack distributes; it does not resize. That is why the box shrink-wraps\n(`fit-content`) rather than claiming its parent's whole width — a layout box\nthat did would be inventing a size nobody declared.\n\n**`fill-width` / `fill-height` are how a child asks for more**, as bare\nattributes on consumer DOM the way `nosnap` opts an element out of snapping:\n\n```html\n<vf-text-field fill-width></vf-text-field>\n```\n\nEach names the *outcome*, not the axis, so the markup means the same thing\nwherever it lands; the stack does the flexbox translation, which is the whole\nreason to have a component. One rule to learn, about geometry rather than\nvocabulary: **the cross axis always has a size, the main axis only has slack\nif you declared one.** So `fill-width` always works in a column (the width is\nthe widest child's) and needs a declared `width` in a row; `fill-height` is\nexactly the other way round. A fill with nothing to take is inert, not an\nerror. Two children filling along the main axis end up *equal* — the zeroed\nflex basis is what lets them divide the slack rather than keep their natural\nsizes — and a child that declares its own size shouldn't also ask to fill it.\n\nA stack reads the same two attributes about *itself*, for the parents that\naren't stacks: a window body, a fieldset, a scroll well, a grid cell. That is\nwhere a panel's width enters the tree, and from there `fill-width` hands it\ndown a level at a time.\n\n**It paints nothing and means nothing.** No border, no background, no role,\nno keyboard behavior — what it holds decides what it is, as with\nVfGrid.\n\n**It holds its box on the device-pixel grid** — the `vf-container`\narrangement, adopted here after first shipping without it. The original\nreasoning (\"no ink of its own, and slotted `vf-*` children correct their own\norigins\") accounted only for kit children: a stack is a positioned ancestor\nand a layout box for *anything*, and consumer content inside it — a div, an\n`<img>`, a run of text — has no controller of its own. So the flex\ncontainer, the placed-child anchor and the `vf-snap` class live on one\nshadow box, and under `applyGridSnap()` the correction moves the stack's\nwhole coordinate system; children (kit or not) ride it, and the sweep's\noutermost-first order means the kit children then find nothing left to\ncorrect. What this deliberately does not fix: a text-governed child width\nmid-row still shifts later siblings fractionally (their own controllers\ncover that), and centering still can't land on a whole pixel by itself.\n\nIt is also the kit's one **typographically transparent** component: `vfBase`'s\nchrome face, ratio line box, color and non-selectability are all reset to\n`inherit` on the host. Wrapping content in a layout box must not change how\nthat content reads — inside a window it goes on inheriting the window's face\nexactly as before, and on a plain page it leaves the page's typography (and\nits whole-pixel line boxes) alone.\n\nWhat it deliberately does **not** do: equalize its children's widths. A row of\nbuttons still belongs in a `vf-button-group`, which sizes them all to the\nwidest and aligns their *faces* rather than the `variant=\"default\"` ring\nboxes a plain flex row would line up.\n---\n\n\n### **Slots:**\n - _default_ - The children to arrange. `fill-width` / `fill-height` on any of them.",
2564
+ "description": "`<vf-stack>` — arrange things inside a window, in system pixels.\n\nA window body is the one place the kit stopped short: every control inside it\nis authored in system px, but the *spaces between* them were the consumer's\nproblem, written by hand as `calc(var(--vf-scale, 1) * 12px)`. This is that\ncalculation, as a component — a flexbox whose `gap`, `pad`, `width` and\n`height` are declared in whole system px and converted internally:\n\n```html\n<vf-stack gap=\"12\"> <!-- a column -->\n <vf-stack fill-width direction=\"row\" gap=\"8\"> <!-- a labeled field -->\n <vf-label width=\"80\" for=\"name\">Name:</vf-label>\n <vf-text-field id=\"name\" fill-width></vf-text-field>\n </vf-stack>\n <vf-stack fill-width place=\"end\"> <!-- an action row -->\n <vf-button-group>\n <vf-button>Cancel</vf-button>\n <vf-button variant=\"default\">Save</vf-button>\n </vf-button-group>\n </vf-stack>\n</vf-stack>\n```\n\n**Why this can't just be page CSS.** Scaling is default-on and *per\ncomponent*: `ScaleController` sets `--vf-scale` on the component's own host,\nnot on the document. So `var(--vf-scale, 1)` in a consumer's stylesheet\nresolves only where the rule's element happens to sit inside a `vf-*`\nancestor and inherit it — true inside a window body, false for a plain\n`<div>` holding two buttons on an ordinary page, where the fallback `1` wins\nand the gap renders at 8px around 3×-sized buttons with no warning. A page\nthat hasn't called `applyScale()` has no way to write \"8 system px\" at all.\nA component always can, because it *is* the scope — which is also why this\none carries a ScaleController of its own: without it a lone stack\nwould resolve its own gap against that same fallback while its children each\nself-scaled around it.\n\n**Whole system px is the only expressible value**, so the gap half of the\nlayout contract (docs/SIZING.md rule 2) stops being a rule to remember. Declaring\n`width`/`height` covers the size half of rule 3 as well.\n\n**The geometry is governed by the content.** A column is as wide as its\nwidest child and a row as tall as its tallest; children keep the size they\ndrew themselves at (`flex: 0 0 auto` — no growing, no shrinking). System 7\nboxes are the size they are: a push button is as wide as its label, a popup\nmenu hugs its widest option, and a window is a fixed box whose overflow is\nclipped at the frame, not a layout that squeezes its controls to fit. The\nstack distributes; it does not resize. That is why the box shrink-wraps\n(`fit-content`) rather than claiming its parent's whole width — a layout box\nthat did would be inventing a size nobody declared.\n\n**`fill-width` / `fill-height` are how a child asks for more**, as bare\nattributes on consumer DOM the way `nosnap` opts an element out of snapping:\n\n```html\n<vf-text-field fill-width></vf-text-field>\n```\n\nEach names the *outcome*, not the axis, so the markup means the same thing\nwherever it lands; the stack does the flexbox translation, which is the whole\nreason to have a component. One rule to learn, about geometry rather than\nvocabulary: **the cross axis always has a size, the main axis only has slack\nif you declared one.** So `fill-width` always works in a column (the width is\nthe widest child's) and needs a declared `width` in a row; `fill-height` is\nexactly the other way round. A fill with nothing to take is inert, not an\nerror. Two children filling along the main axis end up *equal* — the zeroed\nflex basis is what lets them divide the slack rather than keep their natural\nsizes — and a child that declares its own size shouldn't also ask to fill it.\n\nA stack reads the same two attributes about *itself*, for the parents that\naren't stacks: a window body, a fieldset, a scroll well, a grid cell. That is\nwhere a panel's width enters the tree, and from there `fill-width` hands it\ndown a level at a time.\n\n**It paints nothing and means nothing.** No border, no background, no role,\nno keyboard behavior — what it holds decides what it is, as with\nVfGrid.\n\n**It holds its box on the device-pixel grid** — the `vf-container`\narrangement, adopted here after first shipping without it. The original\nreasoning (\"no ink of its own, and slotted `vf-*` children correct their own\norigins\") accounted only for kit children: a stack is a positioned ancestor\nand a layout box for *anything*, and consumer content inside it — a div, an\n`<img>`, a run of text — has no controller of its own. So the flex\ncontainer, the placed-child anchor and the `vf-snap` class live on one\nshadow box, and the correction moves the stack's\nwhole coordinate system; children (kit or not) ride it, and the sweep's\noutermost-first order means the kit children then find nothing left to\ncorrect. What this deliberately does not fix: a text-governed child width\nmid-row still shifts later siblings fractionally (their own controllers\ncover that).\n\n**Centering lands on whole system px as well** — halving an odd count of free\nsystem px gives a half, which no container can round in CSS. See\nCrossCenterController.\n\nIt is also the kit's one **typographically transparent** component: `vfBase`'s\nchrome face, ratio line box, color and non-selectability are all reset to\n`inherit` on the host. Wrapping content in a layout box must not change how\nthat content reads — inside a window it goes on inheriting the window's face\nexactly as before, and on a plain page it leaves the page's typography (and\nits whole-pixel line boxes) alone.\n\nWhat it deliberately does **not** do: equalize its children's widths. A row of\nbuttons still belongs in a `vf-button-group`, which sizes them all to the\nwidest and aligns their *faces* rather than the `variant=\"default\"` ring\nboxes a plain flex row would line up.\n---\n\n\n### **Slots:**\n - _default_ - The children to arrange. `fill-width` / `fill-height` on any of them.",
2485
2565
  "doc-url": "",
2486
2566
  "attributes": [
2487
2567
  {
@@ -2501,7 +2581,7 @@
2501
2581
  },
2502
2582
  {
2503
2583
  "name": "place",
2504
- "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nNote the one thing centering cannot do: land on a whole pixel by itself. A\n16px caption centered in a row set by the 25-system-px `vf-number-field`\nsits at 4.5 system px, and no container can round that it would have to\nread each child's height. `applyGridSnap()` keeps the caption's own ink\ncrisp regardless (it corrects the origin inside the child's shadow root);\n`place=\"start\"` is the deterministic escape.",
2584
+ "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nCentering divides the free space in two, so an odd count of system px would\nland a child on a half a 16px caption centered in a row set by the\n25-system-px `vf-number-field` sits at 4.5. CrossCenterController\nsteps that back onto whole system px, the exact half going toward the start\nthe way QuickDraw's `div 2` did; `place=\"start\"` is still the way to ask\nfor no centering at all.",
2505
2585
  "value": { "type": "VfStackPlace | undefined" }
2506
2586
  },
2507
2587
  {
@@ -2551,7 +2631,7 @@
2551
2631
  },
2552
2632
  {
2553
2633
  "name": "place",
2554
- "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nNote the one thing centering cannot do: land on a whole pixel by itself. A\n16px caption centered in a row set by the 25-system-px `vf-number-field`\nsits at 4.5 system px, and no container can round that it would have to\nread each child's height. `applyGridSnap()` keeps the caption's own ink\ncrisp regardless (it corrects the origin inside the child's shadow root);\n`place=\"start\"` is the deterministic escape.",
2634
+ "description": "Where the children sit across the stack — `start`, `center` or `end`.\nUnset resolves per direction: `start` down a column, `center` across a row.\n\nNamed `place` rather than `align` for a reason worth keeping in the source:\n`align` is a legacy HTML presentation attribute, and Blink maps it to\n`text-align` on any element, so the cross-axis switch used to re-align every\nrun of copy inside the stack (see the `text-align` reset above).\n\nCentering divides the free space in two, so an odd count of system px would\nland a child on a half a 16px caption centered in a row set by the\n25-system-px `vf-number-field` sits at 4.5. CrossCenterController\nsteps that back onto whole system px, the exact half going toward the start\nthe way QuickDraw's `div 2` did; `place=\"start\"` is still the way to ask\nfor no centering at all.",
2555
2635
  "type": "VfStackPlace | undefined"
2556
2636
  },
2557
2637
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintage-frames",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -56,7 +56,7 @@
56
56
  "test": "node scripts/test.mjs",
57
57
  "dev": "vite",
58
58
  "build": "vite build && tsc -p tsconfig.build.json && npm run analyze",
59
- "build:pages": "vite build --config vite.pages.config.ts && node scripts/copy-strikes.mjs",
59
+ "build:pages": "vite build --config vite.pages.config.ts",
60
60
  "preview:pages": "vite preview --config vite.pages.config.ts",
61
61
  "prepack": "npm run build && npm run verify:manifest",
62
62
  "analyze": "cem analyze",
@@ -68,7 +68,6 @@
68
68
  "shot:buttons": "npx playwright screenshot --wait-for-timeout=2000 http://localhost:5173/pixel-test.html shots/buttons.png",
69
69
  "shot:verify": "python3 scripts/verify-button-screenshot.py shots/buttons.png",
70
70
  "shot:menus": "npx playwright screenshot --wait-for-timeout=2000 http://localhost:5173/menu-test.html shots/menus.png",
71
- "shot:blog": "node scripts/shot-blog.mjs",
72
71
  "measure:menus": "python3 scripts/measure-menu-render.py shots/menus.png",
73
72
  "probe:menus": "node scripts/probe-menu-scale.mjs",
74
73
  "verify:desktop": "node scripts/verify-desktop-upgrade.mjs",
@@ -99,7 +98,6 @@
99
98
  "verify:focus": "node scripts/verify-focus.mjs",
100
99
  "verify:grid": "node scripts/verify-grid.mjs",
101
100
  "verify:snap": "node scripts/verify-snap.mjs",
102
- "verify:blog": "node scripts/verify-blog.mjs",
103
101
  "verify:contract": "node scripts/verify-contract.mjs",
104
102
  "verify:names": "node scripts/verify-names.mjs",
105
103
  "verify:forced-colors": "node scripts/verify-forced-colors.mjs"