vintage-frames 0.12.8 → 0.14.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 (62) hide show
  1. package/custom-elements.json +26 -26
  2. package/dist/chrome.d.ts +1 -1
  3. package/dist/components/vf-checkbox.d.ts +6 -5
  4. package/dist/components/vf-checkbox.js +41 -37
  5. package/dist/components/vf-container.d.ts +16 -20
  6. package/dist/components/vf-container.js +25 -17
  7. package/dist/components/vf-desktop.d.ts +4 -5
  8. package/dist/components/vf-desktop.js +13 -18
  9. package/dist/components/vf-fieldset.js +30 -22
  10. package/dist/components/vf-icon.js +55 -55
  11. package/dist/components/vf-list.js +30 -29
  12. package/dist/components/vf-menu.js +24 -22
  13. package/dist/components/vf-number-field.js +32 -30
  14. package/dist/components/vf-progress-bar.d.ts +2 -2
  15. package/dist/components/vf-progress-bar.js +65 -52
  16. package/dist/components/vf-radio.js +3 -4
  17. package/dist/components/vf-scroll-area.js +27 -26
  18. package/dist/components/vf-select.js +108 -90
  19. package/dist/components/vf-separator.d.ts +5 -5
  20. package/dist/components/vf-separator.js +36 -10
  21. package/dist/components/vf-slider.js +11 -11
  22. package/dist/components/vf-swatch.d.ts +2 -2
  23. package/dist/components/vf-swatch.js +73 -72
  24. package/dist/components/vf-text-area.js +37 -35
  25. package/dist/components/vf-text-field.js +20 -18
  26. package/dist/components/vf-window.d.ts +6 -9
  27. package/dist/components/vf-window.js +106 -87
  28. package/dist/index.d.ts +20 -10
  29. package/dist/index.js +63 -62
  30. package/dist/pattern-fill.d.ts +21 -50
  31. package/dist/pattern-fill.js +19 -54
  32. package/dist/scroll-rail.d.ts +6 -6
  33. package/dist/scroll-rail.js +24 -29
  34. package/dist/styles/base.d.ts +3 -1
  35. package/dist/styles/recipes/field.d.ts +10 -6
  36. package/dist/styles/recipes/field.js +8 -7
  37. package/dist/styles/recipes/focus.d.ts +7 -3
  38. package/dist/styles/recipes/pattern.d.ts +19 -10
  39. package/dist/styles/recipes/pattern.js +27 -18
  40. package/dist/styles/recipes/rule.d.ts +11 -8
  41. package/dist/styles/recipes/rule.js +31 -13
  42. package/dist/styles/recipes/scroll-rail.d.ts +21 -13
  43. package/dist/styles/recipes/scroll-rail.js +27 -37
  44. package/dist/styles/recipes/shadow.d.ts +10 -6
  45. package/dist/styles/recipes/shadow.js +4 -2
  46. package/dist/styles/recipes/stroke.d.ts +85 -0
  47. package/dist/styles/recipes/stroke.js +53 -0
  48. package/dist/styles/recipes/surface.d.ts +8 -4
  49. package/dist/styles/recipes/surface.js +23 -17
  50. package/dist/styles/recipes/tile.d.ts +11 -3
  51. package/dist/styles/recipes/tile.js +12 -12
  52. package/dist/styles/recipes/title-bar.d.ts +7 -0
  53. package/dist/styles/recipes/title-bar.js +25 -22
  54. package/dist/styles/recipes/toggle.js +3 -3
  55. package/dist/tile-grid.d.ts +98 -64
  56. package/dist/tile-grid.js +63 -30
  57. package/dist/track-width.d.ts +1 -2
  58. package/dist/zoom.d.ts +9 -9
  59. package/docs/SPEC.md +33 -32
  60. package/editor/vscode.html-custom-data.json +5 -5
  61. package/editor/web-types.json +8 -8
  62. package/package.json +1 -1
@@ -1,32 +1,50 @@
1
- import { css as e, unsafeCSS as t } from "lit";
1
+ import { STROKE_FORCED_COLORS as e, strokeBorder as t, strokeEdgeShadow as n, strokePadding as r } from "./stroke.js";
2
+ import { css as i, unsafeCSS as a } from "lit";
2
3
  //#region src/styles/recipes/rule.ts
3
- var n = [
4
+ var o = [
4
5
  "top",
5
6
  "right",
6
7
  "bottom",
7
8
  "left"
8
- ], r = t("calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000)"), i = e`
9
+ ], s = (e) => a(`
10
+ --_vf-rule-${e}: ${n(e, 1)};
11
+ padding-${e}: ${r(0, 1)};
12
+ border-${e}: ${t(1)};
13
+ `), c = i`
14
+ .vf-rule-top,
15
+ .vf-rule-right,
16
+ .vf-rule-bottom,
17
+ .vf-rule-left {
18
+ --_vf-stroke-ink: var(--vf-black, #000);
19
+ --_vf-rule-top: 0 0 transparent;
20
+ --_vf-rule-right: 0 0 transparent;
21
+ --_vf-rule-bottom: 0 0 transparent;
22
+ --_vf-rule-left: 0 0 transparent;
23
+ box-shadow: var(--_vf-rule-top), var(--_vf-rule-right), var(--_vf-rule-bottom),
24
+ var(--_vf-rule-left);
25
+ ${a(e)}
26
+ }
9
27
  .vf-rule-top {
10
- border-top: ${r};
28
+ ${s("top")}
11
29
  }
12
30
  .vf-rule-right {
13
- border-right: ${r};
31
+ ${s("right")}
14
32
  }
15
33
  .vf-rule-bottom {
16
- border-bottom: ${r};
34
+ ${s("bottom")}
17
35
  }
18
36
  .vf-rule-left {
19
- border-left: ${r};
37
+ ${s("left")}
20
38
  }
21
39
  `;
22
- function a(e) {
40
+ function l(e) {
23
41
  if (e == null) return [];
24
- let t = e.trim().split(/\s+/).filter(Boolean), r = new Set(t);
25
- for (let e of r) if (!n.includes(e)) return null;
26
- return n.filter((e) => r.has(e));
42
+ let t = e.trim().split(/\s+/).filter(Boolean), n = new Set(t);
43
+ for (let e of n) if (!o.includes(e)) return null;
44
+ return o.filter((e) => n.has(e));
27
45
  }
28
- function o(e) {
46
+ function u(e) {
29
47
  return e.map((e) => `vf-rule-${e}`).join(" ");
30
48
  }
31
49
  //#endregion
32
- export { n as RULE_EDGES, a as parseRule, o as ruleClasses, i as vfRule };
50
+ export { o as RULE_EDGES, l as parseRule, u as ruleClasses, c as vfRule };
@@ -1,6 +1,13 @@
1
- export declare const TROUGH_MOTIF_X: number;
2
- export declare const TROUGH_MOTIF_Y: number;
3
- export declare const TROUGH_RECTS: readonly import("./tile.js").TileRect[];
1
+ /**
2
+ * The trough's 25% lattice is the library's `gray-25` — QuickDraw's
3
+ * `ltGray`, and the UI kit's "Scroll bg" sprite — on its minimal cell: a
4
+ * 4×2-system-px motif with a dot at (0,0) and (2,1). The DOM trough repeats
5
+ * it as a tile drawn at the display's resolution (`--_vf-trough-tile`,
6
+ * written by `ScrollRailController` — 1-bit at every scale, zoom-minted ones
7
+ * included); the SVG span tile below is the forced-colors mask, which
8
+ * repaints the same art in the remapped ink token.
9
+ */
10
+ export declare const TROUGH: import("../../patterns.js").PatternMotif;
4
11
  /**
5
12
  * System 7 scroll rails, drawn by the kit as ordinary shadow DOM — the skin
6
13
  * for the subtree `renderScrollRail()` (src/scroll-rail.ts) produces. The
@@ -17,20 +24,21 @@ export declare const TROUGH_RECTS: readonly import("./tile.js").TileRect[];
17
24
  * is kit art: subpixel layout device-snaps at paint like every other element,
18
25
  * and the whole defect class is unrepresentable. It also renders identically
19
26
  * in every engine (Firefox included), is assertable headless, and lets the
20
- * trough adopt the exact tile raster the other dithers use.
27
+ * trough adopt the repeating tile the other dithers use.
21
28
  *
22
29
  * Geometry (system px) — the classic 16px cell, of which the outermost line
23
30
  * is the component's own 1px frame: the rail element is the 15 inside it —
24
- * a 1px divider on the content side (the rail's own border) plus the 14px
31
+ * a 1px divider on the content side (the rail's own stroke) plus the 14px
25
32
  * channel. Arrow cells are 15 long (14 interior + the 1px divider facing the
26
33
  * track); their glyphs are the 16-unit sprites windowed to the 14×14
27
34
  * interior (`viewBox="1 1 14 14"`), since the sprite's outer ring is exactly
28
- * the frame/divider lines the borders draw. The thumb is a fixed 16px white
29
- * box spanning the channel, its border reading as inset 1px from each
35
+ * the frame/divider lines the strokes draw. The thumb is a fixed 16px white
36
+ * box spanning the channel, its stroke reading as inset 1px from each
30
37
  * channel rail over its extent. The component supplies the outer frame as a
31
- * real border and lays the rail out as a sibling of the scroller — AFTER it,
32
- * so the state selectors below can reach the rail from the scroller's
33
- * attributes.
38
+ * stroke of its own and lays the rail out as a sibling of the scroller —
39
+ * AFTER it, so the state selectors below can reach the rail from the
40
+ * scroller's attributes. Every line here is a stroke (vfStrokeDecls):
41
+ * padding holds it and an inset shadow paints it.
34
42
  *
35
43
  * States, keyed off the attributes `ScrollStateController` writes on the
36
44
  * scroller (now styling real elements, so there is no scrollbar-pseudo
@@ -52,9 +60,9 @@ export declare const TROUGH_RECTS: readonly import("./tile.js").TileRect[];
52
60
  * through the vfBase token remap like every other element. The arrows are
53
61
  * inline `currentColor` SVG now, so they re-ink with the palette (the old
54
62
  * `::-webkit-scrollbar` skin's one genuine residual — mask-image is ignored
55
- * on scrollbar pseudos — dies here). The trough raster keeps literal ink, so
56
- * it is hidden and the trough repaints as the ink token masked by the same
57
- * art (the vfDots idiom; the span mask keeps the zoom caveat every masked
63
+ * on scrollbar pseudos — dies here). The trough's tile keeps literal ink, so
64
+ * it goes and the trough repaints as the ink token masked by the same art
65
+ * (the vfDots idiom; the span mask keeps the zoom caveat every masked
58
66
  * surface has, an accepted residual).
59
67
  */
60
68
  export declare const vfScrollRail: import("lit").CSSResult;
@@ -1,8 +1,10 @@
1
1
  import { tileImage as e, tileRects as t, vfTileMaskSize as n } from "./tile.js";
2
- import { PATTERNS as r, patternMotif as i } from "../../patterns.js";
3
- import { css as a, unsafeCSS as o } from "lit";
2
+ import { vfRepeatTileSize as r } from "../../tile-grid.js";
3
+ import { PATTERNS as i, patternMotif as a } from "../../patterns.js";
4
+ import { vfStrokeDecls as o } from "./stroke.js";
5
+ import { css as s, unsafeCSS as c } from "lit";
4
6
  //#region src/styles/recipes/scroll-rail.ts
5
- var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u)), f = a`
7
+ var l = a(i["gray-25"]), u = e(l.width, l.height, t(l.rects)), d = s`
6
8
  /* Hide the native bar, keep the native scrolling. Both lines ship:
7
9
  scrollbar-width reaches Chromium 121+/Firefox/Safari 18.2+, the pseudo
8
10
  covers older WebKit — the last scrollbar pseudo left in the kit.
@@ -36,7 +38,7 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
36
38
  calc(var(--vf-scale, 1) * 15px)
37
39
  1fr
38
40
  calc(var(--vf-scale, 1) * 15px);
39
- border-left: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
41
+ ${o({ edges: ["left"] })}
40
42
  }
41
43
  .vf-rail--horizontal {
42
44
  height: calc(var(--vf-scale, 1) * 15px);
@@ -44,7 +46,7 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
44
46
  calc(var(--vf-scale, 1) * 15px)
45
47
  1fr
46
48
  calc(var(--vf-scale, 1) * 15px);
47
- border-top: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
49
+ ${o({ edges: ["top"] })}
48
50
  }
49
51
  /* Explicit placement, so hiding the arrows (inactive-window/degenerate
50
52
  states) never reflows the track into an arrow cell — its box holds still
@@ -69,11 +71,11 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
69
71
  }
70
72
 
71
73
  /* Arrow cells: white boxes carrying only the 1px divider that faces the
72
- track — the frame edges are the component's border, the content edge the
73
- rail's own divider — so no line ever doubles to 2px. Each cell's padding
74
+ track — the frame edges are the component's stroke, the content edge the
75
+ rail's own divider — so no line ever doubles to 2px. Each cell's content
74
76
  box is by construction the sprite's 14×14 interior, wherever the cell
75
77
  sits (flush to the frame, or against the corner cell), so the glyph
76
- lands at the padding origin with no per-variant offsets. */
78
+ lands at the content origin with no per-variant offsets. */
77
79
  .vf-rail-button {
78
80
  display: grid;
79
81
  place-items: start;
@@ -81,16 +83,16 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
81
83
  overflow: hidden;
82
84
  }
83
85
  .vf-rail--vertical .vf-rail-button--decrement {
84
- border-bottom: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
86
+ ${o({ edges: ["bottom"] })}
85
87
  }
86
88
  .vf-rail--vertical .vf-rail-button--increment {
87
- border-top: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
89
+ ${o({ edges: ["top"] })}
88
90
  }
89
91
  .vf-rail--horizontal .vf-rail-button--decrement {
90
- border-right: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
92
+ ${o({ edges: ["right"] })}
91
93
  }
92
94
  .vf-rail--horizontal .vf-rail-button--increment {
93
- border-left: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
95
+ ${o({ edges: ["left"] })}
94
96
  }
95
97
  .vf-rail-arrow {
96
98
  display: block;
@@ -117,28 +119,19 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
117
119
  min-width: 0;
118
120
  min-height: 0;
119
121
  }
120
- /* The trough: the token base color under the dither, clipping the raster's
121
- overdraw (the art is ceiled up to whole motifs of the measured track). */
122
+ /* The trough: the dither over the token base color — the repeating tile
123
+ ScrollRailController writes (src/tile-grid.ts), 1-bit at every scale. */
122
124
  .vf-rail-trough {
123
125
  position: absolute;
124
126
  inset: 0;
125
- overflow: hidden;
126
127
  background-color: var(--vf-scrollbar-track, var(--vf-white, #fff));
127
- pointer-events: none;
128
- }
129
- /* The dither raster ScrollRailController sizes and writes — one image px
130
- per system px, magnified nearest-neighbor (the vf-img idiom), so the
131
- lattice is 1-bit at every scale. */
132
- .vf-rail-trough-art {
133
- position: absolute;
134
- top: 0;
135
- left: 0;
136
- background-size: 100% 100%;
137
- background-repeat: no-repeat;
128
+ background-image: var(--_vf-trough-tile, none);
129
+ ${r}
138
130
  image-rendering: pixelated;
131
+ pointer-events: none;
139
132
  }
140
133
  /* The fixed System 7 thumb (never proportional): a white 16px box spanning
141
- the channel. Its border reads as inset 1px from each channel rail — the
134
+ the channel. Its stroke reads as inset 1px from each channel rail — the
142
135
  divider and the frame — over its extent; the controller writes its
143
136
  travel as a whole-system-px translate. */
144
137
  .vf-rail-thumb {
@@ -146,7 +139,7 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
146
139
  top: 0;
147
140
  left: 0;
148
141
  background: var(--vf-scrollbar-thumb, #ffffff);
149
- border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
142
+ ${o()}
150
143
  }
151
144
  .vf-rail--vertical .vf-rail-thumb {
152
145
  width: 100%;
@@ -169,8 +162,7 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
169
162
  width: calc(var(--vf-scale, 1) * 15px);
170
163
  height: calc(var(--vf-scale, 1) * 15px);
171
164
  background: var(--vf-white, #fff);
172
- border-top: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
173
- border-left: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
165
+ ${o({ edges: ["top", "left"] })}
174
166
  }
175
167
 
176
168
  /* ── Always-a-rail (driven by ScrollStateController) ─────────────────────
@@ -207,21 +199,19 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
207
199
  display: none;
208
200
  }
209
201
 
210
- /* Forced colors: the raster keeps literal ink, so it hides and the trough
202
+ /* Forced colors: the tile keeps literal ink, so it goes and the trough
211
203
  repaints as the remapped ink token masked by the same art (the vfDots
212
204
  idiom — and like every masked tile, the span mask keeps the zoom
213
205
  caveat). Everything else re-inks through the vfBase token remap; the
214
206
  arrows are currentColor SVG and follow the palette by themselves. */
215
207
  @media (forced-colors: active) {
216
208
  .vf-rail-trough {
209
+ background-image: none;
217
210
  background-color: var(--vf-black, #000);
218
- mask-image: ${o(d)};
219
- ${n(c, l)}
220
- }
221
- .vf-rail-trough-art {
222
- display: none;
211
+ mask-image: ${c(u)};
212
+ ${n(l.width, l.height)}
223
213
  }
224
214
  }
225
215
  `;
226
216
  //#endregion
227
- export { c as TROUGH_MOTIF_X, l as TROUGH_MOTIF_Y, u as TROUGH_RECTS, f as vfScrollRail };
217
+ export { l as TROUGH, d as vfScrollRail };
@@ -3,11 +3,15 @@
3
3
  * with no blur and no spread, System 7's only depth cue. The offset is
4
4
  * tokenized via `--vf-shadow-offset` and scales with `--vf-scale`.
5
5
  *
6
- * Compose it into any raised surface. Every raised surface in the kit does:
7
- * {@link vfPanel} (menus, popups) and {@link vfChromeFrame} (window and dialog
8
- * frames). One declaration, so the kit can't grow two shadows.
9
- *
10
- * Kept in its own module because it is the one recipe consumed on its own:
11
- * vf-swatch composes the shadow without either surface class.
6
+ * The shadow value, for a list: a stroked surface passes it to
7
+ * `vfStrokeDecls({ shadows: vfHardShadow })`, which paints it after the
8
+ * stroke. Every raised surface in the kit does — {@link vfPanel} (menus,
9
+ * popups) and {@link vfChromeFrame} (window frames). One value, so the kit
10
+ * can't grow two shadows.
11
+ */
12
+ export declare const vfHardShadow: import("lit").CSSResult;
13
+ /**
14
+ * {@link vfHardShadow} as the whole `box-shadow` declaration, for a box that
15
+ * casts the shadow and draws no stroke.
12
16
  */
13
17
  export declare const vfHardShadowDecls: import("lit").CSSResult;
@@ -1,5 +1,7 @@
1
1
  import { unsafeCSS as e } from "lit";
2
2
  //#region src/styles/recipes/shadow.ts
3
- var t = e("\n box-shadow: calc(var(--vf-scale, 1) * var(--vf-shadow-offset, 2px))\n calc(var(--vf-scale, 1) * var(--vf-shadow-offset, 2px)) 0 0\n var(--vf-black, #000);\n");
3
+ var t = e("calc(var(--vf-scale, 1) * var(--vf-shadow-offset, 2px))\n calc(var(--vf-scale, 1) * var(--vf-shadow-offset, 2px)) 0 0\n var(--vf-black, #000)"), n = e(`
4
+ box-shadow: ${t};
5
+ `);
4
6
  //#endregion
5
- export { t as vfHardShadowDecls };
7
+ export { t as vfHardShadow, n as vfHardShadowDecls };
@@ -0,0 +1,85 @@
1
+ import { type CSSResult } from 'lit';
2
+ import type { RuleEdge } from './rule.js';
3
+ /** One to four values in shorthand order, as top/right/bottom/left. */
4
+ type Sides<T> = T | readonly T[];
5
+ /** The padding under a stroked edge: the element's own plus the stroke's. */
6
+ export declare function strokePadding(own: number | string, width: number): string;
7
+ /** The border a stroked edge becomes under forced colors; 0 wide otherwise. */
8
+ export declare function strokeBorder(width: number): string;
9
+ /** The inset shadow that paints one edge's stroke. */
10
+ export declare function strokeEdgeShadow(edge: RuleEdge, width: number): string;
11
+ /** The forced-colors switch every stroked element carries. */
12
+ export declare const STROKE_FORCED_COLORS = "@media (forced-colors: active) {\n --_vf-stroke-pad: 0;\n }";
13
+ /**
14
+ * The inset shadow of a stroke `width` system px wide on all four edges — the
15
+ * value to restate when only the painted width changes, as vf-checkbox's
16
+ * press does. Reads the ink and forced-colors switch the element's
17
+ * {@link vfStrokeDecls} declares.
18
+ */
19
+ export declare function vfStrokeShadow(width?: number): CSSResult;
20
+ /** The options of {@link vfStrokeDecls}. */
21
+ export interface VfStrokeOptions {
22
+ /** The stroked edges. Default all four. */
23
+ edges?: readonly RuleEdge[];
24
+ /**
25
+ * The stroke's width in whole system px, one to four values in shorthand
26
+ * order. Default 1.
27
+ */
28
+ width?: Sides<number>;
29
+ /**
30
+ * The element's own padding, in system px, one to four values in shorthand
31
+ * order; each stroked edge adds its stroke. A value is a number or a
32
+ * system-px CSS length (`'var(--vf-select-gutter, 16px)'`). Omitted, the
33
+ * declarations set padding on the stroked edges alone, to the stroke.
34
+ */
35
+ padding?: Sides<number | string>;
36
+ /**
37
+ * More shadows, painted with the stroke: the hard drop shadow
38
+ * ({@link vfHardShadow}), a white ring. The stroke comes first in the list.
39
+ */
40
+ shadows?: string | CSSResult;
41
+ /** The stroke's color. Default `var(--vf-black, #000)`. */
42
+ ink?: string;
43
+ }
44
+ /**
45
+ * A 1-bit stroke that isn't a line width. The declarations for a box whose
46
+ * edges carry a black line of whole system px — every frame, well, rule and
47
+ * box in the kit — for interpolating into the box's rule:
48
+ * `.card { ${vfStrokeDecls({ padding: [4, 8] })} }`.
49
+ *
50
+ * Safari rounds a border width to device px before it applies page zoom, so a
51
+ * `border` of `calc(var(--vf-scale, 1) * 1px)` draws thinner than the art
52
+ * around it at most zoom levels, and moves everything inside the box off the
53
+ * device grid. Padding and box-shadow lengths are not rounded that way. So the
54
+ * stroke's space is padding on each stroked edge, added to the element's own,
55
+ * and the black is an inset `box-shadow`, with `shadows` after it in the same
56
+ * list. Both land on whole device px at every zoom.
57
+ *
58
+ * What changes from a border:
59
+ * - The declarations own `padding` on the stroked edges (on all four, when
60
+ * `padding` is passed) and `box-shadow`. Pass the element's own padding and
61
+ * shadows here; a later `padding` or `box-shadow` on the element replaces
62
+ * the stroke's.
63
+ * - A child placed against the box measures from its outer edge. Offset it by
64
+ * the stroke with {@link vfStrokeInset}.
65
+ * - An `overflow: hidden` box clips at its outer edge, so its content can
66
+ * paint over the stroke. Clip on an inner element instead.
67
+ * - Forced colors paints no `box-shadow`, so there each stroke is a border
68
+ * again and the padding is the element's own: the same box a border draws.
69
+ *
70
+ * The color goes through a private property the declarations set, so pass a
71
+ * `var()` as `ink` to change it from a state rule without restating the
72
+ * shadow list.
73
+ */
74
+ export declare function vfStrokeDecls(options?: VfStrokeOptions): CSSResult;
75
+ /**
76
+ * A length `base` system px in from a stroke's inner edge, measured from the
77
+ * box's outer edge, on an edge carrying `stroke` system px of it (see
78
+ * {@link vfStrokeDecls}): the inset for a child placed against a stroked box
79
+ * (`right: ${vfStrokeInset(0)}` is flush inside a 1px frame), or a padding
80
+ * that restates the stroke's (`padding: ${vfStrokeInset(0)} ${vfStrokeInset(6)}`).
81
+ * `base` is a number or a system-px CSS length. Under forced colors, where
82
+ * the stroke is a border, it is `base`.
83
+ */
84
+ export declare function vfStrokeInset(base: number | string, stroke?: number): CSSResult;
85
+ export {};
@@ -0,0 +1,53 @@
1
+ import { unsafeCSS as e } from "lit";
2
+ //#region src/styles/recipes/stroke.ts
3
+ var t = [
4
+ "top",
5
+ "right",
6
+ "bottom",
7
+ "left"
8
+ ], n = "var(--_vf-stroke-pad, 1)", r = "var(--_vf-stroke-ink)";
9
+ function i(e) {
10
+ let t = Array.isArray(e) ? e : [e], n = t[0], r = t[1] ?? n;
11
+ return [
12
+ n,
13
+ r,
14
+ t[2] ?? n,
15
+ t[3] ?? r
16
+ ];
17
+ }
18
+ var a = (e) => typeof e == "number" ? `${e}px` : e, o = (e) => `calc(var(--vf-scale, 1) * ${e}px * ${n})`;
19
+ function s(e, t) {
20
+ return e === 0 ? o(t) : `calc(var(--vf-scale, 1) * (${a(e)} + ${t}px * ${n}))`;
21
+ }
22
+ function c(e) {
23
+ return `calc(var(--vf-scale, 1) * ${e}px * (1 - ${n})) solid ${r}`;
24
+ }
25
+ function l(e, t) {
26
+ let i = (e) => `calc(var(--vf-scale, 1) * ${e * t}px * ${n})`;
27
+ switch (e) {
28
+ case "top": return `inset 0 ${i(1)} 0 0 ${r}`;
29
+ case "right": return `inset ${i(-1)} 0 0 0 ${r}`;
30
+ case "bottom": return `inset 0 ${i(-1)} 0 0 ${r}`;
31
+ case "left": return `inset ${i(1)} 0 0 0 ${r}`;
32
+ }
33
+ }
34
+ var u = "@media (forced-colors: active) {\n --_vf-stroke-pad: 0;\n }";
35
+ function d(t = 1) {
36
+ return e(`inset 0 0 0 ${o(t)} ${r}`);
37
+ }
38
+ function f(n = {}) {
39
+ let r = new Set(n.edges ?? t), o = i(n.width ?? 1), f = i(n.padding ?? 0), p = t.map((e, t) => r.has(e) ? o[t] : 0), m = p.every((e) => e > 0 && e === p[0]), h = [`--_vf-stroke-ink: ${n.ink ?? "var(--vf-black, #000)"};`], g = [];
40
+ return t.forEach((e, t) => {
41
+ let r = p[t];
42
+ if (!r) {
43
+ n.padding !== void 0 && h.push(`padding-${e}: calc(var(--vf-scale, 1) * ${a(f[t])});`);
44
+ return;
45
+ }
46
+ h.push(`padding-${e}: ${s(f[t], r)};`), h.push(`border-${e}: ${c(r)};`), m || g.push(l(e, r));
47
+ }), m && g.push(String(d(p[0]))), n.shadows && g.push(String(n.shadows)), h.push(`box-shadow: ${g.length ? g.join(", ") : "none"};`), h.push(u), e(h.join("\n "));
48
+ }
49
+ function p(t, n = 1) {
50
+ return e(s(t, n));
51
+ }
52
+ //#endregion
53
+ export { u as STROKE_FORCED_COLORS, c as strokeBorder, l as strokeEdgeShadow, s as strokePadding, f as vfStrokeDecls, p as vfStrokeInset, d as vfStrokeShadow };
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Panel recipe for menus and popups: white face, 1px black border, hard
2
+ * Panel recipe for menus and popups: white face, 1px black frame, hard
3
3
  * offset shadow.
4
4
  */
5
5
  export declare const vfPanel: import("lit").CSSResult;
6
6
  /**
7
7
  * Chrome-frame recipe for the desktop window (vf-window): the same white
8
- * face, 1px black border and hard offset shadow as {@link vfPanel}. Apply
8
+ * face, 1px black frame and hard offset shadow as {@link vfPanel}. Apply
9
9
  * `.vf-frame` to the outer frame element.
10
10
  *
11
11
  * Skin only — the component adds its own layout (a full-size flex column: the
@@ -14,7 +14,7 @@ export declare const vfPanel: import("lit").CSSResult;
14
14
  */
15
15
  export declare const vfChromeFrame: import("lit").CSSResult;
16
16
  /**
17
- * The classic modal-dialog (dBoxProc) frame: 1px outer border, 2px white gap,
17
+ * The classic modal-dialog (dBoxProc) frame: 1px outer rule, 2px white gap,
18
18
  * 2px inner band — and NO drop shadow, per the `Windows/modal dialog.png`
19
19
  * reference. System 7's alert box drew the
20
20
  * *mirror* trace (2px outer, 2px gap, 1px inner rule, with the hard shadow) —
@@ -30,10 +30,14 @@ export declare const vfChromeFrame: import("lit").CSSResult;
30
30
  * six stripes / 3 white rows, the title patch's 7px margins) sits directly
31
31
  * under the outer rule, inset 2px at either end so its stripes keep the same
32
32
  * 2px of white from the outer rule that the inner band keeps; the bar's own
33
- * 1px rule plus the inner box's 1px top border make the 2px band its floor,
33
+ * 1px rule plus the inner box's 1px top stroke make the 2px band its floor,
34
34
  * which then continues down both sides and along the bottom as the band
35
35
  * around the body. No shadow anywhere. Put the `.vf-title-bar` between the
36
36
  * two elements, as the first child of `.vf-modal-frame`, and the three rules
37
37
  * below do the rest.
38
+ *
39
+ * Both lines are strokes ({@link vfStrokeDecls}): padding holds their space
40
+ * and an inset shadow paints them, so the recipe owns both elements' padding
41
+ * and box-shadow.
38
42
  */
39
43
  export declare const vfModalFrame: import("lit").CSSResult;
@@ -1,42 +1,48 @@
1
- import { vfHardShadowDecls as e } from "./shadow.js";
2
- import { css as t, unsafeCSS as n } from "lit";
1
+ import { vfHardShadow as e } from "./shadow.js";
2
+ import { vfStrokeDecls as t, vfStrokeInset as n } from "./stroke.js";
3
+ import { css as r, unsafeCSS as i } from "lit";
3
4
  //#region src/styles/recipes/surface.ts
4
- var r = n(`
5
+ var a = i(`
5
6
  background: var(--vf-white, #fff);
6
- border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
7
- ${e}
8
- `), i = t`
7
+ ${t({ shadows: e })}
8
+ `), o = r`
9
9
  .vf-panel {
10
- ${r}
10
+ ${a}
11
11
  }
12
- `, a = t`
12
+ `, s = r`
13
13
  .vf-frame {
14
- ${r}
14
+ ${a}
15
15
  }
16
- `, o = t`
16
+ `, c = r`
17
17
  .vf-modal-frame {
18
18
  background: var(--vf-white, #fff);
19
- border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
19
+ ${t()}
20
20
  }
21
21
  .vf-modal-frame-inner {
22
22
  margin: calc(var(--vf-scale, 1) * 2px);
23
- border: calc(var(--vf-scale, 1) * 2px) solid var(--vf-black, #000);
23
+ ${t({ width: 2 })}
24
24
  }
25
25
  /* The bar set into the frame: 2px in from the outer rule at either end, its
26
26
  stripes flush to the bar's own edge (vfStripes' 1px side inset is the
27
- window frame's buffer — here the frame's gap is that buffer). */
27
+ window frame's buffer — here the frame's gap is that buffer). The bottom
28
+ inset counts the bar's rule, which is padding (vfTitleBar). */
28
29
  .vf-modal-frame > .vf-title-bar {
29
30
  margin: 0 calc(var(--vf-scale, 1) * 2px);
30
31
  }
31
32
  .vf-modal-frame > .vf-title-bar .vf-stripes {
32
- inset: calc(var(--vf-scale, 1) * 3px) 0;
33
+ inset: calc(var(--vf-scale, 1) * 3px) 0 ${n(3)};
33
34
  }
34
- /* Under a bar, the inner box's top border is the second row of the band —
35
+ /* Under a bar, the inner box's top stroke is the second row of the band —
35
36
  the bar's 1px rule is the first — and there is no gap row between. */
36
37
  .vf-modal-frame > .vf-title-bar + .vf-modal-frame-inner {
37
38
  margin-top: 0;
38
- border-top-width: calc(var(--vf-scale, 1) * 1px);
39
+ ${t({ width: [
40
+ 1,
41
+ 2,
42
+ 2,
43
+ 2
44
+ ] })}
39
45
  }
40
46
  `;
41
47
  //#endregion
42
- export { a as vfChromeFrame, o as vfModalFrame, i as vfPanel };
48
+ export { s as vfChromeFrame, c as vfModalFrame, o as vfPanel };
@@ -61,8 +61,9 @@ export type TileRect = readonly [x: number, y: number, w: number, h: number, fil
61
61
  export declare function tileRects(rects: readonly TileRect[]): string;
62
62
  /**
63
63
  * The motif tiled over `spanWidth × spanHeight` system px as a RASTER image —
64
- * a PNG data URI at one image px per system px — for the whole-surface fill a
65
- * converted tiled surface paints (src/tile-grid.ts).
64
+ * a PNG data URI at `density` image px per system px (one by default) — the
65
+ * encoder behind the tile every kit surface repeats (`repeatTile`,
66
+ * src/tile-grid.ts).
66
67
  *
67
68
  * A raster because of how engines rasterize a fill's art, not how they place
68
69
  * its box. Chromium rasterizes an SVG image at the box's *stored* (layout)
@@ -79,8 +80,15 @@ export declare function tileRects(rects: readonly TileRect[]): string;
79
80
  * out as a data URI and released — kit art only, so there is nothing
80
81
  * cross-origin to taint). Where canvas is unavailable the SVG tile is
81
82
  * returned instead, so the declaration still paints.
83
+ *
84
+ * `density` draws each system px as a `density × density` block of whole
85
+ * image px. An image drawn at the device px per system px it will be shown
86
+ * at is copied 1:1 onto the device grid, so no engine's image filter has
87
+ * anything to decide — Safari smooths a magnified image on its repeating
88
+ * path whatever `image-rendering` says. It is still one pattern bit per
89
+ * system px; only who scales it changes.
82
90
  */
83
- export declare function tileRaster(motifWidth: number, motifHeight: number, rects: readonly TileRect[], spanWidth?: number, spanHeight?: number): string;
91
+ export declare function tileRaster(motifWidth: number, motifHeight: number, rects: readonly TileRect[], spanWidth?: number, spanHeight?: number, density?: number): string;
84
92
  /**
85
93
  * `background-size` for a REPEATING tile — stated as the motif, sized as the
86
94
  * span that motif tiles at ({@link tileSpan}).
@@ -11,18 +11,18 @@ function i(e, t, n, i = r(e), a = r(t)) {
11
11
  function a(e) {
12
12
  return e.map(([e, t, n, r, i]) => `%3Crect ${(e ? `x='${e}' ` : "") + (t ? `y='${t}' ` : "")}width='${n}' height='${r}'${i ? ` fill='${encodeURIComponent(i)}'` : ""}/%3E`).join("");
13
13
  }
14
- function o(e, t, n, o = r(e), s = r(t)) {
15
- let c = () => i(e, t, a(n), o, s);
16
- if (typeof document > "u") return c();
17
- let l = document.createElement("canvas");
18
- l.width = e, l.height = t;
19
- let u = l.getContext("2d");
20
- if (!u) return c();
21
- for (let [e, t, r, i, a] of n) u.fillStyle = a ?? "#000000", u.fillRect(e, t, r, i);
22
- let d = document.createElement("canvas");
23
- d.width = o, d.height = s;
24
- let f = d.getContext("2d"), p = f?.createPattern(l, "repeat");
25
- return !f || !p ? c() : (f.fillStyle = p, f.fillRect(0, 0, o, s), `url("${d.toDataURL("image/png")}")`);
14
+ function o(e, t, n, o = r(e), s = r(t), c = 1) {
15
+ let l = () => i(e, t, a(n), o, s);
16
+ if (typeof document > "u") return l();
17
+ let u = document.createElement("canvas");
18
+ u.width = e * c, u.height = t * c;
19
+ let d = u.getContext("2d");
20
+ if (!d) return l();
21
+ for (let [e, t, r, i, a] of n) d.fillStyle = a ?? "#000000", d.fillRect(e * c, t * c, r * c, i * c);
22
+ let f = document.createElement("canvas");
23
+ f.width = o * c, f.height = s * c;
24
+ let p = f.getContext("2d"), m = p?.createPattern(u, "repeat");
25
+ return !p || !m ? l() : (p.fillStyle = m, p.fillRect(0, 0, f.width, f.height), `url("${f.toDataURL("image/png")}")`);
26
26
  }
27
27
  function s(t, n = t) {
28
28
  return e`
@@ -32,6 +32,9 @@
32
32
  * drag handle, vf-window's only when `[movable]` — suppressing touch scrolling
33
33
  * on a bar that can't be dragged would be a real behavior change, so each
34
34
  * component keeps that one declaration under its own selector.
35
+ *
36
+ * The rule is a stroke ({@link vfStrokeDecls}): the bottom row is padding, so
37
+ * the texture layers placed inside the bar count it in their bottom insets.
35
38
  */
36
39
  export declare const vfTitleBar: import("lit").CSSResult;
37
40
  /**
@@ -45,5 +48,9 @@ export declare const vfTitleBar: import("lit").CSSResult;
45
48
  * Geometry is the standard 18px bar's; vf-window's utility variant overrides
46
49
  * the sizes under its own selector. Host-state rules (hiding widgets on an
47
50
  * inactive window) stay with the component — a dialog has no inactive state.
51
+ *
52
+ * Each box's line is a stroke ({@link vfStrokeDecls}), its white patch ring
53
+ * the next shadow in the same list. State rules recolor the line through
54
+ * `--_vf-stroke-ink`.
48
55
  */
49
56
  export declare const vfWindowWidgets: import("lit").CSSResult;