@samva/markup 0.13.1 → 0.15.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.
package/AGENTS.md CHANGED
@@ -23,14 +23,14 @@ Authoring entries are what a template file imports:
23
23
  functions a body may call.
24
24
  - `@samva/markup/sms` and `@samva/markup/whatsapp` — the channel components (`Sms`; `WhatsApp` with
25
25
  `WhatsApp.Header`, `Body`, `Footer`, `Buttons`, `UrlButton`, `QuickReplyButton`, `PhoneButton`,
26
- `CopyCodeButton`). `whatsappTemplate` derives Meta's positional `{{n}}` text.
26
+ `CopyCodeButton`, `FlowButton`). `whatsappTemplate` derives Meta's positional `{{n}}` text.
27
27
  - `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
28
28
  portable JSON Schema and validator a template's input is checked against.
29
29
  - `samva:brand` — `BrandLogo` (light logo, swapped for the dark one where the client honors dark
30
30
  mode) and `BrandFooter` (company, postal address, social links and the unsubscribe row the send
31
31
  path fills). The compiler binds them to the brand the build resolves; add
32
32
  `"types": ["@samva/markup/samva-brand"]` to the project's tsconfig for `tsc`. In `theme.css`,
33
- `@import "samva:brand";` layers the brand theme; `samva:brand/<slug>` names one brand. These are
33
+ `@import "samva:brand";` layers the brand theme over the default theme (`DEFAULT_THEME`); `samva:brand/<slug>` names one brand. These are
34
34
  Samva's brand plugin (`samvaBrandPlugin` from `@samva/markup/brand`), the default; a host that
35
35
  delivers mail itself passes its own `BrandPlugin` as `brandPlugin` to `compileTemplate`,
36
36
  `renderIr` and `renderBrandFooter` to rename the brand import, the footer markers, the
@@ -65,6 +65,7 @@ entry?)` is the synchronous structural check: it runs the compiler's lowering wi
65
65
  conditional or element is a locked segment; and JSX source locations. Browser-safe: Babel parser
66
66
  only.
67
67
  - `@samva/markup/brand` — brand records and their CSS model (`parseBrandCss`, `printBrandCss`), the
68
+ theme vocabulary (`THEME_COLOR_ROLES`, `THEME_TOKENS`, `DEFAULT_THEME`, `foregroundDefaults`), the
68
69
  footer markers a send reads (`renderedFooterMarkers`, `withoutUnsubscribeRow`), and project
69
70
  assets (`isAssetPath`, `ASSET_CONTENT_TYPES`, `assetUrl`): which imported files become
70
71
  content-addressed assets. The send path imports it, so its closure holds only the css-tree parser
@@ -117,6 +118,15 @@ Read the contract before authoring. Import only subpaths declared in `package.js
117
118
  into file paths, so each must match `TEMPLATE_NAME_PATTERN`, one plain path segment.
118
119
  `defineTemplate` throws a `TypeError` where the name is declared. Code that builds a path from these
119
120
  names imports the pattern instead of restating it.
121
+ - **Naming a second set of tokens for dark mode.** A theme states dark mode once, in
122
+ `@media (prefers-color-scheme: dark) { @theme { --color-card: … } }`, with the same names as its
123
+ light `@theme`. The cascade gives every element whose inlined styles read one of those colors a
124
+ generated `sml-dark-*` class, so `bg-card` switches by itself. A `--color-card-dark` token is just
125
+ another color, and `dark:` only picks a different token. A block styled with palette colors
126
+ (`bg-indigo-600`) or raw hex ignores the theme; style with the vocabulary in `THEME_COLOR_ROLES`.
127
+ - **Reading contrast off the light render.** `checkEmailCompatibility` includes `email-contrast`,
128
+ which judges each fixture in light and dark mode (`CONTRAST_THRESHOLDS`); a color a dark block
129
+ leaves unpaired is an error there, not a visual nit.
120
130
  - **Declaring a webfont as if every client loads it.** A project font is an ordinary `@font-face`
121
131
  in the project's CSS (usually `theme.css`) whose `src` is a `.woff2` file in the project, named
122
132
  by a path relative to that stylesheet; the compiler serves it as a content-addressed asset and
package/CHANGELOG.md CHANGED
@@ -1,3 +1,61 @@
1
+ ## @samva/markup@0.15.0
2
+
3
+ ### WhatsApp template FLOW buttons
4
+
5
+ `<WhatsApp.FlowButton flow="order-survey">Take survey</WhatsApp.FlowButton>` adds a Meta FLOW
6
+ button to a WhatsApp template. `flow` is the Samva flow handle (lowercase kebab-case, the file
7
+ `flows/<handle>.json` in the template project); Samva maps it to Meta's flow id per business
8
+ account when it registers the template. `action` is `navigate` by default or `data_exchange`, and
9
+ `screen` names the screen a `navigate` flow opens on (omitted, the flow's first entry screen);
10
+ `screen` is refused with `data_exchange`. `flow`, `action` and `screen` are string literals, and a
11
+ template holds at most one flow button.
12
+
13
+ The IR button is `{ type: "flow", text, flow, action, screen? }`, and the rendered WhatsApp
14
+ channel and the editor preview carry it. The editor shows a flow button as a locked row; it is
15
+ authored in source.
16
+
17
+ ## @samva/markup@0.14.0
18
+
19
+ ### A semantic theme vocabulary, one dark-mode mechanism and a contrast check
20
+
21
+ Every project compiles over a default theme in a shadcn-style vocabulary: `background`,
22
+ `foreground`, `card`, `primary`, `secondary`, `muted`, `accent` (each with its `-foreground`),
23
+ `border`, `--radius` (with `rounded-sm` to `rounded-xl` derived from it), `--font-sans` and
24
+ `--font-heading`. `starter.css`, a brand and the project's own `@theme` set the same names, so
25
+ `bg-card` or `text-primary-foreground` resolve in any project and a theme restyles every block that
26
+ reads them. `reference.md` and `contract.json` list the tokens, their roles and their defaults;
27
+ `@samva/markup/brand` exports `THEME_COLOR_ROLES`, `THEME_TOKENS` and `DEFAULT_THEME`.
28
+
29
+ Dark mode is one mechanism: the theme's `@media (prefers-color-scheme: dark) { @theme { … } }`
30
+ block, in project CSS and brand CSS alike. The compiler gives each element whose inlined styles
31
+ read a color that block redefines a generated `sml-dark-*` class with a dark-mode rule, so a block
32
+ writes `bg-card` once. Explicit `dark:` utilities still apply after it and resolve theme colors at
33
+ their dark values. A layer that sets a color in light mode only keeps it in dark mode, and a layer
34
+ that sets `--color-primary`, `--color-secondary` or `--color-accent` without its `-foreground` gets
35
+ black or white by contrast.
36
+
37
+ Breaking: the `-dark` suffix convention is gone. Nothing lowers a dark color to
38
+ `--color-<name>-dark` any more; move those values into the dark-mode block and drop the matching
39
+ `dark:…-dark` classes. `brandTailwindCss`, `brandThemeVariables`, `darkVariableName`,
40
+ `BRAND_COLOR_VARIABLE`, `BRAND_FOREGROUND_VARIABLE` and the `brand-css-dark-conflict` finding are
41
+ removed; `brandForegroundDefaults(brand)` is `foregroundDefaults(light, dark)` over the fills.
42
+ The brand footer and logo use `text-muted-foreground`.
43
+
44
+ The primitives read the theme. `Email` paints the page color (`--color-background` by default) on
45
+ the body and, as `bgcolor`, on the wrapper table, and pads its content 16px each side (`gutter`).
46
+ `Section` treats a pixel width as a maximum, so it fits a phone. `Columns` stacks its cells below
47
+ 600px (`stack={false}` keeps a row) and spaces them with `gap` in both layouts. `Button`, `Link`
48
+ and `Divider` default to the primary fill, the primary color and the border color through classes
49
+ a utility on them overrides; their explicit props are inline styles as before. These change the
50
+ compiled output of every template, so the email-parity golden digests change.
51
+
52
+ `checkEmailCompatibility` adds `email-contrast`: each fixture is judged in light and dark mode,
53
+ and text below 3:1 or a button below 1.5:1 against its surroundings is an error located at the
54
+ element; normal-size text below 4.5:1 and a button below 3:1 are warnings. New diagnostic codes:
55
+ `email-contrast`, `invalid-columns-stack`, `invalid-columns-gap` and `invalid-number-prop`.
56
+
57
+ The email starter is redesigned on the vocabulary from copyable blocks in `templates/blocks/`.
58
+
1
59
  ## @samva/markup@0.13.1
2
60
 
3
61
  ### The authoring reference omits channel-only upgrade recipes
package/README.md CHANGED
@@ -194,7 +194,7 @@ explicit argument overrides it.
194
194
  One template carries one schema and a body for each channel it supports. `@samva/markup/sms` and
195
195
  `@samva/markup/whatsapp` export the channel components (`Sms`; `WhatsApp` with `WhatsApp.Header`,
196
196
  `.Body`, `.Footer`, `.Buttons`, `.UrlButton`, `.QuickReplyButton`, `.PhoneButton` and
197
- `.CopyCodeButton`). Their names are distinct per channel, so a wrong prop fails in the type checker.
197
+ `.CopyCodeButton` and `.FlowButton`). Their names are distinct per channel, so a wrong prop fails in the type checker.
198
198
  WhatsApp bindings compile to Meta's positional `{{1}}` placeholders (`whatsappTemplate`), and a
199
199
  conditional in a WhatsApp text part is refused because Meta templates cannot branch. The platform
200
200
  gates these channels.
@@ -33,7 +33,7 @@ interface EmailBrand {
33
33
  readonly footer?: EmailBrandFooter | undefined;
34
34
  }
35
35
  /** Every class the brand components use, compiled for any project that imports them. */
36
- declare const BRAND_COMPONENT_CLASSES: readonly ["dark:hidden!", "hidden", "dark:block!", "text-xs", "text-gray-500", "text-center", "font-semibold", "underline"];
36
+ declare const BRAND_COMPONENT_CLASSES: readonly ["dark:hidden!", "hidden", "dark:block!", "text-xs", "text-muted-foreground", "text-center", "font-semibold", "underline"];
37
37
  interface BrandLogoProps {
38
38
  /** Rendered width in pixels; the height follows the image. */
39
39
  readonly width?: number | undefined;
@@ -1,4 +1,4 @@
1
- import { D as samvaBrandPlugin, O as BRAND_FOOTER_MARKER, k as UNSUBSCRIBE_LINK, r as escapeAttribute } from "./html-eOZfDwVf.js";
1
+ import { D as samvaBrandPlugin, O as BRAND_FOOTER_MARKER, k as UNSUBSCRIBE_LINK, r as escapeAttribute } from "./html-BvRFepeO.js";
2
2
  import { a as jsx, n as Fragment } from "./jsx-runtime-CBlexoeR.js";
3
3
  //#region src/email/assets.ts
4
4
  /** Files a template may import, and what they are served as. */
@@ -67,7 +67,7 @@ const BRAND_COMPONENT_CLASSES = [
67
67
  "hidden",
68
68
  "dark:block!",
69
69
  "text-xs",
70
- "text-gray-500",
70
+ "text-muted-foreground",
71
71
  "text-center",
72
72
  "font-semibold",
73
73
  "underline"
@@ -138,7 +138,7 @@ const brandComponents = (brand, plugin = samvaBrandPlugin) => {
138
138
  const links = footer.socialLinks ?? [];
139
139
  if (links.length > 0) rows.push(row(links.flatMap((link, index) => [...index === 0 ? [] : [" · "], jsx("a", {
140
140
  href: link.url,
141
- className: "text-gray-500 underline",
141
+ className: "text-muted-foreground underline",
142
142
  children: link.label
143
143
  })])));
144
144
  rows.push(row(unsubscribeLink(unsubscribeLabel, plugin), {
@@ -155,7 +155,7 @@ const brandComponents = (brand, plugin = samvaBrandPlugin) => {
155
155
  [BRAND_FOOTER_MARKER]: true,
156
156
  children: jsx("tbody", { children: jsx("tr", { children: jsx("td", {
157
157
  align: "center",
158
- className: classes("text-xs text-gray-500 text-center", className),
158
+ className: classes("text-xs text-muted-foreground text-center", className),
159
159
  style: { padding: "32px 16px" },
160
160
  children: jsx("table", {
161
161
  role: "presentation",
@@ -179,7 +179,7 @@ const brandComponents = (brand, plugin = samvaBrandPlugin) => {
179
179
  */
180
180
  const unsubscribeLink = (label, plugin) => jsx("a", {
181
181
  href: plugin.unsubscribeUrlPlaceholder,
182
- className: "text-gray-500 underline",
182
+ className: "text-muted-foreground underline",
183
183
  [UNSUBSCRIBE_LINK]: true,
184
184
  children: label
185
185
  });
package/dist/brand.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as BrandPlugin } from "./brand-plugin-CHhcei-j.js";
2
- import { _ as EmailBrandSocialLink, a as assetUrl, b as renderedFooterMarkers, c as isExternalAsset, d as BrandFooterProps, f as BrandLogoProps, g as EmailBrandFooter, h as EmailBrandAssets, i as assetEntry, l as BRAND_COMPONENT_CLASSES, m as EmailBrandAddress, n as AssetEntry, o as checkAssetBase, p as EmailBrand, r as AssetManifest, s as isAssetPath, t as ASSET_CONTENT_TYPES, u as BrandComponents, v as RenderedFooterMarkers, x as withoutUnsubscribeRow, y as brandComponents } from "./assets-DCULM6QD.js";
2
+ import { _ as EmailBrandSocialLink, a as assetUrl, b as renderedFooterMarkers, c as isExternalAsset, d as BrandFooterProps, f as BrandLogoProps, g as EmailBrandFooter, h as EmailBrandAssets, i as assetEntry, l as BRAND_COMPONENT_CLASSES, m as EmailBrandAddress, n as AssetEntry, o as checkAssetBase, p as EmailBrand, r as AssetManifest, s as isAssetPath, t as ASSET_CONTENT_TYPES, u as BrandComponents, v as RenderedFooterMarkers, x as withoutUnsubscribeRow, y as brandComponents } from "./assets-tISHaQNF.js";
3
3
  import { t as Diagnostic } from "./diagnostic-model-DxthUZBT.js";
4
4
  import { Result } from "better-result";
5
5
  //#region src/email/samva-brand-plugin.d.ts
@@ -29,8 +29,6 @@ interface BrandCss {
29
29
  readonly dark: Readonly<Record<string, string>>;
30
30
  readonly fontFaces: readonly BrandFontFace[];
31
31
  }
32
- /** The theme variable a dark-mode color lowers to: `--color-brand` → `--color-brand-dark`. */
33
- declare const darkVariableName: (property: string) => string;
34
32
  /** Parse brand theme CSS against the closed grammar above. */
35
33
  declare const parseBrandCss: (css: string) => Result<BrandCss, ReadonlyArray<Diagnostic>>;
36
34
  /**
@@ -39,9 +37,43 @@ declare const parseBrandCss: (css: string) => Result<BrandCss, ReadonlyArray<Dia
39
37
  * output always parses back to the same value.
40
38
  */
41
39
  declare const printBrandCss: (brand: BrandCss) => string;
42
- /** The brand color, and the text color that sits on it. */
43
- declare const BRAND_COLOR_VARIABLE = "--color-brand";
44
- declare const BRAND_FOREGROUND_VARIABLE = "--color-brand-foreground";
40
+ /**
41
+ * The semantic color tokens, as Tailwind color names (`bg-card`,
42
+ * `text-primary-foreground`, `border-border`), each with the role a block uses
43
+ * it for. A block reads only these, so swapping the theme reskins it.
44
+ */
45
+ declare const THEME_COLOR_ROLES: {
46
+ readonly background: "The page, behind everything.";
47
+ readonly foreground: "Text on the page.";
48
+ readonly card: "A surface that groups the message's content.";
49
+ readonly "card-foreground": "Text on a card.";
50
+ readonly primary: "The main action's fill (a button), and links.";
51
+ readonly "primary-foreground": "Text on a primary fill.";
52
+ readonly secondary: "A quieter action's fill.";
53
+ readonly "secondary-foreground": "Text on a secondary fill.";
54
+ readonly muted: "A subdued surface: a code block, a table header.";
55
+ readonly "muted-foreground": "Secondary text on the page, a card or a muted surface: labels, captions, the footer.";
56
+ readonly accent: "A highlighted surface: a callout, a selected row.";
57
+ readonly "accent-foreground": "Text on an accent surface.";
58
+ readonly border: "Rules, dividers and outlines.";
59
+ };
60
+ declare const THEME_COLORS: readonly (keyof typeof THEME_COLOR_ROLES)[];
61
+ type ThemeColor = (typeof THEME_COLORS)[number];
62
+ /**
63
+ * The non-color tokens: the corner radius (`rounded`; `rounded-sm` to `rounded-xl` follow it) and
64
+ * the body and heading families (`font-sans`, `font-heading`).
65
+ */
66
+ declare const THEME_TOKENS: readonly ["--radius", "--font-sans", "--font-heading"];
67
+ /**
68
+ * The theme every project compiles with, beneath its own: a neutral light and
69
+ * dark palette, an 8px radius and the system sans-serif stack. A project's
70
+ * `theme.css`, its imported stylesheets and its brand layer over it.
71
+ */
72
+ declare const DEFAULT_THEME: BrandCss;
73
+ /** `DEFAULT_THEME` as theme CSS. */
74
+ declare const DEFAULT_THEME_CSS: string;
75
+ /** The fills whose text color a brand may leave out: `--color-primary` and `--color-primary-foreground`, and so on. */
76
+ declare const FILL_COLORS: readonly ["primary", "secondary", "accent"];
45
77
  /**
46
78
  * Black or white, whichever has the higher WCAG contrast ratio against
47
79
  * `color`. A translucent color is judged over white, the page an email sits on.
@@ -51,25 +83,16 @@ declare const BRAND_FOREGROUND_VARIABLE = "--color-brand-foreground";
51
83
  */
52
84
  declare const contrastForeground: (color: string) => "#000000" | "#ffffff" | undefined;
53
85
  /**
54
- * The text-on-brand colors a brand gets when it leaves them out: for the light
55
- * theme and, when the brand sets a dark-mode brand color, for dark mode. Each
56
- * is the `contrastForeground` of the brand color it sits on. A brand that sets
57
- * the foreground, or whose brand color does not evaluate, gets no default.
86
+ * The text colors a theme layer gets for the fills it sets without one: for
87
+ * each of `FILL_COLORS`, the `contrastForeground` of the fill, in light mode
88
+ * and, where the layer's dark-mode block sets the fill, in dark mode. A layer
89
+ * that sets the foreground, or whose fill does not evaluate, gets no default.
90
+ * Every build lowers each theme layer with these, so stored theme CSS never
91
+ * carries them.
58
92
  */
59
- declare const brandForegroundDefaults: (brand: BrandCss) => {
60
- readonly light?: string | undefined;
61
- readonly dark?: string | undefined;
93
+ declare const foregroundDefaults: (light: Readonly<Record<string, string>>, dark: Readonly<Record<string, string>>) => {
94
+ readonly light: Readonly<Record<string, string>>;
95
+ readonly dark: Readonly<Record<string, string>>;
62
96
  };
63
- /**
64
- * A brand theme as flat theme variables, the way a build compiles it. Email
65
- * inlining resolves `var()` once, so a dark-mode `@theme` cannot switch a
66
- * value at run time; each dark color lowers to its `-dark` theme variable,
67
- * which `dark:` utilities read (`dark:bg-brand-dark`). A text-on-brand color
68
- * the brand leaves out is filled in by `brandForegroundDefaults`, so every
69
- * build compiles the same default and the stored CSS never carries it.
70
- */
71
- declare const brandThemeVariables: (brand: BrandCss) => Readonly<Record<string, string>>;
72
- /** The stylesheet Tailwind compiles for the theme's brand import: `brandThemeVariables` and the font faces. */
73
- declare const brandTailwindCss: (brand: BrandCss) => string;
74
97
  //#endregion
75
- export { ASSET_CONTENT_TYPES, AssetEntry, AssetManifest, BRAND_COLOR_VARIABLE, BRAND_COMPONENT_CLASSES, BRAND_FOREGROUND_VARIABLE, BrandComponents, BrandCss, BrandFontFace, BrandFooterProps, BrandLogoProps, type BrandPlugin, EmailBrand, EmailBrandAddress, EmailBrandAssets, EmailBrandFooter, EmailBrandSocialLink, RenderedFooterMarkers, assetEntry, assetUrl, brandComponents, brandForegroundDefaults, brandTailwindCss, brandThemeVariables, checkAssetBase, contrastForeground, darkVariableName, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, samvaBrandPlugin, withoutUnsubscribeRow };
98
+ export { ASSET_CONTENT_TYPES, AssetEntry, AssetManifest, BRAND_COMPONENT_CLASSES, BrandComponents, BrandCss, BrandFontFace, BrandFooterProps, BrandLogoProps, type BrandPlugin, DEFAULT_THEME, DEFAULT_THEME_CSS, EmailBrand, EmailBrandAddress, EmailBrandAssets, EmailBrandFooter, EmailBrandSocialLink, FILL_COLORS, RenderedFooterMarkers, THEME_COLORS, THEME_COLOR_ROLES, THEME_TOKENS, ThemeColor, assetEntry, assetUrl, brandComponents, checkAssetBase, contrastForeground, foregroundDefaults, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, samvaBrandPlugin, withoutUnsubscribeRow };
package/dist/brand.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D as samvaBrandPlugin } from "./html-eOZfDwVf.js";
2
- import { a as brandThemeVariables, c as parseBrandCss, i as brandTailwindCss, l as printBrandCss, n as BRAND_FOREGROUND_VARIABLE, o as contrastForeground, r as brandForegroundDefaults, s as darkVariableName, t as BRAND_COLOR_VARIABLE } from "./theme-css-CEUrDpqq.js";
3
- import { a as ASSET_CONTENT_TYPES, c as checkAssetBase, i as withoutUnsubscribeRow, l as isAssetPath, n as brandComponents, o as assetEntry, r as renderedFooterMarkers, s as assetUrl, t as BRAND_COMPONENT_CLASSES, u as isExternalAsset } from "./brand-C45QBKX9.js";
4
- export { ASSET_CONTENT_TYPES, BRAND_COLOR_VARIABLE, BRAND_COMPONENT_CLASSES, BRAND_FOREGROUND_VARIABLE, assetEntry, assetUrl, brandComponents, brandForegroundDefaults, brandTailwindCss, brandThemeVariables, checkAssetBase, contrastForeground, darkVariableName, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, samvaBrandPlugin, withoutUnsubscribeRow };
1
+ import { D as samvaBrandPlugin } from "./html-BvRFepeO.js";
2
+ import { a as THEME_COLOR_ROLES, c as foregroundDefaults, i as THEME_COLORS, l as parseBrandCss, n as DEFAULT_THEME_CSS, o as THEME_TOKENS, r as FILL_COLORS, s as contrastForeground, t as DEFAULT_THEME, u as printBrandCss } from "./theme-css-s9QwD5Ko.js";
3
+ import { a as ASSET_CONTENT_TYPES, c as checkAssetBase, i as withoutUnsubscribeRow, l as isAssetPath, n as brandComponents, o as assetEntry, r as renderedFooterMarkers, s as assetUrl, t as BRAND_COMPONENT_CLASSES, u as isExternalAsset } from "./brand-nreeKWxF.js";
4
+ export { ASSET_CONTENT_TYPES, BRAND_COMPONENT_CLASSES, DEFAULT_THEME, DEFAULT_THEME_CSS, FILL_COLORS, THEME_COLORS, THEME_COLOR_ROLES, THEME_TOKENS, assetEntry, assetUrl, brandComponents, checkAssetBase, contrastForeground, foregroundDefaults, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, samvaBrandPlugin, withoutUnsubscribeRow };
@@ -1,8 +1,8 @@
1
1
  import { n as parseBrandSpecifier, t as BrandPlugin } from "./brand-plugin-CHhcei-j.js";
2
2
  import { a as TemplateDiagnostic, r as EmailDiagnostic } from "./diagnostics-CUYlQ_6x.js";
3
3
  import { r as EmailNode } from "./jsx-runtime-DC7xG0aA.js";
4
- import { n as AssetEntry, p as EmailBrand, u as BrandComponents } from "./assets-DCULM6QD.js";
5
- import { C as Compound, D as EmittedPosition, S as CompiledSelector, T as matchesSelector, _ as Specificity, b as parseStylesheet, ct as TemplateIr, g as CssRule, h as CssDeclaration, r as RenderedEmail, v as Stylesheet, w as classListOf, x as Ancestry, y as compileSelector } from "./render-C6OTH8BQ.js";
4
+ import { n as AssetEntry, p as EmailBrand, u as BrandComponents } from "./assets-tISHaQNF.js";
5
+ import { C as Compound, D as EmittedPosition, S as CompiledSelector, T as matchesSelector, _ as Specificity, b as parseStylesheet, ct as TemplateIr, g as CssRule, h as CssDeclaration, r as RenderedEmail, v as Stylesheet, w as classListOf, x as Ancestry, y as compileSelector } from "./render-BdvewoQt.js";
6
6
  import { a as TEMPLATE_NAME_PATTERN, n as EmailContent } from "./template-B-UDXK8a.js";
7
7
  import { caniemail } from "caniemail";
8
8
  //#region src/email/compatibility.d.ts
@@ -81,7 +81,7 @@ declare const finalizeValue: (value: string, variables: Readonly<Record<string,
81
81
  /** Media Queries Level 4 range syntax down to the `min-`/`max-` prefixes clients support. */
82
82
  declare const downlevelMediaQuery: (condition: string) => string;
83
83
  /** Render the rules that could not be inlined as one `<style>` body. */
84
- declare const renderHeadCss: (rules: readonly CssRule[], headAtRules: readonly string[], variables: Readonly<Record<string, string>>) => string;
84
+ declare const renderHeadCss: (rules: readonly CssRule[], headAtRules: readonly string[], variables: Readonly<Record<string, string>>, darkVariables?: Readonly<Record<string, string>>) => string;
85
85
  //#endregion
86
86
  //#region src/email/css-color.d.ts
87
87
  /** A color in extended sRGB, alpha included, before clamping and gamma. */
@@ -101,15 +101,24 @@ declare const downlevelColors: (value: string) => string;
101
101
  //#region src/email/cascade.d.ts
102
102
  interface AppliedStyles {
103
103
  readonly tree: EmailNode;
104
- /** Rules that could not be inlined, ready for a `<style>` element in `<head>`. */
104
+ /** Rules that could not be inlined, ready for a `<style>` element in `<head>` after `darkRules`. */
105
105
  readonly headCss: string;
106
+ /**
107
+ * A generated class for each distinct set of dark-mode declarations, mapped
108
+ * to those declarations. An element whose inlined styles read a color the
109
+ * theme's dark-mode block redefines carries the class.
110
+ */
111
+ readonly darkRules: ReadonlyMap<string, string>;
106
112
  /** Project `@font-face` rules, for a `<head>` style classic Outlook cannot see. */
107
113
  readonly fontFaceCss: string;
108
114
  readonly diagnostics: readonly EmailDiagnostic[];
109
115
  }
110
116
  /**
111
117
  * Resolve every stylesheet against the tree: inline what a client will honor on
112
- * the element, and keep the rest for `<head>`.
118
+ * the element, and keep the rest for `<head>`. Where the sheet has a dark-mode
119
+ * theme, an element whose inlined styles read a color it redefines also gets a
120
+ * generated class whose dark-mode rule restates those styles at their dark
121
+ * values.
113
122
  */
114
123
  declare const applyStylesheet: (root: EmailNode, sheet: Stylesheet, options?: {
115
124
  readonly knownClasses?: ReadonlySet<string>;
@@ -194,7 +203,10 @@ interface TransformOptions {
194
203
  }
195
204
  interface ProjectTransform {
196
205
  readonly assets: readonly AssetEntry[];
197
- /** The compiled Tailwind sheet, when the project used any utility class. */
206
+ /**
207
+ * The compiled Tailwind sheet: the default theme, the primitives' rules, the project theme and
208
+ * the utilities the project uses. Without Tailwind, the default theme and the primitives' rules.
209
+ */
198
210
  readonly tailwindSheet: Stylesheet | undefined;
199
211
  /** Every project stylesheet, parsed, by path. */
200
212
  readonly stylesheets: ReadonlyMap<string, Stylesheet>;
@@ -215,6 +227,11 @@ declare const transformProject: (files: Readonly<Record<string, string | Uint8Ar
215
227
  interface RenderedBrandFooter {
216
228
  readonly html: string;
217
229
  readonly text: string;
230
+ /**
231
+ * The rules the footer needs in the message's `<head>`: its dark-mode colors and the
232
+ * `dark:` swap of the brand logo. Empty when it needs none.
233
+ */
234
+ readonly headCss: string;
218
235
  }
219
236
  /**
220
237
  * Render `BrandFooter` against a brand on its own, for the send path to