@master/internal-site 0.2.4 → 0.2.6

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 (46) hide show
  1. package/dist/astro.mjs +1842 -5
  2. package/dist/{localization-LfIbIysE.mjs → localization-IeZXkrNw.mjs} +11 -2
  3. package/dist/localization.mjs +1 -1
  4. package/package.json +9 -8
  5. package/src/astro/image-variants.ts +96 -0
  6. package/src/astro/index.ts +12 -3
  7. package/src/astro/site-translations.ts +31 -0
  8. package/src/astro/svelte-client.ts +13 -0
  9. package/src/demo/specimens/background-attachment.json +3 -3
  10. package/src/demo/specimens/background-origin.json +9 -9
  11. package/src/demo/specimens/clip-path.json +3 -3
  12. package/src/demo/specimens/counter-set.json +8 -8
  13. package/src/demo/specimens/filter.json +6 -6
  14. package/src/demo/specimens/grid-auto-flow.json +3 -3
  15. package/src/demo/specimens/grid-template-areas.json +5 -5
  16. package/src/demo/specimens/grid-template-columns.json +6 -6
  17. package/src/demo/specimens/justify-content.json +8 -8
  18. package/src/demo/specimens/mask-image.json +3 -3
  19. package/src/demo/specimens/object-position.json +6 -6
  20. package/src/demo/specimens/place-content.json +4 -4
  21. package/src/demo/specimens/scroll-snap-stop.json +3 -3
  22. package/src/demo/specimens/shape-margin.json +5 -5
  23. package/src/demo/specimens/shape-outside.json +3 -3
  24. package/src/demo/specimens/transform-origin.json +3 -3
  25. package/src/docs-shell/components/Image.svelte +7 -4
  26. package/src/docs-shell/components/SearchButton.svelte +5 -3
  27. package/src/docs-shell/components/SearchDialog.svelte +47 -161
  28. package/src/docs-shell/components/SearchDialogPanel.svelte +182 -0
  29. package/src/docs-shell/contexts/search.ts +8 -0
  30. package/src/docs-shell/contexts/theme-mode.svelte.ts +6 -1
  31. package/src/docs-shell/scripts/code.ts +9 -4
  32. package/src/docs-shell/site-dictionary.ts +36 -0
  33. package/src/docs-shell/types/StaticImage.ts +2 -0
  34. package/src/examples/components/ArticleTransitionScene.svelte +16 -14
  35. package/src/home/home-lamp.ts +35 -13
  36. package/src/home/icons/artwork/render-prerender-dark.webp +0 -0
  37. package/src/home/icons/artwork/render-prerender-light.webp +0 -0
  38. package/src/home/icons/artwork/source.json +6 -0
  39. package/src/home/icons/artwork.ts +3 -0
  40. package/src/home/icons/catalog.ts +9 -1
  41. package/src/home/icons/render-prerender.svg +12 -0
  42. package/src/layouts/Document.astro +24 -9
  43. package/src/scripts/atomic-class-authoring.ts +4 -1
  44. package/src/scripts/localization.ts +13 -4
  45. package/src/styles/blog-article.css +5 -4
  46. package/src/styles/system/DESIGN.md +1 -1
@@ -4,7 +4,7 @@
4
4
  "id": "fade-overflowing-media",
5
5
  "title": "Fade overflowing media",
6
6
  "html": [
7
- "<!-- Scrollable list · fixed lower fade -->\n<div class=\"w(240) max-w-full\">\n <div class=\"outline(2):focus-within outline-solid:focus-within outline-blue:focus-within\n outline-offset(3):focus-within\">\n <div id=\"target\" role=\"region\" tabindex=\"0\"\n aria-label=\"Layer list\"\n aria-describedby=\"scroll-help\"\n class=\"overflow-auto h(160) bg-surface-base\n (mask-image:linear-gradient(to|bottom,black|60%,transparent))\n (mask-mode:alpha) (outline:none):focus-visible\">\n <ol class=\"m(0) pb-3xl pl-lg pr-sm font-sm fg-text-body\">\n <li id=\"first-layer\" class=\"py-xs\">Outline</li>\n <li class=\"py-xs\">Surface</li>\n <li class=\"py-xs\">Foreground</li>\n <li class=\"py-xs\">Accent</li>\n <li class=\"py-xs\">Annotation</li>\n <li class=\"py-xs\">Selection</li>\n <li class=\"py-xs\">Focus</li>\n <li id=\"last-layer\" class=\"py-xs\">Export</li>\n </ol>\n </div>\n </div>\n <p id=\"scroll-help\" class=\"mb(0) mt-sm font-sm fg-text-body\">\n Eight layers. Scroll or follow the links below.\n </p>\n <nav aria-label=\"Layer navigation\"\n class=\"flex flex-wrap gap-md mt-sm font-sm\">\n <a href=\"#first-layer\">First layer</a>\n <a href=\"#last-layer\">Last layer</a>\n </nav>\n</div>"
7
+ "<!-- Scrollable list · fixed lower fade -->\n<div class=\"w(240) max-w-full\">\n <div class=\"outline(2):focus-within outline-solid:focus-within outline-blue:focus-within\n outline-offset(3):focus-within\">\n <div id=\"target\" role=\"region\" tabindex=\"0\"\n aria-label=\"Layer list\"\n aria-describedby=\"scroll-help\"\n class=\"overflow-auto h(160) bg-surface-base\n (mask-image:linear-gradient(to_bottom,black_60%,transparent))\n (mask-mode:alpha) (outline:none):focus-visible\">\n <ol class=\"m(0) pb-3xl pl-lg pr-sm font-sm fg-text-body\">\n <li id=\"first-layer\" class=\"py-xs\">Outline</li>\n <li class=\"py-xs\">Surface</li>\n <li class=\"py-xs\">Foreground</li>\n <li class=\"py-xs\">Accent</li>\n <li class=\"py-xs\">Annotation</li>\n <li class=\"py-xs\">Selection</li>\n <li class=\"py-xs\">Focus</li>\n <li id=\"last-layer\" class=\"py-xs\">Export</li>\n </ol>\n </div>\n </div>\n <p id=\"scroll-help\" class=\"mb(0) mt-sm font-sm fg-text-body\">\n Eight layers. Scroll or follow the links below.\n </p>\n <nav aria-label=\"Layer navigation\"\n class=\"flex flex-wrap gap-md mt-sm font-sm\">\n <a href=\"#first-layer\">First layer</a>\n <a href=\"#last-layer\">Last layer</a>\n </nav>\n</div>"
8
8
  ],
9
9
  "css": "",
10
10
  "classes": [
@@ -17,7 +17,7 @@
17
17
  "overflow-auto",
18
18
  "h(160)",
19
19
  "bg-surface-base",
20
- "(mask-image:linear-gradient(to|bottom,black|60%,transparent))",
20
+ "(mask-image:linear-gradient(to_bottom,black_60%,transparent))",
21
21
  "(mask-mode:alpha)",
22
22
  "(outline:none):focus-visible",
23
23
  "m(0)",
@@ -36,7 +36,7 @@
36
36
  "classLists": [
37
37
  "w(240) max-w-full",
38
38
  "outline(2):focus-within outline-solid:focus-within outline-blue:focus-within outline-offset(3):focus-within",
39
- "overflow-auto h(160) bg-surface-base (mask-image:linear-gradient(to|bottom,black|60%,transparent)) (mask-mode:alpha) (outline:none):focus-visible",
39
+ "overflow-auto h(160) bg-surface-base (mask-image:linear-gradient(to_bottom,black_60%,transparent)) (mask-mode:alpha) (outline:none):focus-visible",
40
40
  "m(0) pb-3xl pl-lg pr-sm font-sm fg-text-body",
41
41
  "py-xs",
42
42
  "py-xs",
@@ -4,8 +4,8 @@
4
4
  "id": "use-precise-positions",
5
5
  "title": "Use precise positions",
6
6
  "html": [
7
- "<!-- 50% 25% · 12.5px top crop -->\n<img id=\"target\" src=\"/demo/landscape.svg\"\n width=\"320\" height=\"200\"\n alt=\"Blue and violet mountains beneath a yellow sun\"\n class=\"block h(100) w(240) max-w-full\n bg-surface-base object-cover\n (object-position:50%|25%)\" />",
8
- "<!-- Left 12px · top 8px -->\n<img id=\"target\" src=\"/demo/landscape.svg\"\n width=\"320\" height=\"200\"\n alt=\"Blue and violet mountains beneath a yellow sun\"\n class=\"block h(100) w(240) max-w-full\n bg-surface-base object-cover\n (object-position:left|12px|top|8px)\" />"
7
+ "<!-- 50% 25% · 12.5px top crop -->\n<img id=\"target\" src=\"/demo/landscape.svg\"\n width=\"320\" height=\"200\"\n alt=\"Blue and violet mountains beneath a yellow sun\"\n class=\"block h(100) w(240) max-w-full\n bg-surface-base object-cover\n (object-position:50%_25%)\" />",
8
+ "<!-- Left 12px · top 8px -->\n<img id=\"target\" src=\"/demo/landscape.svg\"\n width=\"320\" height=\"200\"\n alt=\"Blue and violet mountains beneath a yellow sun\"\n class=\"block h(100) w(240) max-w-full\n bg-surface-base object-cover\n (object-position:left_12px_top_8px)\" />"
9
9
  ],
10
10
  "css": "",
11
11
  "classes": [
@@ -15,12 +15,12 @@
15
15
  "max-w-full",
16
16
  "bg-surface-base",
17
17
  "object-cover",
18
- "(object-position:50%|25%)",
19
- "(object-position:left|12px|top|8px)"
18
+ "(object-position:50%_25%)",
19
+ "(object-position:left_12px_top_8px)"
20
20
  ],
21
21
  "classLists": [
22
- "block h(100) w(240) max-w-full bg-surface-base object-cover (object-position:50%|25%)",
23
- "block h(100) w(240) max-w-full bg-surface-base object-cover (object-position:left|12px|top|8px)"
22
+ "block h(100) w(240) max-w-full bg-surface-base object-cover (object-position:50%_25%)",
23
+ "block h(100) w(240) max-w-full bg-surface-base object-cover (object-position:left_12px_top_8px)"
24
24
  ],
25
25
  "highlighted": []
26
26
  }
@@ -4,13 +4,13 @@
4
4
  "id": "use-separate-axis-values",
5
5
  "title": "Use separate axis values",
6
6
  "html": [
7
- "<div id=\"layout\" class=\"grid (grid-template-columns:5rem|5rem) (grid-template-rows:3rem|3rem) content-start justify-between gap-md (height:14rem) (width:17.5rem) max-w-full\">\n <div id=\"target\" class=\"p-sm\">01</div>\n <div id=\"peer\" class=\"p-sm\">02</div>\n <div id=\"item-3\" class=\"p-sm\">03</div>\n <div id=\"item-4\" class=\"p-sm\">04</div>\n</div>"
7
+ "<div id=\"layout\" class=\"grid (grid-template-columns:5rem_5rem) (grid-template-rows:3rem_3rem) content-start justify-between gap-md (height:14rem) (width:17.5rem) max-w-full\">\n <div id=\"target\" class=\"p-sm\">01</div>\n <div id=\"peer\" class=\"p-sm\">02</div>\n <div id=\"item-3\" class=\"p-sm\">03</div>\n <div id=\"item-4\" class=\"p-sm\">04</div>\n</div>"
8
8
  ],
9
9
  "css": "",
10
10
  "classes": [
11
11
  "grid",
12
- "(grid-template-columns:5rem|5rem)",
13
- "(grid-template-rows:3rem|3rem)",
12
+ "(grid-template-columns:5rem_5rem)",
13
+ "(grid-template-rows:3rem_3rem)",
14
14
  "content-start",
15
15
  "justify-between",
16
16
  "gap-md",
@@ -20,7 +20,7 @@
20
20
  "p-sm"
21
21
  ],
22
22
  "classLists": [
23
- "grid (grid-template-columns:5rem|5rem) (grid-template-rows:3rem|3rem) content-start justify-between gap-md (height:14rem) (width:17.5rem) max-w-full",
23
+ "grid (grid-template-columns:5rem_5rem) (grid-template-rows:3rem_3rem) content-start justify-between gap-md (height:14rem) (width:17.5rem) max-w-full",
24
24
  "p-sm",
25
25
  "p-sm",
26
26
  "p-sm",
@@ -4,14 +4,14 @@
4
4
  "id": "use-sparingly",
5
5
  "title": "Use sparingly",
6
6
  "html": [
7
- "<div id=\"scrollport\" class=\"flex overflow-x-auto gap-md (scroll-snap-type:x|mandatory)\"\n tabindex=\"0\" aria-label=\"Horizontal panels\">\n <article id=\"panel-1\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 1</article>\n <article id=\"panel-2\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:always) (scroll-snap-align:start)\">Panel 2</article>\n <article id=\"panel-3\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 3</article>\n <article id=\"panel-4\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 4</article>\n <article id=\"panel-5\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 5</article>\n <article id=\"panel-6\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 6</article>\n</div>"
7
+ "<div id=\"scrollport\" class=\"flex overflow-x-auto gap-md (scroll-snap-type:x_mandatory)\"\n tabindex=\"0\" aria-label=\"Horizontal panels\">\n <article id=\"panel-1\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 1</article>\n <article id=\"panel-2\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:always) (scroll-snap-align:start)\">Panel 2</article>\n <article id=\"panel-3\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 3</article>\n <article id=\"panel-4\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 4</article>\n <article id=\"panel-5\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 5</article>\n <article id=\"panel-6\" class=\"shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)\">Panel 6</article>\n</div>"
8
8
  ],
9
9
  "css": "",
10
10
  "classes": [
11
11
  "flex",
12
12
  "overflow-x-auto",
13
13
  "gap-md",
14
- "(scroll-snap-type:x|mandatory)",
14
+ "(scroll-snap-type:x_mandatory)",
15
15
  "shrink(0)",
16
16
  "(height:9rem)",
17
17
  "(width:12rem)",
@@ -20,7 +20,7 @@
20
20
  "(scroll-snap-stop:always)"
21
21
  ],
22
22
  "classLists": [
23
- "flex overflow-x-auto gap-md (scroll-snap-type:x|mandatory)",
23
+ "flex overflow-x-auto gap-md (scroll-snap-type:x_mandatory)",
24
24
  "shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)",
25
25
  "shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:always) (scroll-snap-align:start)",
26
26
  "shrink(0) (height:9rem) (width:12rem) (scroll-snap-stop:normal) (scroll-snap-align:start)",
@@ -4,8 +4,8 @@
4
4
  "id": "increase-margin-for-large-media",
5
5
  "title": "Reserve room for a larger shape margin",
6
6
  "html": [
7
- "<!-- Margin md · 16px around the circle -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) mb-xl mr-xl\n outline(1) outline-solid outline-line-subtle fill-blue-60\n shape-margin-md\n (shape-outside:circle(40px)|border-box)\">\n <circle cx=\"60\" cy=\"60\" r=\"40\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>",
8
- "<!-- Margin xl · 32px around the circle -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) mb-xl mr-xl\n outline(1) outline-solid outline-line-subtle fill-blue-60\n shape-margin-xl\n (shape-outside:circle(40px)|border-box)\">\n <circle cx=\"60\" cy=\"60\" r=\"40\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>"
7
+ "<!-- Margin md · 16px around the circle -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) mb-xl mr-xl\n outline(1) outline-solid outline-line-subtle fill-blue-60\n shape-margin-md\n (shape-outside:circle(40px)_border-box)\">\n <circle cx=\"60\" cy=\"60\" r=\"40\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>",
8
+ "<!-- Margin xl · 32px around the circle -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) mb-xl mr-xl\n outline(1) outline-solid outline-line-subtle fill-blue-60\n shape-margin-xl\n (shape-outside:circle(40px)_border-box)\">\n <circle cx=\"60\" cy=\"60\" r=\"40\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>"
9
9
  ],
10
10
  "css": "",
11
11
  "classes": [
@@ -23,16 +23,16 @@
23
23
  "outline-line-subtle",
24
24
  "fill-blue-60",
25
25
  "shape-margin-md",
26
- "(shape-outside:circle(40px)|border-box)",
26
+ "(shape-outside:circle(40px)_border-box)",
27
27
  "m(0)",
28
28
  "shape-margin-xl"
29
29
  ],
30
30
  "classLists": [
31
31
  "flow-root w(280) max-w-full font-sm (line-height:24px) fg-text-body",
32
- "(float:left) mb-xl mr-xl outline(1) outline-solid outline-line-subtle fill-blue-60 shape-margin-md (shape-outside:circle(40px)|border-box)",
32
+ "(float:left) mb-xl mr-xl outline(1) outline-solid outline-line-subtle fill-blue-60 shape-margin-md (shape-outside:circle(40px)_border-box)",
33
33
  "m(0)",
34
34
  "flow-root w(280) max-w-full font-sm (line-height:24px) fg-text-body",
35
- "(float:left) mb-xl mr-xl outline(1) outline-solid outline-line-subtle fill-blue-60 shape-margin-xl (shape-outside:circle(40px)|border-box)",
35
+ "(float:left) mb-xl mr-xl outline(1) outline-solid outline-line-subtle fill-blue-60 shape-margin-xl (shape-outside:circle(40px)_border-box)",
36
36
  "m(0)"
37
37
  ],
38
38
  "highlighted": []
@@ -5,7 +5,7 @@
5
5
  "title": "Wrap text around a circle",
6
6
  "html": [
7
7
  "<!-- Normal float · rectangular wrap -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) outline(1) outline-solid outline-line-subtle\n fill-blue-60 (shape-outside:none)\">\n <circle cx=\"60\" cy=\"60\" r=\"48\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>",
8
- "<!-- Circle · text follows the curved boundary -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) outline(1) outline-solid outline-line-subtle\n fill-blue-60\n (shape-outside:circle(48px)|border-box)\">\n <circle cx=\"60\" cy=\"60\" r=\"48\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>"
8
+ "<!-- Circle · text follows the curved boundary -->\n<article class=\"flow-root w(280) max-w-full font-sm\n (line-height:24px) fg-text-body\">\n <svg id=\"target\" role=\"img\" aria-label=\"Blue circle\"\n width=\"120\" height=\"120\" viewBox=\"0 0 120 120\"\n class=\"(float:left) outline(1) outline-solid outline-line-subtle\n fill-blue-60\n (shape-outside:circle(48px)_border-box)\">\n <circle cx=\"60\" cy=\"60\" r=\"48\" />\n </svg>\n <p class=\"m(0)\">\n A clear layout leaves room for both artwork and words.\n Follow the first lines as they pass the blue shape.\n Their starting positions change with the available space.\n Below the float, the paragraph returns to its full width.\n The artwork keeps the same size throughout the comparison.\n </p>\n</article>"
9
9
  ],
10
10
  "css": "",
11
11
  "classes": [
@@ -22,14 +22,14 @@
22
22
  "fill-blue-60",
23
23
  "(shape-outside:none)",
24
24
  "m(0)",
25
- "(shape-outside:circle(48px)|border-box)"
25
+ "(shape-outside:circle(48px)_border-box)"
26
26
  ],
27
27
  "classLists": [
28
28
  "flow-root w(280) max-w-full font-sm (line-height:24px) fg-text-body",
29
29
  "(float:left) outline(1) outline-solid outline-line-subtle fill-blue-60 (shape-outside:none)",
30
30
  "m(0)",
31
31
  "flow-root w(280) max-w-full font-sm (line-height:24px) fg-text-body",
32
- "(float:left) outline(1) outline-solid outline-line-subtle fill-blue-60 (shape-outside:circle(48px)|border-box)",
32
+ "(float:left) outline(1) outline-solid outline-line-subtle fill-blue-60 (shape-outside:circle(48px)_border-box)",
33
33
  "m(0)"
34
34
  ],
35
35
  "highlighted": []
@@ -5,7 +5,7 @@
5
5
  "title": "Set the pivot point",
6
6
  "html": [
7
7
  "<!-- Center · four edges move inward -->\n<div class=\"p-lg\">\n <div class=\"relative h(96) w(160) outline(1) outline-dashed outline-line-subtle\">\n <div id=\"target\"\n class=\"grid items-start (justify-items:center) h(96) w(160) p-sm\n b(1) b-solid b-blue-60 r-sm font-sm bg-blue-10\n fg-blue-80 (transform:scale(.75))\n (transform-origin:center)\">\n Center pivot\n </div>\n <span aria-hidden=\"true\"\n class=\"absolute left(77) top(45) w(6) h(6) (border-radius:100%)\n bg-violet-60 pointer-none\"></span>\n </div>\n</div>",
8
- "<!-- Top left · two edges stay fixed -->\n<div class=\"p-lg\">\n <div class=\"relative h(96) w(160) outline(1) outline-dashed outline-line-subtle\">\n <div id=\"target\"\n class=\"grid items-start (justify-items:center) h(96) w(160) p-sm\n b(1) b-solid b-blue-60 r-sm font-sm bg-blue-10\n fg-blue-80 (transform:scale(.75))\n (transform-origin:top|left)\">\n Corner pivot\n </div>\n <span aria-hidden=\"true\"\n class=\"absolute left(-3) top(-3) w(6) h(6) (border-radius:100%)\n bg-violet-60 pointer-none\"></span>\n </div>\n</div>"
8
+ "<!-- Top left · two edges stay fixed -->\n<div class=\"p-lg\">\n <div class=\"relative h(96) w(160) outline(1) outline-dashed outline-line-subtle\">\n <div id=\"target\"\n class=\"grid items-start (justify-items:center) h(96) w(160) p-sm\n b(1) b-solid b-blue-60 r-sm font-sm bg-blue-10\n fg-blue-80 (transform:scale(.75))\n (transform-origin:top_left)\">\n Corner pivot\n </div>\n <span aria-hidden=\"true\"\n class=\"absolute left(-3) top(-3) w(6) h(6) (border-radius:100%)\n bg-violet-60 pointer-none\"></span>\n </div>\n</div>"
9
9
  ],
10
10
  "css": "",
11
11
  "classes": [
@@ -37,7 +37,7 @@
37
37
  "(border-radius:100%)",
38
38
  "bg-violet-60",
39
39
  "pointer-none",
40
- "(transform-origin:top|left)",
40
+ "(transform-origin:top_left)",
41
41
  "left(-3)",
42
42
  "top(-3)"
43
43
  ],
@@ -48,7 +48,7 @@
48
48
  "absolute left(77) top(45) w(6) h(6) (border-radius:100%) bg-violet-60 pointer-none",
49
49
  "p-lg",
50
50
  "relative h(96) w(160) outline(1) outline-dashed outline-line-subtle",
51
- "grid items-start (justify-items:center) h(96) w(160) p-sm b(1) b-solid b-blue-60 r-sm font-sm bg-blue-10 fg-blue-80 (transform:scale(.75)) (transform-origin:top|left)",
51
+ "grid items-start (justify-items:center) h(96) w(160) p-sm b(1) b-solid b-blue-60 r-sm font-sm bg-blue-10 fg-blue-80 (transform:scale(.75)) (transform-origin:top_left)",
52
52
  "absolute left(-3) top(-3) w(6) h(6) (border-radius:100%) bg-violet-60 pointer-none"
53
53
  ],
54
54
  "highlighted": []
@@ -30,7 +30,7 @@
30
30
  </script>
31
31
 
32
32
  <script lang="ts">
33
- /** Render a bitmap with intrinsic dimensions and, on Cloudflare, resized sources. */
33
+ /** Render a bitmap with intrinsic dimensions and resized sources: built AVIF candidates for imports, Cloudflare for URLs. */
34
34
  let { src, width, height, fill, priority, quality, placeholder: _placeholder, loading, sizes, style, alt = '', fetchpriority, ...props }: ImageProps = $props()
35
35
 
36
36
  const source = $derived(typeof src === 'string' ? src : src.src)
@@ -38,15 +38,18 @@
38
38
  const intrinsicHeight = $derived(fill ? undefined : Number(height ?? (typeof src === 'object' ? src.height : undefined)) || undefined)
39
39
  const url = (size: number) => createCloudflareImageUrl({ src: source, width: size, quality, siteUrl: import.meta.env.PUBLIC_URL })
40
40
  const widths = $derived(candidateWidths(intrinsicWidth, sizes))
41
- const resized = $derived(transformations && url(widths[0]) !== source)
41
+ const built = $derived(typeof src === 'object' ? src.srcset : undefined)
42
+ const resized = $derived(!built && transformations && url(widths[0]) !== source)
43
+ // Built candidates use width descriptors; a fixed display width tells the browser which one fits.
44
+ const builtSizes = $derived(sizes ?? (fill ? '100vw' : width ? `${Number(width)}px` : undefined))
42
45
  </script>
43
46
 
44
47
  <img
45
48
  {...props}
46
49
  {alt}
47
50
  src={resized ? url(widths.at(-1)!) : source}
48
- srcset={resized ? widths.map((size, index) => sizes || !intrinsicWidth ? `${url(size)} ${size}w` : `${url(size)} ${index + 1}x`).join(', ') : undefined}
49
- sizes={resized ? sizes ?? (fill ? '100vw' : undefined) : undefined}
51
+ srcset={built ?? (resized ? widths.map((size, index) => sizes || !intrinsicWidth ? `${url(size)} ${size}w` : `${url(size)} ${index + 1}x`).join(', ') : undefined)}
52
+ sizes={built ? builtSizes : resized ? sizes ?? (fill ? '100vw' : undefined) : undefined}
50
53
  width={intrinsicWidth}
51
54
  height={intrinsicHeight}
52
55
  loading={priority ? 'eager' : loading ?? 'lazy'}
@@ -5,7 +5,7 @@
5
5
  import type { Snippet } from 'svelte'
6
6
  import type { HTMLButtonAttributes } from 'svelte/elements'
7
7
  import IconSearch from '@tabler/icons-svelte-runes/icons/search'
8
- import { openSearch, searchLabels } from '../contexts/search'
8
+ import { openSearch, prefetchSearch, searchLabels } from '../contexts/search'
9
9
  import { getSite } from '../site'
10
10
 
11
11
  interface Props extends HTMLButtonAttributes {
@@ -13,7 +13,7 @@
13
13
  }
14
14
 
15
15
  /** Opens the page's search dialog. Without children it renders the search field trigger (`ui-search-trigger`) with its shortcut. */
16
- let { class: className, children, onclick, type = 'button', ...props }: Props = $props()
16
+ let { class: className, children, onclick, onfocus, onpointerenter, type = 'button', ...props }: Props = $props()
17
17
  const { t, locale } = getSite()
18
18
  const { searchText, searchLabel } = searchLabels(t, locale)
19
19
 
@@ -23,7 +23,9 @@
23
23
  }
24
24
  </script>
25
25
 
26
- <button {...props} {type} class={className} aria-label={props['aria-label'] ?? searchLabel} aria-keyshortcuts="Meta+K Control+K" onclick={handleClick}>
26
+ <button {...props} {type} class={className} aria-label={props['aria-label'] ?? searchLabel} aria-keyshortcuts="Meta+K Control+K" onclick={handleClick}
27
+ onpointerenter={event => { onpointerenter?.(event); prefetchSearch() }}
28
+ onfocus={event => { onfocus?.(event); prefetchSearch() }}>
27
29
  {#if children}
28
30
  {@render children()}
29
31
  {:else}
@@ -1,176 +1,62 @@
1
- <script lang="ts" module>
2
- import type { Attachment } from 'svelte/attachments'
3
- import type { DocumentationSearchPage } from '../utils/documentation-search'
4
-
5
- const cache: Record<string, Promise<DocumentationSearchPage[]>> = {}
6
-
7
- async function loadPages(locale: string) {
8
- cache[locale] ??= fetch(`/search/${encodeURIComponent(locale)}.json`).then(response => {
9
- if (!response.ok) throw new Error(`Search unavailable (${response.status})`)
10
- return response.json()
11
- }).catch(error => { delete cache[locale]; throw error })
12
- return cache[locale]
13
- }
14
-
15
- /** The dialog lives at the end of the body, outside the island that opens it. */
16
- const portal: Attachment<HTMLElement> = (node) => {
17
- document.body.append(node)
18
- return () => node.remove()
19
- }
20
- </script>
21
-
22
1
  <script lang="ts">
23
- import '@master/internal-site/styles/interface.css'
24
- import '@master/internal-site/styles/system/dialog.css'
25
-
26
- import '../../styles/docs-shell/documentation-search.css'
27
- import { untrack } from 'svelte'
28
- import Link from './Link.svelte'
29
- import { getSite } from '../site'
30
- import { createDocumentationSearch } from '../utils/documentation-search'
31
- import { openSearchEvent } from '../contexts/search'
32
- import IconArrowDown from '@tabler/icons-svelte-runes/icons/arrow-down'
33
- import IconArrowUp from '@tabler/icons-svelte-runes/icons/arrow-up'
34
- import IconCornerDownLeft from '@tabler/icons-svelte-runes/icons/corner-down-left'
35
- import IconSearch from '@tabler/icons-svelte-runes/icons/search'
36
- import IconX from '@tabler/icons-svelte-runes/icons/x'
37
- import { isInteractOutside } from '../utils/isInteractOutside'
38
-
39
- /** The page's single search dialog; search triggers in other islands open it by event (`contexts/search`). */
40
- const { t, locale } = getSite()
41
- let opened = $state(false)
42
- let query = $state('')
43
- let shortcutModifier = $state('⌘')
44
- let pages = $state.raw<DocumentationSearchPage[]>([])
45
- let error = $state('')
46
- let loading = $state(false)
47
- let retry = $state(0)
48
- let selected = $state(0)
49
- let dialog = $state<HTMLDialogElement>()
50
- let input = $state<HTMLInputElement>()
51
- let scroll = $state<HTMLDivElement>()
52
- let scrollTop = 0
53
- let returnFocus: HTMLElement | null = null
54
- const search = $derived(createDocumentationSearch(pages))
55
- const results = $derived(search(query))
56
-
57
- function selectQuery(value: string) {
58
- query = value
59
- selected = 0
60
- scrollTop = 0
61
- if (scroll) scroll.scrollTop = 0
62
- input?.focus()
63
- }
64
-
65
- function close() {
66
- scrollTop = scroll?.scrollTop ?? 0
67
- dialog?.close()
68
- opened = false
69
- returnFocus?.focus()
70
- }
71
-
72
- function open(event?: Event) {
73
- shortcutModifier = /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? '⌘' : 'Ctrl'
74
- const trigger = (event as CustomEvent<{ trigger?: HTMLElement }> | undefined)?.detail?.trigger
75
- returnFocus = trigger instanceof HTMLElement ? trigger : document.activeElement as HTMLElement
76
- opened = true
77
- }
78
-
79
- $effect(() => {
80
- if (!opened) return
81
- void retry
82
- let active = true
83
- loading = true
84
- error = ''
85
- loadPages(locale).then(data => {
86
- if (active) { pages = data; loading = false }
87
- }, reason => {
88
- if (active) { error = reason.message; loading = false }
2
+ import type { Component } from 'svelte'
3
+ import { openSearchEvent, prefetchSearchEvent } from '../contexts/search'
4
+ // A client-only chunk delivers no CSS of its own: link the panel's compiled styles explicitly.
5
+ import searchStyles from '../../styles/docs-shell/documentation-search.css?url&no-inline'
6
+
7
+ type Panel = Component<{ initialTrigger?: HTMLElement | null }>
8
+
9
+ /** The page's single search dialog. Its panel and styles load on the first request or on intent
10
+ * (`prefetchSearch`), so a page does not deliver dialog CSS that its first render never uses. */
11
+ let Panel = $state<Panel>()
12
+ let initialTrigger = $state<HTMLElement | null>(null)
13
+ let loading: Promise<Panel | undefined> | undefined
14
+
15
+ function linkStylesheet(href: string) {
16
+ return new Promise<void>((resolve, reject) => {
17
+ if (document.querySelector(`link[rel="stylesheet"][href="${href}"]`)) return resolve()
18
+ const link = Object.assign(document.createElement('link'), { rel: 'stylesheet', href })
19
+ link.onload = () => resolve()
20
+ link.onerror = () => { link.remove(); reject(new Error(`Cannot load ${href}`)) }
21
+ document.head.append(link)
89
22
  })
90
- return () => { active = false }
91
- })
23
+ }
92
24
 
93
- $effect(() => {
94
- if (opened && dialog) {
95
- untrack(() => {
96
- if (!dialog!.open) dialog!.showModal()
97
- input?.focus()
98
- if (scroll) scroll.scrollTop = scrollTop
25
+ // The panel mounts only after its styles apply, so its first frame is styled.
26
+ function load() {
27
+ return loading ??= Promise.all([import('./SearchDialogPanel.svelte'), linkStylesheet(searchStyles)])
28
+ .then(([module]) => module.default)
29
+ .catch(error => {
30
+ loading = undefined
31
+ console.error('Search dialog could not load', error)
99
32
  })
100
- }
101
- })
102
-
103
- const closeOnBackdropClick: Attachment<HTMLDialogElement> = (element) => {
104
- const onclick = (event: MouseEvent) => {
105
- if (event.target === element && isInteractOutside(element, event)) close()
106
- }
107
- element.addEventListener('click', onclick)
108
- return () => element.removeEventListener('click', onclick)
109
33
  }
110
34
 
111
- $effect(() => {
112
- window.addEventListener(openSearchEvent, open)
113
- return () => window.removeEventListener(openSearchEvent, open)
114
- })
35
+ async function request(event: Event) {
36
+ const trigger = (event as CustomEvent<{ trigger?: HTMLElement }>).detail?.trigger
37
+ initialTrigger = trigger instanceof HTMLElement ? trigger : document.activeElement as HTMLElement | null
38
+ Panel = await load()
39
+ }
115
40
 
41
+ // Until the panel mounts, it owns no listeners: forward the first opening to it.
116
42
  function shortcut(event: KeyboardEvent) {
117
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { event.preventDefault(); opened ? close() : open() }
43
+ if (Panel || !(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 'k') return
44
+ event.preventDefault()
45
+ void request(event)
118
46
  }
119
47
 
120
- function onkeydown(event: KeyboardEvent) {
121
- if (event.isComposing || event.keyCode === 229) return
122
- if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); close(); return }
123
- if (event.key === 'Enter' && results.length) { event.preventDefault(); document.getElementById(`documentation-result-${selected}`)?.click() }
124
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
125
- event.preventDefault()
126
- const next = Math.max(0, Math.min(results.length - 1, selected + (event.key === 'ArrowDown' ? 1 : -1)))
127
- selected = next
128
- document.getElementById(`documentation-result-${next}`)?.scrollIntoView({ block: 'nearest' })
48
+ $effect(() => {
49
+ if (Panel) return
50
+ const prefetch = () => void load()
51
+ window.addEventListener(openSearchEvent, request)
52
+ window.addEventListener(prefetchSearchEvent, prefetch)
53
+ return () => {
54
+ window.removeEventListener(openSearchEvent, request)
55
+ window.removeEventListener(prefetchSearchEvent, prefetch)
129
56
  }
130
- }
131
-
132
- const kinds: Record<string, string> = { utility: 'Utility', rule: 'Rule', tokens: 'Tokens', directive: 'Directive', tool: 'Tool', package: 'API' }
133
-
57
+ })
134
58
  </script>
135
59
 
136
60
  <svelte:window onkeydown={shortcut} />
137
61
 
138
- {#snippet excerpt(text: string, query: string)}
139
- {@const index = text.toLowerCase().indexOf(query.toLowerCase())}
140
- {@const start = Math.max(0, index - 45)}
141
- {@const value = text.slice(start, start + 220)}
142
- {@const offset = value.toLowerCase().indexOf(query.toLowerCase())}
143
- {start > 0 ? '…' : ''}{#if offset < 0}{value}{:else}{value.slice(0, offset)}<mark>{value.slice(offset, offset + query.length)}</mark>{value.slice(offset + query.length)}{/if}{start + 220 < text.length ? '…' : ''}
144
- {/snippet}
145
-
146
- {#if opened}
147
- <dialog bind:this={dialog} {@attach portal} {@attach closeOnBackdropClick} class="ui-dialog" data-variant="search" aria-label={t('Search documentation')} oncancel={close}>
148
- <div class="ui-dialog-search">
149
- <IconSearch size={18} stroke={1.5} aria-hidden="true" />
150
- <input bind:this={input} type="search" role="combobox" aria-autocomplete="list" aria-expanded={Boolean(query)} value={query} autocomplete="off" spellcheck={false} placeholder={t('Search documentation…')} aria-label={t('Search documentation')} aria-controls="documentation-search-results" aria-activedescendant={results[selected] ? `documentation-result-${selected}` : undefined}
151
- oninput={event => selectQuery(event.currentTarget.value)}
152
- {onkeydown} />
153
- {#if query}<button type="button" class="ui-button" data-variant="quiet" data-shape="icon" data-size="sm" onclick={() => selectQuery('')} aria-label={t('Clear search')}><IconX size={16} stroke={1.5} aria-hidden="true" /></button>{/if}
154
- <button type="button" class="ui-button ui-dialog-close" data-variant="quiet" data-size="sm" onclick={close} aria-label={t('Close search')}><span class="ui-kbd">esc</span><span>{t('Cancel')}</span></button>
155
- </div>
156
- <div class="ui-dialog-body" bind:this={scroll}>
157
- <p class="ui-dialog-status" role="status">{loading ? (t('Loading documentation…')) : error ? (t('Search could not load.')) : query ? t(results.length === 40 ? 'Top {count} results' : '{count} results').replace('{count}', String(results.length)) : (t('Find a name, alias or CSS property'))}</p>
158
- {#if error}<div class="ui-dialog-note"><p>{t('Check your connection and try again.')}</p><button type="button" class="ui-button" data-size="sm" onclick={() => { retry += 1; input?.focus() }}>{t('Try again')}</button></div>{/if}
159
- {#if !query && !error && !loading}<div class="ui-dialog-note">
160
- <p>{t('Try searching')}</p>
161
- {#each ['padding', 'padding-inline-start:', 'fg-red:hover@sm'] as value (value)}<button type="button" class="ui-button" data-size="sm" onclick={() => selectQuery(value)}><code>{value}</code><IconCornerDownLeft size={14} stroke={1.5} aria-hidden="true" /></button>{/each}
162
- </div>{/if}
163
- {#if query && !results.length && !error && !loading}<div class="ui-dialog-note" data-align="center"><IconSearch size={24} stroke={1.25} aria-hidden="true" /><b>{t('No matching documents')}</b><p>{t('Try a property name, a token name or a different keyword.')}</p></div>{/if}
164
- <ol id="documentation-search-results" class="ui-results" role="listbox" aria-label={t('Search results')} aria-busy={loading}>
165
- {#if !error}{#each results as result, index (result.page.url)}<li role="presentation">
166
- <Link id={`documentation-result-${index}`} role="option" tabindex={-1} aria-selected={selected === index} href={result.href} onclick={close} onpointermove={() => { selected = index }}>
167
- <small>{t(result.page.category ?? '')} · {t(kinds[result.page.kind ?? ''] ?? (result.page.url.includes('/guide') ? 'Guide' : 'Page'))} · {t(result.reason)}</small>
168
- <b>{result.page.title}</b>
169
- <span>{@render excerpt(result.excerpt, query)}</span>
170
- </Link>
171
- </li>{/each}{/if}
172
- </ol>
173
- </div>
174
- <div class="ui-dialog-foot" aria-hidden="true"><span><span class="ui-kbd"><IconArrowUp size={12} /></span><span class="ui-kbd"><IconArrowDown size={12} /></span>{t('Navigate')}</span><span><span class="ui-kbd"><IconCornerDownLeft size={12} /></span>{t('Open')}</span><span class="ui-search-shortcut">{t('Search anytime')}<kbd class="ui-kbd">{shortcutModifier}</kbd><kbd class="ui-kbd">K</kbd></span></div>
175
- </dialog>
176
- {/if}
62
+ {#if Panel}<Panel {initialTrigger} />{/if}