@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/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: ["vitops.carousel"],
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: ["vitops.gallery"],
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: ["vitops.entries"],
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: ["vitops.copyButton"],
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: ["vitops.banner"],
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: ["vitops.imageCompare"],
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: ["vitops.marquee"],
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: ["vitops.steps"],
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: ["vitops.stats"],
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: ["vitops.callout"],
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: ["vitops.logos"],
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: ["vitops.embed"],
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: ["vitops.stats"],
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); `vitops.form` has a fixed, editor-defined field list and no such field type",
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: ["vitops.form"],
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 `vitops.form` block does both."
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: ["vitops.details", "vitops.accordion"],
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: ["vitops.pricing"],
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: ["vitops.cards"],
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: ["vitops.hero"],
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: ["vitops.nav"],
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 `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."
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: ["vitops.actionLink"],
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: ["vitops.actionLink"],
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: ["vitops.actionLink"],
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: ["vitops.reviews"],
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, `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."
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: ["vitops.banner"],
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("vitops.*")} block below.
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 `vitops.banner` with `tone: "warning"`,
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: "vitops.banner",
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 `vitops.banner` in Portable Text) so it cannot
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";\n`);
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 = [];