@theme-registry/refract 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_createTheme-255ff48e.esm.js +2 -0
  3. package/dist/_createTheme-255ff48e.esm.js.map +1 -0
  4. package/dist/_createTheme-41c01033.cjs.js +2 -0
  5. package/dist/_createTheme-41c01033.cjs.js.map +1 -0
  6. package/dist/_errors-be768b25.cjs.js +2 -0
  7. package/dist/_errors-be768b25.cjs.js.map +1 -0
  8. package/dist/_errors-d75a5c74.esm.js +2 -0
  9. package/dist/_errors-d75a5c74.esm.js.map +1 -0
  10. package/dist/adapter-kit.cjs.js +2 -0
  11. package/dist/adapter-kit.cjs.js.map +1 -0
  12. package/dist/adapter-kit.d.ts +88 -0
  13. package/dist/adapter-kit.esm.js +2 -0
  14. package/dist/adapter-kit.esm.js.map +1 -0
  15. package/dist/build/auditCommand.d.ts +19 -0
  16. package/dist/build/buildCommand.d.ts +23 -0
  17. package/dist/build/cli.d.ts +2 -0
  18. package/dist/build/config.d.ts +58 -0
  19. package/dist/build/diff.d.ts +56 -0
  20. package/dist/build/diffCommand.d.ts +29 -0
  21. package/dist/build/emit.d.ts +20 -0
  22. package/dist/build/emitTheme.d.ts +39 -0
  23. package/dist/build/guide.d.ts +34 -0
  24. package/dist/build/importCommand.d.ts +42 -0
  25. package/dist/build/index.d.ts +33 -0
  26. package/dist/build/init.d.ts +32 -0
  27. package/dist/build/paths.d.ts +42 -0
  28. package/dist/build/skillsCommand.d.ts +53 -0
  29. package/dist/build/tokensCommand.d.ts +17 -0
  30. package/dist/build/vendor.d.ts +39 -0
  31. package/dist/build.cjs.js +2 -0
  32. package/dist/build.cjs.js.map +1 -0
  33. package/dist/build.esm.js +2 -0
  34. package/dist/build.esm.js.map +1 -0
  35. package/dist/cli.js +8250 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/color-math.cjs.js +2 -0
  38. package/dist/color-math.cjs.js.map +1 -0
  39. package/dist/color-math.esm.js +2 -0
  40. package/dist/color-math.esm.js.map +1 -0
  41. package/dist/core/ThemeAdapter.d.ts +234 -0
  42. package/dist/core/createTheme.d.ts +96 -0
  43. package/dist/core/cssProperties.d.ts +8 -0
  44. package/dist/core/defineAdapter.d.ts +25 -0
  45. package/dist/core/derive/crossProperty.d.ts +31 -0
  46. package/dist/core/derive/index.d.ts +11 -0
  47. package/dist/core/derive/resolveTokens.d.ts +27 -0
  48. package/dist/core/errors.d.ts +24 -0
  49. package/dist/core/index.d.ts +16 -0
  50. package/dist/core/media/containers.d.ts +20 -0
  51. package/dist/core/media/defaults.d.ts +3 -0
  52. package/dist/core/media/descriptors.d.ts +12 -0
  53. package/dist/core/media/index.d.ts +11 -0
  54. package/dist/core/media/queries.d.ts +20 -0
  55. package/dist/core/model/buildModel.d.ts +116 -0
  56. package/dist/core/model/index.d.ts +14 -0
  57. package/dist/core/model/interpreted.d.ts +37 -0
  58. package/dist/core/model/mergeRuleSet.d.ts +68 -0
  59. package/dist/core/model/model.d.ts +260 -0
  60. package/dist/core/noopAdapter.d.ts +26 -0
  61. package/dist/core/normalize/index.d.ts +15 -0
  62. package/dist/core/normalize/properties.d.ts +20 -0
  63. package/dist/core/normalize/recipeResolver.d.ts +19 -0
  64. package/dist/core/normalize/recipes.d.ts +38 -0
  65. package/dist/core/normalize/responsive.d.ts +40 -0
  66. package/dist/core/normalize/types.d.ts +261 -0
  67. package/dist/core/rawTheme.d.ts +53 -0
  68. package/dist/core/ref.d.ts +13 -0
  69. package/dist/core/subsystem.d.ts +115 -0
  70. package/dist/core/types.d.ts +31 -0
  71. package/dist/core/units.d.ts +93 -0
  72. package/dist/dtcg/export.d.ts +48 -0
  73. package/dist/dtcg/import.d.ts +43 -0
  74. package/dist/dtcg/index.d.ts +21 -0
  75. package/dist/dtcg/parse.d.ts +6 -0
  76. package/dist/dtcg/types.d.ts +69 -0
  77. package/dist/dtcg.cjs.js +2 -0
  78. package/dist/dtcg.cjs.js.map +1 -0
  79. package/dist/dtcg.esm.js +2 -0
  80. package/dist/dtcg.esm.js.map +1 -0
  81. package/dist/index.cjs.js +2 -0
  82. package/dist/index.cjs.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.esm.js +2 -0
  85. package/dist/index.esm.js.map +1 -0
  86. package/dist/subsystems/animation/index.d.ts +13 -0
  87. package/dist/subsystems/animation/recipes.d.ts +20 -0
  88. package/dist/subsystems/animation/types.d.ts +52 -0
  89. package/dist/subsystems/borders/index.d.ts +10 -0
  90. package/dist/subsystems/borders/recipes.d.ts +29 -0
  91. package/dist/subsystems/borders/types.d.ts +50 -0
  92. package/dist/subsystems/colors/audit.d.ts +67 -0
  93. package/dist/subsystems/colors/colorInput.d.ts +17 -0
  94. package/dist/subsystems/colors/index.d.ts +12 -0
  95. package/dist/subsystems/colors/keywords.d.ts +9 -0
  96. package/dist/subsystems/colors/normalize.d.ts +26 -0
  97. package/dist/subsystems/colors/recipes.d.ts +21 -0
  98. package/dist/subsystems/colors/types.d.ts +98 -0
  99. package/dist/subsystems/colors/utils.d.ts +104 -0
  100. package/dist/subsystems/components/index.d.ts +13 -0
  101. package/dist/subsystems/components/recipes.d.ts +22 -0
  102. package/dist/subsystems/components/types.d.ts +46 -0
  103. package/dist/subsystems/effects/index.d.ts +9 -0
  104. package/dist/subsystems/effects/recipes.d.ts +24 -0
  105. package/dist/subsystems/effects/types.d.ts +63 -0
  106. package/dist/subsystems/effects/utils.d.ts +57 -0
  107. package/dist/subsystems/globals/index.d.ts +21 -0
  108. package/dist/subsystems/globals/presets.d.ts +33 -0
  109. package/dist/subsystems/globals/types.d.ts +48 -0
  110. package/dist/subsystems/index.d.ts +21 -0
  111. package/dist/subsystems/layout/index.d.ts +14 -0
  112. package/dist/subsystems/layout/normalize.d.ts +35 -0
  113. package/dist/subsystems/layout/recipes.d.ts +16 -0
  114. package/dist/subsystems/layout/structural.d.ts +37 -0
  115. package/dist/subsystems/layout/types.d.ts +154 -0
  116. package/dist/subsystems/typography/index.d.ts +9 -0
  117. package/dist/subsystems/typography/normalize.d.ts +17 -0
  118. package/dist/subsystems/typography/recipes.d.ts +21 -0
  119. package/dist/subsystems/typography/types.d.ts +49 -0
  120. package/package.json +83 -0
  121. package/skills/adapter-scaffold/SKILL.md +85 -0
  122. package/skills/adapter-usage/SKILL.md +75 -0
  123. package/skills/build-config/SKILL.md +77 -0
  124. package/skills/colors/SKILL.md +134 -0
  125. package/skills/consuming-the-output/SKILL.md +62 -0
  126. package/skills/dtcg-import/SKILL.md +76 -0
  127. package/skills/layout/SKILL.md +65 -0
  128. package/skills/overrides-and-child-themes/SKILL.md +70 -0
  129. package/skills/recipes-and-composition/SKILL.md +110 -0
  130. package/skills/theme-authoring/SKILL.md +107 -0
  131. package/skills/theme-foundations/SKILL.md +127 -0
  132. package/skills/troubleshooting/SKILL.md +97 -0
  133. package/skills/typography/SKILL.md +66 -0
  134. package/skills/visual-effects/SKILL.md +96 -0
  135. package/src/subsystems/colors/utils.ts +299 -0
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: theme-foundations
3
+ description: The cross-cutting vocabulary shared by every refract subsystem — breakpoints, containers, the responsive/variant/target/modes/states override axes, and bare-string references. Read once; the domain guides (colors, typography, layout, …) build on it instead of re-explaining. Triggers: "breakpoints", "container queries / container sizes", "responsive override", "variant vs target", "dark mode / modes", "states (hover/disabled)", "how do refs work".
4
+ tier: core
5
+ ---
6
+
7
+ # Theme foundations
8
+
9
+ Vocabulary reused across every subsystem. Learn it here so the domain guides can stay focused
10
+ on their own tokens. All of this is defined once in the core and validated on every reference.
11
+
12
+ ## Literals vs references — the one rule
13
+
14
+ Whether a bare string is a token reference or a raw literal depends on the context:
15
+
16
+ - **Composition fields** — a recipe's cross-subsystem fields (`colors: "solid.brand"`) and a
17
+ colour recipe's declaration fields (`background: "brand"`, `color: "brand.text"`) — **compose
18
+ tokens**, so a bare string there is always a **reference** (lowers to `var(--…)`). Paths are
19
+ dotted and subsystem-scoped (`"colors.link"`); within a subsystem's own recipe the leading
20
+ segment is implied (`"brand"` inside a colors recipe).
21
+ - **The `css` block** and **`globals` element declarations** are **raw CSS**, so a bare string
22
+ there is a **literal** (`display: "flex"` just works). Mark the occasional token with the
23
+ **`ref()`** helper — `color: ref("colors.brand.text")` — or the JSON-safe **`{ ref: "…" }`**
24
+ object (for a `theme.raw.json`, which can't call a function).
25
+ - A plain **number** is always a literal.
26
+ - **Prefer `ref()` explicitly** when authoring — it makes the intent unmistakable. A `ref()` at a
27
+ token that doesn't exist fails loud at build; a bare literal is never validated.
28
+ - **Trust boundary:** a literal `css` value passes through to the output **verbatim** (refract
29
+ validates structure, not the content of raw declarations). Token values are re-serialized to a
30
+ canonical form, but the `css` block is passthrough — so if you compile an **untrusted** (user- or
31
+ agent-authored) theme and serve the result, sanitize its literal `css` values first, as you would
32
+ any untrusted CSS.
33
+
34
+ ## Breakpoints — set them first
35
+
36
+ Top-level, min-width pixels:
37
+
38
+ ```ts
39
+ breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1280 } // the default set
40
+ ```
41
+
42
+ Every `responsive` entry references these **names**, so settle them before authoring anything
43
+ responsive. Threshold **units** (how `@media` widths render — `px | em | rem`) are a build-time
44
+ concern set on `createTheme({ media })` / the config, not in the raw theme.
45
+
46
+ ## Containers — container queries
47
+
48
+ Top-level, opt-in. A container defines named sizes that recipe overrides target with
49
+ `{ container, size }` instead of `{ breakpoint }`:
50
+
51
+ ```ts
52
+ containers: {
53
+ card: { type: "inline-size", sizes: { sm: 320, md: 480 } },
54
+ }
55
+ ```
56
+
57
+ Use breakpoints for viewport-relative variation and containers for component-relative variation.
58
+
59
+ ## The override axes
60
+
61
+ Both **properties** and **recipes** carry a `responsive: [...]` list. Each entry combines a
62
+ condition with a change:
63
+
64
+ - **Condition:** `breakpoint` (+ `query: "min" | "max" | "exact"`, default `min`),
65
+ `orientation`, or `container` + `size`.
66
+ - **Change (pick one):**
67
+ - a plain new value for the property/field;
68
+ - `variant: X` — reassign the base to reference sibling variant `X`;
69
+ - `target: X, …` — override variant `X`'s value specifically (not the base).
70
+
71
+ ```ts
72
+ responsive: [
73
+ { breakpoint: "md", query: "min", fontSize: "typography.fontSize.lg" }, // plain
74
+ { breakpoint: "sm", query: "max", variant: "compact" }, // swap base → compact
75
+ { breakpoint: "lg", target: "wide", padding: "layout.spacing.xl" }, // scope to variant "wide"
76
+ ]
77
+ ```
78
+
79
+ **variant vs target:** `variant` *swaps which variant the base points at*; `target` *edits one
80
+ named variant*. A recipe's `variants` map (see **recipes-and-composition**) desugars each variant
81
+ into a flat sibling, so `variant:` / `target:` references resolve against those siblings.
82
+
83
+ ## modes — light/dark appearance
84
+
85
+ Two things share the name. A top-level **`modes` registry** declares which appearance modes exist
86
+ (`modes: ["dark", "light"]` — the default; declare more to use a custom mode like `hc`). And a
87
+ per-property **`modes` axis** (chiefly on colors) is a **LIST of override entries**, each
88
+ `{ mode, target?, …value }`: `mode` is the WHEN (validated against the registry), the optional
89
+ `target` scopes into a variant's var, and the value is a literal or a `{ ref?, modifiers }`
90
+ derivation. Adapters realize modes as conditional blocks (`prefers-color-scheme` media,
91
+ `[data-theme]` attribute, or both).
92
+
93
+ ```ts
94
+ modes: ["dark", "light"], // top-level registry (optional; this is the default)
95
+ colors: {
96
+ surface: {
97
+ base: "#fff",
98
+ modes: [
99
+ { mode: "dark", base: "#111" }, // a literal mode override
100
+ { mode: "dark", modifiers: [{ darken: 10 }] }, // …or a derivation off the own base
101
+ ],
102
+ },
103
+ }
104
+ ```
105
+
106
+ Because dark rides the **referenced token**, anything that refs `colors.surface` flips for free —
107
+ so `globals` element rules and recipes never need their own `modes` axis.
108
+
109
+ ## states — recipe conditions
110
+
111
+ `states` is a **recipe-only** axis (hover, focus, disabled, …), a **LIST of override entries**,
112
+ each `{ state, target?, …deltas }`. The **allowed set is owned by the adapter** — an unknown state
113
+ throws at build. `state` is the WHEN; the optional `target` scopes the override onto a recipe
114
+ variant's sibling (`<item>-<variant>`), so two same-state entries can behave differently per
115
+ variant.
116
+
117
+ ```ts
118
+ states: [
119
+ { state: "hover", background: "brand.dark" },
120
+ { state: "disabled", opacity: 0.5 },
121
+ { state: "hover", target: "lg", background: "brand" }, // a hover just for the `lg` variant sibling
122
+ ]
123
+ ```
124
+
125
+ modes / states / responsive are all **arrays of overrides over one spine** — WHEN
126
+ (`mode`/`state`/`breakpoint`) · WHERE (`target`) · WHAT (the value/deltas). See
127
+ **recipes-and-composition** for how states compose with variants and responsive entries.
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: troubleshooting
3
+ description: Diagnose refract build errors — map createTheme's validation messages (unknown token/breakpoint/variant/state, cyclic or duplicate recipes, mode overrides nothing) to their cause and fix. Use when createTheme or refract build throws. Triggers: "createTheme throws", "references unknown token/breakpoint/variant", "state not allowed", "cyclic recipe", "duplicate recipe name", "overrides nothing".
4
+ tier: optional
5
+ ---
6
+
7
+ # Troubleshooting
8
+
9
+ refract validates the theme up front and throws a message that **names the offending reference**.
10
+ Read the quoted name first — it points straight at the authoring mistake. Common errors and fixes:
11
+
12
+ Every authoring/build error is a **`RefractError`** with a stable machine `code` (`err.code`, e.g.
13
+ `REFRACT_E_COLOR_INPUT`, `REFRACT_E_STEPS`, `REFRACT_E_NAMING`) you can branch on without matching the
14
+ message text. Post-build reference validation **collects all failures** and throws them at once as
15
+ `REFRACT_E_VALIDATION` — `err.failures` is the array of every bad reference, so fix them in one pass:
16
+
17
+ ```ts
18
+ import { RefractError } from "@theme-registry/refract";
19
+ try { createTheme(raw, { adapter }); }
20
+ catch (e) {
21
+ if (e instanceof RefractError && e.code === "REFRACT_E_VALIDATION") console.error(e.failures);
22
+ }
23
+ ```
24
+
25
+ ## `references unknown token '<ref>'`
26
+
27
+ > `components.<group>.<variant>: css '<prop>' references unknown token '<ref>' — check the token path, or use a bare string / number for a raw CSS value`
28
+ > `globals element '<sel>': '<prop>' references unknown token '<ref>' — …`
29
+
30
+ A `ref("…")` (or `{ ref: "…" }`) pointed at a token that doesn't exist. **Fix the path** — e.g.
31
+ `ref("colors.link")`, `ref("typography.fontSize.lg")`. (A raw CSS value doesn't need a ref at all
32
+ — inside a `css` block a bare string is already a literal: `cursor: "pointer"`.) See the
33
+ literals-vs-references rule in **theme-foundations**.
34
+
35
+ ## `references unknown breakpoint "<name>"`
36
+
37
+ > `Responsive entry … references unknown breakpoint "<name>".`
38
+ > `Responsive entry … is missing a "breakpoint" value.`
39
+
40
+ A `responsive` entry names a breakpoint not in the top-level `breakpoints` map (or omits one).
41
+ Add the breakpoint, or fix the name. Set `breakpoints` first — see **theme-foundations**.
42
+
43
+ ## `references unknown variant "<name>"` / `unknown target "<name>"`
44
+
45
+ A `variant:` / `target:` in a responsive entry points at a variant that doesn't exist on that
46
+ property/recipe. Define the variant, or fix the reference. Remember a recipe's `variants` map
47
+ desugars to siblings that `variant:`/`target:` can then reach — see **recipes-and-composition**.
48
+
49
+ ## `references unknown state "<name>"`
50
+
51
+ > `Responsive recipe entry … references unknown state "<name>".`
52
+
53
+ The state isn't in the **adapter's** allowed set. Use a state the adapter supports (CSS knows
54
+ `hover`/`focus`/`disabled`/…), or choose an adapter that renders it. States are adapter-owned, not
55
+ authored — see **theme-foundations** → states.
56
+
57
+ ## `Unknown recipe property "<prop>" in <path>`
58
+
59
+ > `Unknown recipe property "<prop>" in <path> — expected a CSS property or a reserved key (variant, target, state, breakpoint, query, orientation, container, size).`
60
+
61
+ A declaration key that's neither a real CSS property nor a reserved recipe key — almost always a
62
+ typo: `ref:` where you meant a `variant:` swap, or `colr:` for `color:`. refract rejects it so it
63
+ can't ship as a stray literal declaration. Use the CSS property, or the reserved key you meant.
64
+
65
+ ## `Recipe variant "<name>" is not defined` / `Cyclic recipe reference`
66
+
67
+ > `Recipe variant "<name>" is not defined in "<group>".`
68
+ > `Cyclic recipe reference in "<group>": <cycle>`
69
+
70
+ A recipe references a sibling that doesn't exist, or two recipes reference each other in a loop.
71
+ Fix the composition graph so references point at real, acyclic recipes.
72
+
73
+ ## `produced a duplicate recipe name "<name>"`
74
+
75
+ > `Recipe variant expansion … produced a duplicate recipe name "<name>" — a desugared "<recipe>-<variant>" collides with an existing sibling recipe.`
76
+
77
+ A `variants` entry desugars to `<recipe>-<variant>`, which collides with a hand-authored sibling.
78
+ Rename the variant or the sibling recipe.
79
+
80
+ ## `Appearance mode "<path>" overrides nothing` / `Unable to resolve base value`
81
+
82
+ > `Appearance mode "<path>" overrides nothing.`
83
+ > `Unable to resolve base value…`
84
+
85
+ A `modes` block redefines a field the property never had, or a property has no resolvable base
86
+ value. Give the property a real base, and make each mode redefine an existing field — see
87
+ **theme-foundations** → modes.
88
+
89
+ ## General
90
+
91
+ - The message's quoted path is the location — start there, fix the *theme*, never the engine.
92
+ - If the shape itself is wrong, the `satisfies RawTheme` typecheck usually catches it before
93
+ runtime — check the red squiggle first.
94
+ - **Validate against the real adapters.** With an MCP server connected, `validateTheme(candidate)`
95
+ builds the edit against every configured target and returns **all** problems at once (per target) —
96
+ it catches adapter-level rules (unknown state, naming collision) a bare core build misses. See
97
+ **theme-authoring → Agent tools**.
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: typography
3
+ description: Author the `typography` slice of a refract theme — the open map of type properties (font families, weights, line-height, letter-spacing, text props) and the `fontSize` modular scale synthesized from a base + named ratio. Use when writing or tuning the type domain. Triggers: "type scale", "modular scale / ratio", "font sizes", "fontFamily / fontWeight / lineHeight", "typography recipe".
4
+ tier: core
5
+ ---
6
+
7
+ # Typography
8
+
9
+ The `typography` key is an **open** map: each key is a type property holding a value, plus the
10
+ reserved `recipes` block. Cross-cutting axes (`responsive`, `variants`, refs) are in
11
+ **theme-foundations**; recipe mechanics are in **recipes-and-composition**. Exhaustive options live
12
+ in `docs/authoring.md`.
13
+
14
+ ## Type properties
15
+
16
+ Properties are heterogeneous — a value is `string | number`, or an extended `{ base, … }`:
17
+
18
+ ```ts
19
+ typography: {
20
+ fontFamily: { base: "Inter, sans-serif", variants: { mono: "ui-monospace, monospace" } },
21
+ fontWeight: { base: 400, variants: { medium: 500, bold: 700 } },
22
+ lineHeight: { base: 1.5, variants: { tight: 1.25 } },
23
+ letterSpacing: { base: "0", variants: { wide: "0.05em" } },
24
+ // text props also live here: fontStyle, textTransform, textDecoration, textAlign
25
+ }
26
+ ```
27
+
28
+ Each `variants` entry is named and referenced elsewhere by bare name (see **recipes-and-composition**).
29
+
30
+ ## fontSize — the modular scale
31
+
32
+ Give `fontSize` a `base` plus a named `ratio` and refract synthesizes the scale steps as variants —
33
+ each `base × ratio^step`, rounded to `precision`:
34
+
35
+ ```ts
36
+ fontSize: {
37
+ base: 16,
38
+ ratio: "minor-third", // one of the named ratios below
39
+ precision: 4, // decimal places (default 4)
40
+ baseFontSize: 16, // scale origin if it differs from `base` (defaults to `base`)
41
+ unit: "rem", // "px" | "rem"
42
+ }
43
+ ```
44
+
45
+ Generated step keys and their exponents: `xs` (−2), `sm` (−1), `md` (0), `lg` (1), `xl` (2), `2xl`
46
+ (3), `3xl` (4), `4xl` (5). Named ratios:
47
+
48
+ | Name | Factor | | Name | Factor |
49
+ |---|---|---|---|---|
50
+ | `minor-second` | 1.067 | | `augmented-fourth` | 1.414 |
51
+ | `major-second` | 1.125 | | `perfect-fifth` | 1.5 |
52
+ | `minor-third` | 1.2 | | `golden` | 1.618 |
53
+ | `major-third` | 1.25 | | | |
54
+ | `perfect-fourth` | 1.333 | | | |
55
+
56
+ Without a (recognized) `ratio`, `fontSize` passes through untouched. Author-declared `variants` win
57
+ over synthesized steps and **seed the chain** — a hand-pinned step becomes the origin the next
58
+ generated step compounds from.
59
+
60
+ ## recipes — typography recipes
61
+
62
+ The reserved `recipes` block declares typographic properties (`fontFamily`, `fontSize`, `fontWeight`,
63
+ `lineHeight`, `letterSpacing`, `fontStyle`, `textTransform`, `textDecoration`, `textAlign`, plus any
64
+ extra passed through). Each value names a **variant** of the matching property (`fontSize: "3xl"`,
65
+ `fontWeight: "bold"`, or `"base"` for the base value). Recipe structure (states, responsive,
66
+ composition) is in **recipes-and-composition**.
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: visual-effects
3
+ description: Author the three visual-effect slices of a refract theme — `effects` (shadows, transitions, blur, opacity, z-index), `borders` (stroke geometry: width/style/offset/radius), and `animation` (motion tokens + keyframes + animation-shorthand recipes). Use when writing shadows, transitions, strokes, or motion. Triggers: "box shadow", "transition tokens", "border / outline / radius", "keyframes", "animation recipe", "blur / opacity / z-index".
4
+ tier: core
5
+ ---
6
+
7
+ # Visual effects — effects · borders · animation
8
+
9
+ Three sibling subsystems, each with its own top-level key. Cross-cutting axes (`responsive`,
10
+ `variants`, `modes`, refs) are in **theme-foundations**; recipe mechanics in
11
+ **recipes-and-composition**. Exhaustive options live in `docs/authoring.md`.
12
+
13
+ ## effects
14
+
15
+ Open map. `shadow` and `transitions` are **structured object-leaf** properties — format-neutral flat
16
+ leaves, never raw CSS. `blur` / `opacity` / `zIndex` are plain scalars.
17
+
18
+ The flat leaf fields sit at the property top level and **are** the (single-layer) base — there is
19
+ **no `base` key**. A `variants` entry — and the value of each `modes` / `responsive` entry (both are
20
+ lists of `{ mode|breakpoint, …leaf }`, per **theme-foundations**) — is a full leaf value, or an
21
+ **array** of leaves for a multi-layer value. The keyword `"none"` means no shadow / no transition.
22
+ **Raw CSS strings are rejected** (only `"none"` is accepted).
23
+
24
+ ```ts
25
+ effects: {
26
+ shadow: {
27
+ offsetY: 2, blur: 8, color: "colors.shadow", // top-level leaf = the base
28
+ variants: {
29
+ lg: [ // array = multi-layer
30
+ { offsetY: 4, blur: 12, color: "colors.shadow" },
31
+ { offsetY: 1, blur: 3, color: "colors.shadow.a20" },
32
+ ],
33
+ none: "none",
34
+ },
35
+ },
36
+ transitions: { property: "opacity", duration: 200, timingFunction: "ease-out", delay: 0 },
37
+ blur: 8, opacity: 1, zIndex: 10,
38
+ }
39
+ ```
40
+
41
+ Shadow leaf fields: `offsetX`, `offsetY`, `blur`, `spread` (a bare number is a **deferred** length
42
+ resolved by the unit pass; a `"1px"` / `"0.5rem"` string is **pinned**), `color` (a `colors.*`
43
+ **ref** — translucency comes from an `alpha` colour variant, e.g. `colors.shadow.a20`, never a shadow
44
+ field), and `inset` (boolean). Transition part fields: `property` (**required**), `duration` (ms),
45
+ `timingFunction` (keyword or `cubic-bezier(...)`), `delay` (ms).
46
+
47
+ Reserved `recipes` (EffectsRecipeProps): `boxShadow` (→ `shadow`), `transition` (→ `transitions`),
48
+ `blur` (lowers to `filter: blur(var(--…))`), `opacity`, `zIndex` — each naming a variant.
49
+
50
+ ## borders
51
+
52
+ Open map of **stroke geometry only** — colour is never a borders token. Properties: `width` (px
53
+ number), `style` (string), `offset` (px number, outline only), `radius` (px number, or a string like
54
+ `"9999px"`).
55
+
56
+ ```ts
57
+ borders: {
58
+ width: { base: 1, variants: { thick: 2 } },
59
+ style: { base: "solid", variants: { dashed: "dashed" } },
60
+ radius: { base: 4, variants: { pill: "9999px" } },
61
+ offset: { base: 2 },
62
+ }
63
+ ```
64
+
65
+ Reserved `recipes` (BordersRecipeProps): `as` (`"border"` default | `"outline"`) and `side`
66
+ (`top`/`right`/`bottom`/`left`, border only) are **modifiers** that route each aspect to its
67
+ longhand. The geometry aspects `width` / `style` / `offset` / `radius` name a **variant** of the
68
+ matching property by bare name (`"thick"` → `borders.width.thick`). `color` is a **value-level
69
+ `colors.*` ref** (`"colors.primary"`), never a borders token.
70
+
71
+ ## animation
72
+
73
+ Motion tokens `duration` (ms number) / `easing` (string) / `delay` (ms number) are regular
74
+ properties (base + variants). The reserved `keyframes` key holds named definitions; `recipes`
75
+ composes them into an `animation:` shorthand.
76
+
77
+ ```ts
78
+ animation: {
79
+ duration: { base: 200, variants: { fast: 120, slow: 400 } },
80
+ easing: { base: "ease-out", variants: { spring: "cubic-bezier(.5,1.5,.5,1)" } },
81
+ keyframes: {
82
+ fadeInUp: {
83
+ from: { opacity: 0, transform: "translateY(20px)" }, // literal or { ref: "colors.surface" }
84
+ to: { opacity: 1, transform: "translateY(0)" },
85
+ },
86
+ },
87
+ }
88
+ ```
89
+
90
+ A keyframe definition maps a step selector (`from` / `to` / `"0%"` / `"50%"` / grouped `"0%, 100%"`)
91
+ to declarations — kebab-case CSS property → a **literal** value or a **`{ ref }`** (resolved late so a
92
+ keyframe can animate a themed value). Authoring order is preserved.
93
+
94
+ Reserved `recipes` (AnimationRecipeProps): `keyframes` names a keyframe; `duration` / `easing` /
95
+ `delay` name a **variant** of the matching motion token; `iterationCount` / `direction` / `fillMode`
96
+ / `playState` pass through — all composed into one `animation:` shorthand.