@getvitops/generator 8.3.0-alpha.0 → 8.3.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/assets/css/index.css +1 -0
- package/assets/css/patterns/card.css +6 -0
- package/assets/css/patterns/counter.css +17 -0
- package/assets/css/patterns/logos.css +20 -0
- package/assets/css/patterns/section.css +8 -0
- package/assets/css/patterns/sitenav.css +19 -1
- package/assets/css/patterns/stats.css +3 -0
- package/assets/css/patterns/steps.css +7 -0
- package/assets/css/patterns/widget-area.css +22 -0
- package/dist/index.d.mts +11 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +69 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/config-BfcWKH-5.mjs +0 -1743
- package/dist/config-BfcWKH-5.mjs.map +0 -1
- package/dist/config-Bhg3Xv3D.mjs +0 -1774
- package/dist/config-Bhg3Xv3D.mjs.map +0 -1
- package/dist/schema-BV2n2phj.d.mts +0 -291
- package/dist/schema-BV2n2phj.d.mts.map +0 -1
- package/dist/tokens-BzBbTeTi.d.mts +0 -225
- package/dist/tokens-BzBbTeTi.d.mts.map +0 -1
- package/dist/tokens-C1RJwFeG.mjs +0 -695
- package/dist/tokens-C1RJwFeG.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { dirname, join } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { transform } from "lightningcss";
|
|
9
|
+
import { pickOn } from "@getvitops/utils/color";
|
|
9
10
|
import { AD_PLATFORMS, AD_PROVIDER_KEYS } from "@getvitops/utils/ads";
|
|
10
11
|
import { generateIconInclude, nodesToHtml, resolveIcon, t } from "@getvitops/utils";
|
|
11
12
|
import { resolveSitemapUrl } from "@getvitops/utils/indexing";
|
|
@@ -49,7 +50,7 @@ const TIERS = {
|
|
|
49
50
|
use: "`<Tree items={…} />` — it emits `<wc-tree>` itself, so do NOT add your own wrapper."
|
|
50
51
|
},
|
|
51
52
|
carousel: {
|
|
52
|
-
emdash: ["
|
|
53
|
+
emdash: ["vitops_carousel"],
|
|
53
54
|
css: c([
|
|
54
55
|
"carousel",
|
|
55
56
|
"carousel__track",
|
|
@@ -67,7 +68,7 @@ const TIERS = {
|
|
|
67
68
|
use: "`<Carousel slides={…} label=\"…\" />` — it emits `<wc-carousel>` itself, so do NOT add your own wrapper. `.carousel` is the shell and `.carousel__track` the scroller; add `loop` for the cloned infinite strip (`autoplay` implies it)."
|
|
68
69
|
},
|
|
69
70
|
gallery: {
|
|
70
|
-
emdash: ["
|
|
71
|
+
emdash: ["vitops_gallery"],
|
|
71
72
|
css: c([
|
|
72
73
|
"gallery",
|
|
73
74
|
"gallery__item",
|
|
@@ -83,7 +84,7 @@ const TIERS = {
|
|
|
83
84
|
use: "`<Gallery images={…} label=\"…\" />` — it emits `<wc-gallery>` wrapping a `<Carousel />` (which emits its own `<wc-carousel>`) itself, so do NOT add your own wrapper around either. Each thumbnail is a plain `<a href=\"#slide-id\">` into the carousel's own `<li id=\"slide-id\">` inside a single `<dialog class=\"lightbox-dialog\">` (see `lightbox`), so it is a working modal lightbox — WITH prev/next — before any script runs. Stepping between images is the carousel's job, not the gallery's."
|
|
84
85
|
},
|
|
85
86
|
entries: {
|
|
86
|
-
emdash: ["
|
|
87
|
+
emdash: ["vitops_entries"],
|
|
87
88
|
css: c([
|
|
88
89
|
"entries__table",
|
|
89
90
|
"entries__scroll",
|
|
@@ -99,7 +100,7 @@ const TIERS = {
|
|
|
99
100
|
use: "`<Entries entries={[{ heading, pairs: [{ term, value }] }]} />` — it emits `<wc-entries>` itself, so do NOT add your own wrapper. By hand: `<wc-entries>` around `<h3>` + `<dl>` pairs. The tier-2 exemplar: semantic pairs with no JS, a table with it."
|
|
100
101
|
},
|
|
101
102
|
copy: {
|
|
102
|
-
emdash: ["
|
|
103
|
+
emdash: ["vitops_copyButton"],
|
|
103
104
|
css: c([
|
|
104
105
|
"copy",
|
|
105
106
|
"copy__button",
|
|
@@ -115,7 +116,7 @@ const TIERS = {
|
|
|
115
116
|
use: "`<wc-copy>` around the content and a `[data-copy]` button."
|
|
116
117
|
},
|
|
117
118
|
dismissable: {
|
|
118
|
-
emdash: ["
|
|
119
|
+
emdash: ["vitops_banner"],
|
|
119
120
|
css: c(["banner__close", "notification__close"], "patterns/banner.css"),
|
|
120
121
|
wc: {
|
|
121
122
|
tag: "wc-dismissable",
|
|
@@ -141,7 +142,7 @@ const TIERS = {
|
|
|
141
142
|
use: "`<wc-split-panel>` with two children. Unenhanced they simply stack."
|
|
142
143
|
},
|
|
143
144
|
"image-compare": {
|
|
144
|
-
emdash: ["
|
|
145
|
+
emdash: ["vitops_imageCompare"],
|
|
145
146
|
css: c([
|
|
146
147
|
"image-compare",
|
|
147
148
|
"image-compare__before",
|
|
@@ -156,7 +157,7 @@ const TIERS = {
|
|
|
156
157
|
use: "`<wc-image-compare>` with two images. Both are visible without JS."
|
|
157
158
|
},
|
|
158
159
|
marquee: {
|
|
159
|
-
emdash: ["
|
|
160
|
+
emdash: ["vitops_marquee"],
|
|
160
161
|
css: c([
|
|
161
162
|
"marquee",
|
|
162
163
|
"marquee__content",
|
|
@@ -188,12 +189,13 @@ const TIERS = {
|
|
|
188
189
|
"section__sub",
|
|
189
190
|
"section__body",
|
|
190
191
|
"section--split",
|
|
191
|
-
"section--center"
|
|
192
|
+
"section--center",
|
|
193
|
+
"block-anchor"
|
|
192
194
|
], "patterns/section.css"),
|
|
193
195
|
use: "`.section.region` > `.centered` > `.section__inner` holding `.section__head` (kicker, heading, sub) and `.section__body`. `.section--split` puts the head beside the body when wide. In EmDash it is a block TYPE (`vitops_section`) with a Portable Text body that accepts any other block — see `vitopsBlockTypes()`."
|
|
194
196
|
},
|
|
195
197
|
steps: {
|
|
196
|
-
emdash: ["
|
|
198
|
+
emdash: ["vitops_steps"],
|
|
197
199
|
css: c([
|
|
198
200
|
"steps",
|
|
199
201
|
"steps__item",
|
|
@@ -205,7 +207,7 @@ const TIERS = {
|
|
|
205
207
|
use: "`<ol class=\"steps tone\">` of `<li class=\"steps__item\">` each holding a `.steps__body` with a real heading. The number is a CSS counter and the connector line a pseudo-element; `.steps--row` runs it sideways when wide."
|
|
206
208
|
},
|
|
207
209
|
stats: {
|
|
208
|
-
emdash: ["
|
|
210
|
+
emdash: ["vitops_stats"],
|
|
209
211
|
css: c([
|
|
210
212
|
"stats",
|
|
211
213
|
"stats__item",
|
|
@@ -218,7 +220,7 @@ const TIERS = {
|
|
|
218
220
|
use: "`<ul class=\"stats\">` of `.stats__item`, each figure a `<Counter>`. `.stats--tiles` adds `.stats__group` and `.stats__delta`; `data-state=\"up|down\"` colours the delta through the success/danger roles."
|
|
219
221
|
},
|
|
220
222
|
callout: {
|
|
221
|
-
emdash: ["
|
|
223
|
+
emdash: ["vitops_callout"],
|
|
222
224
|
css: c([
|
|
223
225
|
"callout",
|
|
224
226
|
"callout__body",
|
|
@@ -230,16 +232,17 @@ const TIERS = {
|
|
|
230
232
|
use: "`.callout.tone` with a `.callout__body` and `.callout__actions`. A call-to-action band that stays on the page — NOT `banner`, which is a dismissible announcement."
|
|
231
233
|
},
|
|
232
234
|
logos: {
|
|
233
|
-
emdash: ["
|
|
235
|
+
emdash: ["vitops_logos"],
|
|
234
236
|
css: c([
|
|
235
237
|
"logos",
|
|
236
238
|
"logos__item",
|
|
239
|
+
"logos__name",
|
|
237
240
|
"logos--grayscale"
|
|
238
241
|
], "patterns/logos.css"),
|
|
239
|
-
use: "`<ul class=\"logos\">` of `.logos__item` images (alt text = the name). A still grid; `marquee` is the scrolling version."
|
|
242
|
+
use: "`<ul class=\"logos\">` of `.logos__item` images (alt text = the name), or `.logos__name` text when there is no image. A still grid; `marquee` is the scrolling version."
|
|
240
243
|
},
|
|
241
244
|
embed: {
|
|
242
|
-
emdash: ["
|
|
245
|
+
emdash: ["vitops_embed"],
|
|
243
246
|
css: c([
|
|
244
247
|
"embed",
|
|
245
248
|
"embed__frame",
|
|
@@ -248,7 +251,7 @@ const TIERS = {
|
|
|
248
251
|
use: "`.embed` around an `<iframe class=\"embed__frame\" data-consent=\"marketing\" data-consent-src=\"…\">` with NO `src`, and a `.embed__placeholder`. The consent runtime assigns the address after the grant; the placeholder holds a real link. Never give the iframe a live `src`."
|
|
249
252
|
},
|
|
250
253
|
counter: {
|
|
251
|
-
emdash: ["
|
|
254
|
+
emdash: ["vitops_stats"],
|
|
252
255
|
css: c([
|
|
253
256
|
"counter",
|
|
254
257
|
"counter__value",
|
|
@@ -263,7 +266,7 @@ const TIERS = {
|
|
|
263
266
|
use: "`<Counter value=\"94%\" />` — it emits `<wc-counter>` itself, so do NOT add your own wrapper. The final value is parsed from the fallback text, never passed separately."
|
|
264
267
|
},
|
|
265
268
|
"multi-field": {
|
|
266
|
-
emdashExempt: "repeats rows a visitor adds at submit time (several phone numbers, guests); `
|
|
269
|
+
emdashExempt: "repeats rows a visitor adds at submit time (several phone numbers, guests); `vitops_form` has a fixed, editor-defined field list and no such field type",
|
|
267
270
|
css: c([
|
|
268
271
|
"form-group",
|
|
269
272
|
"input-group",
|
|
@@ -278,14 +281,14 @@ const TIERS = {
|
|
|
278
281
|
use: "`<wc-multi-field>` around a field template. One row submits fine with no JS."
|
|
279
282
|
},
|
|
280
283
|
turnstile: {
|
|
281
|
-
emdash: ["
|
|
284
|
+
emdash: ["vitops_form"],
|
|
282
285
|
css: c(["form-group"], "patterns/forms.css"),
|
|
283
286
|
wc: {
|
|
284
287
|
tag: "wc-turnstile",
|
|
285
288
|
registered: true,
|
|
286
289
|
adds: "loads Cloudflare Turnstile on first interaction with the form and holds a submit until its token arrives"
|
|
287
290
|
},
|
|
288
|
-
use: "`<wc-turnstile sitekey=\"…\">` inside a `<form>`, around a paragraph saying the spam check needs JavaScript. Pair it with `createConversionRoute({ turnstile })`; the `
|
|
291
|
+
use: "`<wc-turnstile sitekey=\"…\">` inside a `<form>`, around a paragraph saying the spam check needs JavaScript. Pair it with `createConversionRoute({ turnstile })`; the `vitops_form` block does both."
|
|
289
292
|
},
|
|
290
293
|
"color-scheme-toggle": {
|
|
291
294
|
emdashExempt: "site chrome (header/footer), not page content; belongs in the nav/layout",
|
|
@@ -374,7 +377,7 @@ const TIERS = {
|
|
|
374
377
|
use: "Not shipped. See `color-wheel`."
|
|
375
378
|
},
|
|
376
379
|
details: {
|
|
377
|
-
emdash: ["
|
|
380
|
+
emdash: ["vitops_details", "vitops_accordion"],
|
|
378
381
|
css: c([
|
|
379
382
|
"details",
|
|
380
383
|
"details-trigger",
|
|
@@ -412,7 +415,7 @@ const TIERS = {
|
|
|
412
415
|
use: "`<NavShell>` with a `nav` slot, plus `<NavShellToggle>` if the toggle must live outside the shell."
|
|
413
416
|
},
|
|
414
417
|
subgrid: {
|
|
415
|
-
emdash: ["
|
|
418
|
+
emdash: ["vitops_pricing"],
|
|
416
419
|
css: c([
|
|
417
420
|
"subgrid",
|
|
418
421
|
"subgrid-card",
|
|
@@ -422,8 +425,8 @@ const TIERS = {
|
|
|
422
425
|
use: "`<Subgrid>` — emits `<ul class=\"subgrid\" role=\"list\">`; author `<li class=\"card subgrid-card\">` items yourself, so tranches align across the set."
|
|
423
426
|
},
|
|
424
427
|
card: {
|
|
425
|
-
emdash: ["
|
|
426
|
-
css: c(["card"], "patterns/card.css", true),
|
|
428
|
+
emdash: ["vitops_cards"],
|
|
429
|
+
css: c(["card", "card__title"], "patterns/card.css", true),
|
|
427
430
|
wc: {
|
|
428
431
|
tag: "wc-cards",
|
|
429
432
|
registered: true,
|
|
@@ -443,7 +446,7 @@ const TIERS = {
|
|
|
443
446
|
use: "`<Icon name=\"menu\" />`. Names are semantic and resolve per configured set; a name containing `:` passes through."
|
|
444
447
|
},
|
|
445
448
|
split: {
|
|
446
|
-
emdash: ["
|
|
449
|
+
emdash: ["vitops_hero"],
|
|
447
450
|
css: c([
|
|
448
451
|
"split",
|
|
449
452
|
"split-1-2",
|
|
@@ -471,9 +474,9 @@ const TIERS = {
|
|
|
471
474
|
"navlist--columns"
|
|
472
475
|
], "patterns/sitenav.css"),
|
|
473
476
|
astro: [...a("SiteNav", "css"), ...a("SiteFooter", "css")],
|
|
474
|
-
emdash: ["
|
|
477
|
+
emdash: ["vitops_nav"],
|
|
475
478
|
bricks: "vitops-sitenav",
|
|
476
|
-
use: "`.sitenav` with `--bp-*` and a drawer direction. `<details>` + Invoker Commands, no JS. In Astro: `<SiteNav {...await loadNavMenus(getMenu)} />` renders EmDash menu `primary` (the default: dropdowns from `md`, drawer below) and, if they exist, `primary-mobile` (depth-1 nav below `md`) and `primary-mega` (mega panels from `xl`) — adding the menu in the admin is the whole change. An item's `titleAttr` is its description. `<SiteFooter sitemap legal name>` renders `footer-sitemap` + `footer-legal` + the copyright line. For a menu inside page content (a site map, a section's links, footer-style columns) use `.navlist` — plain nested lists, `--inline` / `--columns` — or, in EmDash, the `
|
|
479
|
+
use: "`.sitenav` with `--bp-*` and a drawer direction. `<details>` + Invoker Commands, no JS. In Astro: `<SiteNav {...await loadNavMenus(getMenu)} />` renders EmDash menu `primary` (the default: dropdowns from `md`, drawer below) and, if they exist, `primary-mobile` (depth-1 nav below `md`) and `primary-mega` (mega panels from `xl`) — adding the menu in the admin is the whole change. An item's `titleAttr` is its description. `<SiteFooter sitemap legal name>` renders `footer-sitemap` + `footer-legal` + the copyright line. For a menu inside page content (a site map, a section's links, footer-style columns) use `.navlist` — plain nested lists, `--inline` / `--columns` — or, in EmDash, the `vitops_nav` block (menu, layout `list|inline|columns|sitenav`, depth 1–3, heading, tone). Name each `<nav>` uniquely: the header is `Primary` and the footer `Site map` / `Legal`, and the block never reuses those."
|
|
477
480
|
},
|
|
478
481
|
"split-link": {
|
|
479
482
|
css: c([
|
|
@@ -485,17 +488,17 @@ const TIERS = {
|
|
|
485
488
|
use: "`.split-link` — a link plus a disclosure toggle. No JS."
|
|
486
489
|
},
|
|
487
490
|
btn: {
|
|
488
|
-
emdash: ["
|
|
491
|
+
emdash: ["vitops_actionLink"],
|
|
489
492
|
css: c(["btn"], void 0, true),
|
|
490
493
|
use: "`<button>` gets it with no class; `.btn` carries it to other tags. `fill: false`, so states drive `color`."
|
|
491
494
|
},
|
|
492
495
|
cta: {
|
|
493
|
-
emdash: ["
|
|
496
|
+
emdash: ["vitops_actionLink"],
|
|
494
497
|
css: c(["cta", "cta-danger"], void 0, true),
|
|
495
498
|
use: "`.cta` on any element — usually `<a>`, since a CTA navigates. Filled, with role variants."
|
|
496
499
|
},
|
|
497
500
|
link: {
|
|
498
|
-
emdash: ["
|
|
501
|
+
emdash: ["vitops_actionLink"],
|
|
499
502
|
css: c([
|
|
500
503
|
"link",
|
|
501
504
|
"skip-link",
|
|
@@ -505,9 +508,19 @@ const TIERS = {
|
|
|
505
508
|
use: "`<a>` gets it at zero specificity; `.link` for other tags. `.stretched-link` makes one link cover its positioned ancestor (whole-card click, no JS — but the card text stops being selectable); `.raised` lifts content back above that overlay."
|
|
506
509
|
},
|
|
507
510
|
badge: {
|
|
511
|
+
emdash: ["vitops_badges"],
|
|
508
512
|
css: c(["badge", "badge-indicator"], "patterns/tag.css", true),
|
|
509
513
|
use: "`.badge` — a STATIC label. Use `tag` if it is dismissable or editable."
|
|
510
514
|
},
|
|
515
|
+
"widget-area": {
|
|
516
|
+
emdash: ["vitops_widgetArea"],
|
|
517
|
+
css: c([
|
|
518
|
+
"widget-area",
|
|
519
|
+
"widget__title",
|
|
520
|
+
"widget__menu"
|
|
521
|
+
], "patterns/widget-area.css"),
|
|
522
|
+
use: "Editor-managed regions: `<VitopsWidgetArea name=\"footer-columns\" />` (`@getvitops/emdash/astro`) renders an area from Widgets in the admin — titled groups, a menu as a labelled `<nav>`, content as Portable Text. In a page, the `vitops_widgetArea` block places one. An area holding a `core:*` component widget falls back to EmDash's own renderer, and an area cannot hold another. Prerendered pages show an edit after the next deploy."
|
|
523
|
+
},
|
|
511
524
|
tag: {
|
|
512
525
|
css: c([
|
|
513
526
|
"tag",
|
|
@@ -570,8 +583,8 @@ const TIERS = {
|
|
|
570
583
|
"review__source"
|
|
571
584
|
], "patterns/review.css", true),
|
|
572
585
|
astro: a("ReviewCard", "css"),
|
|
573
|
-
emdash: ["
|
|
574
|
-
use: "`<ReviewCard review={…} />` — one card, from `vitops reviews`' `.vitops/reviews.json` cache. Put several inside `<Carousel>` slides or `<Marquee>` items; there is no group component and no `wc-review`. In EmDash, `
|
|
586
|
+
emdash: ["vitops_reviews"],
|
|
587
|
+
use: "`<ReviewCard review={…} />` — one card, from `vitops reviews`' `.vitops/reviews.json` cache. Put several inside `<Carousel>` slides or `<Marquee>` items; there is no group component and no `wc-review`. In EmDash, `vitops_reviews` is that whole row: it reads the cache as a build-time import (`virtual:getvitops/reviews`, served by `vitops()`; empty when the file is absent), picks reviews with `selectReviews()`, and lays them out as a grid, carousel or marquee. A new review appears after `vitops reviews` is re-run, committed and the site rebuilt."
|
|
575
588
|
},
|
|
576
589
|
tabs: {
|
|
577
590
|
css: c(["tabs"], void 0, true),
|
|
@@ -595,7 +608,7 @@ const TIERS = {
|
|
|
595
608
|
use: "`.navbar` with `--start/--center/--end`."
|
|
596
609
|
},
|
|
597
610
|
banner: {
|
|
598
|
-
emdash: ["
|
|
611
|
+
emdash: ["vitops_banner"],
|
|
599
612
|
css: c([
|
|
600
613
|
"banner",
|
|
601
614
|
"banner__content",
|
|
@@ -2494,7 +2507,7 @@ ${bricks}
|
|
|
2494
2507
|
Portable Text block ${code("_type")}s from ${code("@getvitops/emdash")}. Repeating patterns are
|
|
2495
2508
|
repeater fields. A Portable Text block cannot contain other blocks, so a pattern that must hold
|
|
2496
2509
|
them is a schema-level block TYPE with a Portable Text body instead — ${code("vitops_section")},
|
|
2497
|
-
from ${code("vitopsBlockTypes()")}. Its body accepts every ${code("
|
|
2510
|
+
from ${code("vitopsBlockTypes()")}. Its body accepts every ${code("vitops_*")} block below.
|
|
2498
2511
|
|
|
2499
2512
|
| Block | Pattern | Renders via |
|
|
2500
2513
|
| --- | --- | --- |
|
|
@@ -4418,7 +4431,7 @@ function toHtmlFragment(blocks) {
|
|
|
4418
4431
|
* Two deliberate mappings:
|
|
4419
4432
|
* - the `# ` heading is **dropped** — in EmDash the page title is a field
|
|
4420
4433
|
* (`data.title`), and repeating it as the first content block renders twice;
|
|
4421
|
-
* - the review blockquote becomes a `
|
|
4434
|
+
* - the review blockquote becomes a `vitops_banner` with `tone: "warning"`,
|
|
4422
4435
|
* which is exactly the block the hand-written placeholder pages used.
|
|
4423
4436
|
*
|
|
4424
4437
|
* `_key`s are derived from position rather than randomness, so regenerating an
|
|
@@ -4431,7 +4444,7 @@ function toPortableText(blocks, prefix) {
|
|
|
4431
4444
|
if (b.kind === "heading" && b.level === 1) return;
|
|
4432
4445
|
if (b.kind === "quote") {
|
|
4433
4446
|
out.push({
|
|
4434
|
-
_type: "
|
|
4447
|
+
_type: "vitops_banner",
|
|
4435
4448
|
_key: key,
|
|
4436
4449
|
message: b.spans.map((s) => s.text).join(""),
|
|
4437
4450
|
tone: "warning",
|
|
@@ -4483,7 +4496,7 @@ const REVIEW_NOTICE = "> Generated from this site's configuration. It is a start
|
|
|
4483
4496
|
* designated and is published, that a privacy impact assessment precedes any
|
|
4484
4497
|
* communication outside Quebec, that consent mechanics match the Act, and — easy
|
|
4485
4498
|
* to miss — that content directed at Quebec consumers is available in French. It
|
|
4486
|
-
* is a second blockquote (a second `
|
|
4499
|
+
* is a second blockquote (a second `vitops_banner` in Portable Text) so it cannot
|
|
4487
4500
|
* be skimmed past as part of the first.
|
|
4488
4501
|
*/
|
|
4489
4502
|
const QC_REVIEW_NOTICE = "> Quebec (Law 25): this document is written for the Quebec privacy regime and has NOT been reviewed by counsel. Before publishing, have a lawyer confirm that a person in charge of the protection of personal information is designated, that any transfer of information outside Quebec is preceded by a privacy impact assessment, that your consent and cookie mechanisms match the Act, and whether a French version is required for your audience. It is not legal advice.";
|
|
@@ -5223,6 +5236,18 @@ function generateLegal(cfg, opts = {}) {
|
|
|
5223
5236
|
*/
|
|
5224
5237
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
5225
5238
|
const DEFAULT_ASSETS = join(HERE, "..", "assets");
|
|
5239
|
+
/**
|
|
5240
|
+
* `--color-<hue>-<step>-on`: the readable text colour for a fill at that step, computed with the
|
|
5241
|
+
* same APCA pick as `text-on-<role>`. A role has one (`--color-text-on-<role>`); a raw hue step
|
|
5242
|
+
* did not, so a block filling with `color: amber-500` had no site-defined foreground to use. These
|
|
5243
|
+
* live in a plain `:root`, never `@theme`: a `--color-*` theme key would mint `text-<hue>-<step>-on`
|
|
5244
|
+
* utilities nobody asked for.
|
|
5245
|
+
*/
|
|
5246
|
+
function hueOnTokens(palette) {
|
|
5247
|
+
const out = [];
|
|
5248
|
+
for (const [name, hue] of Object.entries(palette)) for (const [n, hex] of Object.entries(hue.numeric)) out.push(`--color-${name}-${n}-on: ${pickOn(hex)};`);
|
|
5249
|
+
return out;
|
|
5250
|
+
}
|
|
5226
5251
|
const UTILITY_PROPS = {
|
|
5227
5252
|
bg: "background-color",
|
|
5228
5253
|
text: "color",
|
|
@@ -5511,6 +5536,8 @@ function build(ds, format, assetsDir, buildOpts = {}) {
|
|
|
5511
5536
|
css += ` color-scheme: ${buildOpts.darkOnly ? "dark" : "light"};\n`;
|
|
5512
5537
|
css += ` /* Hue scales (numeric steps, 50 = tinted near-white … 950 = tinted near-black) */\n`;
|
|
5513
5538
|
for (const [name, hue] of Object.entries(expandedPalette)) for (const [n, hex] of Object.entries(hue.numeric)) css += ` --color-${name}-${n}: ${hex};\n`;
|
|
5539
|
+
css += `\n /* Foreground for a fill at a hue step (--color-<hue>-<step>-on) */\n`;
|
|
5540
|
+
for (const line of hueOnTokens(expandedPalette)) css += ` ${line}\n`;
|
|
5514
5541
|
css += `\n /* Role tokens (the public API) */\n`;
|
|
5515
5542
|
for (const fr of scaleRoles) for (const [t, val] of Object.entries(fr.light)) css += ` ${tokenVar(fr.role, t)}: ${val};\n`;
|
|
5516
5543
|
if (focusRole) {
|
|
@@ -5970,6 +5997,7 @@ function build(ds, format, assetsDir, buildOpts = {}) {
|
|
|
5970
5997
|
assetsDir,
|
|
5971
5998
|
utilities: UTILITIES,
|
|
5972
5999
|
systemColorScheme: buildOpts.systemColorScheme === true,
|
|
6000
|
+
sources: buildOpts.sources ?? [],
|
|
5973
6001
|
darkOnly: buildOpts.darkOnly === true
|
|
5974
6002
|
}),
|
|
5975
6003
|
designMd: format !== "design" ? "" : emitDesignMd({
|
|
@@ -5990,7 +6018,9 @@ function emitTailwind(ctx) {
|
|
|
5990
6018
|
const cssDir = join(assetsDir, "css");
|
|
5991
6019
|
const nl = (arr) => arr.filter(Boolean).join("\n");
|
|
5992
6020
|
const parts = ["/* GENERATED for Tailwind v4 (Astro) — do not edit by hand.\n Class reference: npx vitops docs classes · All topics: npx vitops docs\n Fonts below are STACKS ONLY — this file loads no webfonts. Load them with\n Astro's fonts: config and point the token at the family's cssVariable. */"];
|
|
5993
|
-
parts.push(`@import "tailwindcss"
|
|
6021
|
+
parts.push(`@import "tailwindcss";`);
|
|
6022
|
+
for (const src of ctx.sources) parts.push(`@source ${JSON.stringify(src)};`);
|
|
6023
|
+
parts.push("");
|
|
5994
6024
|
const theme = [];
|
|
5995
6025
|
theme.push(` /* Hue scales → bg-<hue>-<step> text-* border-* (numeric, 50…950) */`);
|
|
5996
6026
|
for (const [name, hue] of Object.entries(expandedPalette)) for (const [n, hex] of Object.entries(hue.numeric)) theme.push(` --color-${name}-${n}: ${hex};`);
|
|
@@ -6020,6 +6050,7 @@ function emitTailwind(ctx) {
|
|
|
6020
6050
|
fn += ` --surface-glass: color-mix(in oklch, var(--color-bg-surface) 72%, transparent);\n`;
|
|
6021
6051
|
fn += ` --overlay: color-mix(in oklch, var(--color-${surface.hue}-950) 45%, transparent);\n`;
|
|
6022
6052
|
}
|
|
6053
|
+
for (const line of hueOnTokens(expandedPalette)) fn += ` ${line}\n`;
|
|
6023
6054
|
if (ctx.darkOnly) fn = ` color-scheme: dark;\n${fn}`;
|
|
6024
6055
|
parts.push(`/* Role tokens (the public API) */\n:root {\n${fn}}\n`);
|
|
6025
6056
|
}
|
|
@@ -6515,7 +6546,8 @@ async function generate(options) {
|
|
|
6515
6546
|
const config = options.site != null ? loadConfigFile(options.site, options.siteEnv) : embedded;
|
|
6516
6547
|
const built = build(ds, format, assetsDir, {
|
|
6517
6548
|
systemColorScheme: options.systemColorScheme ?? config?.designSystem?.defaultColorScheme === "system",
|
|
6518
|
-
darkOnly: config?.designSystem?.defaultColorScheme === "dark"
|
|
6549
|
+
darkOnly: config?.designSystem?.defaultColorScheme === "dark",
|
|
6550
|
+
...options.sources?.length ? { sources: options.sources } : {}
|
|
6519
6551
|
});
|
|
6520
6552
|
mkdirSync(outDir, { recursive: true });
|
|
6521
6553
|
const written = [];
|