css-is-awesome 1.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 (198) hide show
  1. package/AGENTS.md +343 -0
  2. package/CHANGELOG.md +1036 -0
  3. package/CLAUDE.md +9 -0
  4. package/CONTRACT.md +580 -0
  5. package/GEMINI.md +9 -0
  6. package/LICENSE +21 -0
  7. package/LICENSE-third-party +88 -0
  8. package/MIGRATION.md +312 -0
  9. package/README.md +380 -0
  10. package/ROADMAP.md +714 -0
  11. package/THEMING.md +290 -0
  12. package/THREE-TIERS.md +158 -0
  13. package/VERSIONING.md +200 -0
  14. package/_index.scss +34 -0
  15. package/api.scss +16 -0
  16. package/bin/README.md +57 -0
  17. package/bin/cia.cjs +114 -0
  18. package/bin/migrate-bootstrap.cjs +427 -0
  19. package/bin/migrate-tailwind.cjs +832 -0
  20. package/css-is-awesome.instructions.md +533 -0
  21. package/dist/css-is-awesome.core.css +598 -0
  22. package/dist/css-is-awesome.core.min.css +1 -0
  23. package/dist/css-is-awesome.css +3733 -0
  24. package/dist/css-is-awesome.min.css +1 -0
  25. package/dist/css-is-awesome.utilities.css +2445 -0
  26. package/dist/css-is-awesome.utilities.min.css +1 -0
  27. package/dist/tokens.css +508 -0
  28. package/dist/tokens.d.ts +266 -0
  29. package/dist/tokens.min.css +1 -0
  30. package/figma-tokens/README.md +57 -0
  31. package/figma-tokens/primitives-brand.scss +121 -0
  32. package/figma-tokens/semantic-dark.scss +61 -0
  33. package/figma-tokens/semantic-light.scss +61 -0
  34. package/figma-tokens/tokens.json +144 -0
  35. package/llm.txt +183 -0
  36. package/mcp/server.cjs +1495 -0
  37. package/package.json +199 -0
  38. package/public/icons/README.md +278 -0
  39. package/public/icons/arrow-right.svg +4 -0
  40. package/public/icons/check.svg +4 -0
  41. package/public/icons/chevron-down.svg +4 -0
  42. package/public/icons/close.svg +4 -0
  43. package/public/icons/core/arrow-down.svg +13 -0
  44. package/public/icons/core/arrow-left.svg +13 -0
  45. package/public/icons/core/arrow-right.svg +13 -0
  46. package/public/icons/core/arrow-up.svg +13 -0
  47. package/public/icons/core/bell.svg +13 -0
  48. package/public/icons/core/calendar.svg +15 -0
  49. package/public/icons/core/check.svg +12 -0
  50. package/public/icons/core/chevron-down.svg +12 -0
  51. package/public/icons/core/chevron-left.svg +12 -0
  52. package/public/icons/core/chevron-right.svg +12 -0
  53. package/public/icons/core/chevron-up.svg +12 -0
  54. package/public/icons/core/clock.svg +13 -0
  55. package/public/icons/core/close.svg +13 -0
  56. package/public/icons/core/copy.svg +13 -0
  57. package/public/icons/core/download.svg +14 -0
  58. package/public/icons/core/edit.svg +13 -0
  59. package/public/icons/core/error.svg +14 -0
  60. package/public/icons/core/external-link.svg +14 -0
  61. package/public/icons/core/eye-off.svg +15 -0
  62. package/public/icons/core/eye.svg +13 -0
  63. package/public/icons/core/filter.svg +12 -0
  64. package/public/icons/core/heart.svg +12 -0
  65. package/public/icons/core/help.svg +14 -0
  66. package/public/icons/core/home.svg +13 -0
  67. package/public/icons/core/info.svg +14 -0
  68. package/public/icons/core/loading.svg +12 -0
  69. package/public/icons/core/lock.svg +13 -0
  70. package/public/icons/core/mail.svg +13 -0
  71. package/public/icons/core/menu.svg +14 -0
  72. package/public/icons/core/message.svg +12 -0
  73. package/public/icons/core/minus.svg +12 -0
  74. package/public/icons/core/more-horizontal.svg +14 -0
  75. package/public/icons/core/pause.svg +13 -0
  76. package/public/icons/core/play.svg +12 -0
  77. package/public/icons/core/plus.svg +13 -0
  78. package/public/icons/core/refresh.svg +15 -0
  79. package/public/icons/core/save.svg +14 -0
  80. package/public/icons/core/search.svg +13 -0
  81. package/public/icons/core/settings.svg +13 -0
  82. package/public/icons/core/share.svg +14 -0
  83. package/public/icons/core/sort.svg +15 -0
  84. package/public/icons/core/star.svg +12 -0
  85. package/public/icons/core/success.svg +13 -0
  86. package/public/icons/core/trash.svg +16 -0
  87. package/public/icons/core/unlock.svg +13 -0
  88. package/public/icons/core/upload.svg +14 -0
  89. package/public/icons/core/user.svg +13 -0
  90. package/public/icons/core/users.svg +15 -0
  91. package/public/icons/core/warning.svg +14 -0
  92. package/public/icons/download.svg +4 -0
  93. package/public/icons/edit.svg +4 -0
  94. package/public/icons/menu.svg +4 -0
  95. package/public/icons/search.svg +4 -0
  96. package/public/theme.css +4179 -0
  97. package/public/themes/README.md +102 -0
  98. package/public/themes/boilerplate/theme.css +154 -0
  99. package/public/themes/boilerplate-dark/theme.css +171 -0
  100. package/public/themes/boilerplate-light/theme.css +171 -0
  101. package/public/themes/cupertino/theme.css +165 -0
  102. package/public/themes/cupertino-dark/theme.css +200 -0
  103. package/public/themes/cupertino-light/theme.css +227 -0
  104. package/public/themes/glass/theme.css +190 -0
  105. package/public/themes/glass-dark/theme.css +201 -0
  106. package/public/themes/glass-light/theme.css +229 -0
  107. package/public/themes/graphite/theme.css +155 -0
  108. package/public/themes/graphite-dark/theme.css +223 -0
  109. package/public/themes/graphite-light/theme.css +191 -0
  110. package/public/themes/press/theme.css +189 -0
  111. package/public/themes/press-dark/theme.css +199 -0
  112. package/public/themes/press-light/theme.css +235 -0
  113. package/public/themes/prism/theme.css +159 -0
  114. package/public/themes/prism-dark/theme.css +185 -0
  115. package/public/themes/prism-light/theme.css +188 -0
  116. package/public/themes/sketchbook/theme.css +152 -0
  117. package/public/themes/sketchbook-dark/theme.css +194 -0
  118. package/public/themes/sketchbook-light/theme.css +188 -0
  119. package/public/themes/terminal/theme.css +159 -0
  120. package/public/themes/terminal-dark/theme.css +229 -0
  121. package/public/themes/terminal-light/theme.css +152 -0
  122. package/scripts/README.md +70 -0
  123. package/scripts/icon-contract.json +67 -0
  124. package/scripts/icon-validator.js +205 -0
  125. package/scripts/prepare-dist.mjs +65 -0
  126. package/scripts/theme-a11y.js +415 -0
  127. package/scripts/theme-contract.json +171 -0
  128. package/scripts/theme-validator.js +674 -0
  129. package/scss/README.md +55 -0
  130. package/scss/_animations-utilities.scss +107 -0
  131. package/scss/_animations.scss +248 -0
  132. package/scss/_app-styles.scss +15 -0
  133. package/scss/_generator.scss +174 -0
  134. package/scss/_icons.scss +475 -0
  135. package/scss/_index.scss +17 -0
  136. package/scss/_layout.scss +597 -0
  137. package/scss/_mixins.scss +1036 -0
  138. package/scss/_system.scss +291 -0
  139. package/scss/_utilities.scss +377 -0
  140. package/scss/api.scss +61 -0
  141. package/scss/components/_accordion.scss +72 -0
  142. package/scss/components/_buttons.scss +171 -0
  143. package/scss/components/_copy-button.scss +69 -0
  144. package/scss/components/_data.scss +190 -0
  145. package/scss/components/_feedback.scss +197 -0
  146. package/scss/components/_forms.scss +272 -0
  147. package/scss/components/_index.scss +17 -0
  148. package/scss/components/_navigation.scss +186 -0
  149. package/scss/components/_overlay.scss +259 -0
  150. package/scss/components/_stepper.scss +159 -0
  151. package/scss/components/_tabs.scss +72 -0
  152. package/scss/core.scss +54 -0
  153. package/scss/examples/_theming.scss +107 -0
  154. package/scss/examples/_usage.scss +317 -0
  155. package/scss/main.scss +64 -0
  156. package/scss/recipes/README.md +110 -0
  157. package/scss/recipes/_bare-tags.scss +232 -0
  158. package/scss/recipes/_recipe-template.md +119 -0
  159. package/scss/recipes/combobox.md +509 -0
  160. package/scss/recipes/dialog.md +265 -0
  161. package/scss/recipes/print-to-pdf.md +263 -0
  162. package/scss/theme/README.md +67 -0
  163. package/scss/theme/_brand.scss +18 -0
  164. package/scss/theme/_colors-dark.scss +68 -0
  165. package/scss/theme/_colors-light.scss +70 -0
  166. package/scss/theme/_components.scss +94 -0
  167. package/scss/theme/_icons.scss +104 -0
  168. package/scss/theme/_index.scss +62 -0
  169. package/scss/theme/_shadows.scss +65 -0
  170. package/scss/theme/_shape.scss +13 -0
  171. package/scss/theme/_typography.scss +9 -0
  172. package/scss/theme/registry.json +447 -0
  173. package/scss/themes/boilerplate-dark.scss +212 -0
  174. package/scss/themes/boilerplate-light.scss +209 -0
  175. package/scss/themes/boilerplate.scss +181 -0
  176. package/scss/themes/cupertino-dark.scss +238 -0
  177. package/scss/themes/cupertino-light.scss +266 -0
  178. package/scss/themes/cupertino.scss +194 -0
  179. package/scss/themes/glass-dark.scss +238 -0
  180. package/scss/themes/glass-light.scss +266 -0
  181. package/scss/themes/glass.scss +220 -0
  182. package/scss/themes/graphite-dark.scss +260 -0
  183. package/scss/themes/graphite-light.scss +228 -0
  184. package/scss/themes/graphite.scss +182 -0
  185. package/scss/themes/press-dark.scss +237 -0
  186. package/scss/themes/press-light.scss +273 -0
  187. package/scss/themes/press.scss +241 -0
  188. package/scss/themes/prism-dark.scss +217 -0
  189. package/scss/themes/prism-light.scss +220 -0
  190. package/scss/themes/prism.scss +190 -0
  191. package/scss/themes/sketchbook-dark.scss +231 -0
  192. package/scss/themes/sketchbook-light.scss +231 -0
  193. package/scss/themes/sketchbook.scss +178 -0
  194. package/scss/themes/terminal-dark.scss +267 -0
  195. package/scss/themes/terminal-light.scss +175 -0
  196. package/scss/themes/terminal.scss +185 -0
  197. package/scss/tokens.scss +23 -0
  198. package/scss/utilities-only.scss +4 -0
package/scss/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # scss/
2
+
3
+ The Sass source of css-is-awesome. Tier 1 consumers want the compiled CSS in
4
+ `dist/`; Tier 2 consumers (`@use` mixins, author their own class names) want
5
+ the files in this folder. Imported through the npm package — don't copy these
6
+ into your project.
7
+
8
+ ## Canonical example
9
+
10
+ Per-component styles import the **zero-emit authoring barrel** — the whole API
11
+ under one namespace, and nothing is printed until you call a mixin (safe inside
12
+ a `.module.scss` under Next.js CSS Modules pure mode):
13
+
14
+ ```scss
15
+ @use 'css-is-awesome/api' as cia;
16
+
17
+ .hero-cta { @include cia.btn(primary, $r: full); @include cia.elevation(2); }
18
+ ```
19
+
20
+ The global tokens (`:root { --… }`) are emitted once at your app root by the CSS
21
+ bundle (`css-is-awesome`) or a theme stylesheet — not per component.
22
+
23
+ Compile with Sass + a load path (or the modern package importer):
24
+
25
+ ```bash
26
+ sass app.scss app.css --load-path=node_modules
27
+ ```
28
+
29
+ ## What lives here
30
+
31
+ | Path | Purpose |
32
+ | ----------------------- | -------------------------------------------------------------- |
33
+ | `main.scss` | Full library entry — tokens + resets + utilities. |
34
+ | `core.scss` | Tokens + resets only (pair with your own utility set). |
35
+ | `utilities-only.scss` | Drop-in `cia-*` utility classes, no tokens or resets. |
36
+ | `api.scss` | **Zero-emit authoring barrel** — `@use 'css-is-awesome/api' as cia;`. Full mixin/function API, prints nothing until called. Use in component `.module.scss` files. |
37
+ | `_index.scss` | Kitchen-sink barrel — `api` **plus** the global `.cia-anim-*` utility CSS. |
38
+ | `_mixins.scss` | Core mixin API: `color`, `space`, `btn-base`, `font`, etc. |
39
+ | `_layout.scss` | `container`, `grid`, `page-layout`, stack/inline helpers. |
40
+ | `_utilities.scss` | `cia-*` utility class generator (margin, padding, color, …). |
41
+ | `_icons.scss` | SVG + Font Awesome icon mixins (`svg`, `svg-text`, `fa`). |
42
+ | `_animations.scss` | Keyframe library + `animate()` mixin + `cia-anim-*` utilities. |
43
+ | `_generator.scss` | SCSS maps → `:root { --token: value; }` writer. |
44
+ | `_system.scss` | Compile-time constants (breakpoints, spacing scale, z-index). |
45
+ | `components/` | Per-family component mixins (`buttons`, `forms`, `data`, …). |
46
+ | `theme/` | Default theme maps (brand, colors, type, shape, shadows). |
47
+ | `recipes/` | Opt-in presets — currently `bare-tags` (Tier 3, Pico-mode). |
48
+ | `examples/` | Reference snippets, not part of the entry. Read, don't `@use`. |
49
+ | `_app-styles.scss` | Template for consumer-owned styles. NOT part of the library. |
50
+
51
+ ## Deeper reading
52
+
53
+ - Authoring rules, tier decisions, mixin contracts → `../AGENTS.md`
54
+ - Full reference (~14 KB) → `../css-is-awesome.instructions.md`
55
+ - Contributors (repo only) → `../CONTRIBUTING.md`
@@ -0,0 +1,107 @@
1
+ // ============================================================================
2
+ // ANIMATIONS-UTILITIES — keyframes + .cia-anim-* + .cia-hover-* + global safety net
3
+ // ============================================================================
4
+ // Load ONCE globally. NOT safe to `@use` from a CSS Module (`.module.scss`)
5
+ // because of the global `*, *::before, *::after` selector — CSS Modules
6
+ // pure-mode rejects unscoped selectors.
7
+ //
8
+ // Counterpart to _animations.scss (which is the mixin surface).
9
+ //
10
+ // Two consumer patterns:
11
+ //
12
+ // 1. Full bundle (recommended) — main.scss already loads this file:
13
+ // @use 'css-is-awesome/scss/main';
14
+ //
15
+ // 2. Pieces — load this file once globally, then @use the mixin file
16
+ // from any .module.scss for the animate() / animate-on() API:
17
+ // // globals.scss (loaded once at app root)
18
+ // @use 'css-is-awesome/scss/animations-utilities';
19
+ //
20
+ // // any-component.module.scss
21
+ // @use 'css-is-awesome/scss/animations' as anim;
22
+ // .my-spinner { @include anim.animate(spin, $iteration: infinite, $timing: linear); }
23
+ //
24
+ // The mixin's `animation-name: cia-spin` reference resolves at runtime
25
+ // against the global @keyframes declared here — even when the mixin call
26
+ // site is inside a CSS Module.
27
+ // ============================================================================
28
+ @use './animations' as a;
29
+
30
+ // ----------------------------------------------------------------------------
31
+ // KEYFRAME LIBRARY
32
+ // ----------------------------------------------------------------------------
33
+ // The definitions themselves live in `_animations.scss` (`keyframes-for()`),
34
+ // next to the `animate()` mixin that has to be able to emit them on its own.
35
+ // This module just asks for all of them, so the global bundle keeps shipping
36
+ // the full set while there is only ONE copy of each definition to maintain.
37
+ @each $name in a.animation-names() {
38
+ @include a.keyframes-for($name);
39
+ }
40
+
41
+ // ----------------------------------------------------------------------------
42
+ // UTILITY CLASSES — .cia-anim-* + .cia-hover-*
43
+ // ----------------------------------------------------------------------------
44
+
45
+ .cia-anim-fade-in { @include a.animate(fade-in); }
46
+ .cia-anim-fade-in-fast { @include a.animate(fade-in, $speed: fast); }
47
+ .cia-anim-fade-in-slow { @include a.animate(fade-in, $speed: slow); }
48
+
49
+ .cia-anim-fade-out { @include a.animate(fade-out); }
50
+ .cia-anim-fade-out-fast { @include a.animate(fade-out, $speed: fast); }
51
+ .cia-anim-fade-out-slow { @include a.animate(fade-out, $speed: slow); }
52
+
53
+ .cia-anim-slide-up { @include a.animate(slide-up); }
54
+ .cia-anim-slide-up-fast { @include a.animate(slide-up, $speed: fast); }
55
+ .cia-anim-slide-up-slow { @include a.animate(slide-up, $speed: slow); }
56
+
57
+ .cia-anim-slide-down { @include a.animate(slide-down); }
58
+ .cia-anim-slide-down-fast { @include a.animate(slide-down, $speed: fast); }
59
+ .cia-anim-slide-down-slow { @include a.animate(slide-down, $speed: slow); }
60
+
61
+ .cia-anim-slide-left { @include a.animate(slide-left); }
62
+ .cia-anim-slide-left-fast { @include a.animate(slide-left, $speed: fast); }
63
+ .cia-anim-slide-left-slow { @include a.animate(slide-left, $speed: slow); }
64
+
65
+ .cia-anim-slide-right { @include a.animate(slide-right); }
66
+ .cia-anim-slide-right-fast { @include a.animate(slide-right, $speed: fast); }
67
+ .cia-anim-slide-right-slow { @include a.animate(slide-right, $speed: slow); }
68
+
69
+ .cia-anim-scale-in { @include a.animate(scale-in); }
70
+ .cia-anim-scale-in-fast { @include a.animate(scale-in, $speed: fast); }
71
+ .cia-anim-scale-in-slow { @include a.animate(scale-in, $speed: slow); }
72
+
73
+ .cia-anim-pop { @include a.animate(pop); }
74
+ .cia-anim-pop-fast { @include a.animate(pop, $speed: fast); }
75
+ .cia-anim-pop-slow { @include a.animate(pop, $speed: slow); }
76
+
77
+ .cia-anim-wiggle { @include a.animate(wiggle); }
78
+ .cia-anim-wiggle-fast { @include a.animate(wiggle, $speed: fast); }
79
+ .cia-anim-wiggle-slow { @include a.animate(wiggle, $speed: slow); }
80
+
81
+ // Loopers — infinite by default when used as utility
82
+ .cia-anim-spin { @include a.animate(spin, $speed: slow, $iteration: infinite, $timing: linear); }
83
+ .cia-anim-pulse { @include a.animate(pulse, $speed: slow, $iteration: infinite); }
84
+ .cia-anim-shimmer { @include a.animate(shimmer, $speed: slow, $iteration: infinite, $timing: linear); }
85
+
86
+ .cia-hover-lift { @include a.animate-on(hover, lift); }
87
+ .cia-hover-glow { @include a.animate-on(hover, glow); }
88
+ .cia-hover-press { @include a.animate-on(hover, press); }
89
+ .cia-hover-fade { @include a.animate-on(hover, fade); }
90
+
91
+ // ----------------------------------------------------------------------------
92
+ // GLOBAL REDUCED-MOTION SAFETY NET
93
+ // ----------------------------------------------------------------------------
94
+ // Lives here (not in the mixin file) because the `*, *::before, *::after`
95
+ // selector is bare-global — CSS Modules pure-mode rejects it. Consumers
96
+ // load this file once globally; .module.scss code @use's the mixin file
97
+ // instead and inherits this safety net from the global scope.
98
+ @media (prefers-reduced-motion: reduce) {
99
+ *, *::before, *::after {
100
+ /* stylelint-disable declaration-no-important -- !important is the canonical pattern for the global reduced-motion safety net: it must override any animation/transition declared anywhere in the document. */
101
+ animation-duration: 0.01ms !important;
102
+ animation-iteration-count: 1 !important;
103
+ transition-duration: 0.01ms !important;
104
+ scroll-behavior: auto !important;
105
+ /* stylelint-enable declaration-no-important */
106
+ }
107
+ }
@@ -0,0 +1,248 @@
1
+ // ============================================================================
2
+ // ANIMATIONS — Mixin-only surface (safe to @use from .module.scss)
3
+ // ============================================================================
4
+ // Philosophy: a fixed keyframe vocabulary, theme-driven duration + easing.
5
+ // Every animation reads from --duration-* and --ease so swapping themes
6
+ // changes the feel (Terminal snappy, Glass floaty) with zero code change.
7
+ //
8
+ // Respects prefers-reduced-motion — all animations collapse to 0s.
9
+ //
10
+ // This file is the MIXIN surface. The keyframe definitions, .cia-anim-*
11
+ // utility classes, and the global *, *::before, *::after reduced-motion
12
+ // safety net live in _animations-utilities.scss — load that ONCE globally
13
+ // (via main.scss or your app's global SCSS entry).
14
+ //
15
+ // Why split: top-level rules + global selectors here would fail Next.js
16
+ // CSS Modules pure-mode (`Selector "*, *::before, *::after" is not pure`)
17
+ // when a `.module.scss` does `@use 'css-is-awesome/scss/animations' as anim;`.
18
+ // Mixin-only files are safe to @use from any scoped context.
19
+ // ============================================================================
20
+ @use 'sass:map';
21
+ @use 'sass:list';
22
+ @use 'sass:meta';
23
+
24
+ // ----------------------------------------------------------------------------
25
+ // ANIMATION VOCABULARY
26
+ // ----------------------------------------------------------------------------
27
+ // Maps animation slug → keyframe name. The keyframes themselves live in
28
+ // _animations-utilities.scss (loaded globally). The mixin emits the name as
29
+ // a string reference, which resolves at runtime against the globally-loaded
30
+ // @keyframes — even when the mixin call site is inside a CSS Module.
31
+
32
+ $_anims: (
33
+ fade-in: cia-fade-in,
34
+ fade-out: cia-fade-out,
35
+ slide-up: cia-slide-up,
36
+ slide-down: cia-slide-down,
37
+ slide-left: cia-slide-left,
38
+ slide-right: cia-slide-right,
39
+ scale-in: cia-scale-in,
40
+ pop: cia-pop,
41
+ pulse: cia-pulse,
42
+ shimmer: cia-shimmer,
43
+ spin: cia-spin,
44
+ wiggle: cia-wiggle,
45
+ );
46
+
47
+ $_speeds: (
48
+ fast: var(--duration-fast, 180ms),
49
+ normal: var(--duration-normal, 240ms),
50
+ slow: var(--duration-slow, 380ms),
51
+ );
52
+
53
+ // ----------------------------------------------------------------------------
54
+ // MIXIN API
55
+ // ----------------------------------------------------------------------------
56
+ // @include animate(fade-in);
57
+ // @include animate(slide-up, $speed: slow);
58
+ // @include animate(spin, $speed: slow, $iteration: infinite, $timing: linear);
59
+ // ----------------------------------------------------------------------------
60
+
61
+ // ----------------------------------------------------------------------------
62
+ // KEYFRAME LIBRARY — the single definition of every cia keyframe.
63
+ //
64
+ // This lives here, next to `animate()`, rather than in `_animations-utilities`
65
+ // because `animate()` has to be able to emit the keyframe it references. The
66
+ // utilities module loops over these for the global bundle, so there is exactly
67
+ // one copy of each definition.
68
+ //
69
+ // `@at-root` hoists the block out of whatever selector called `animate()`, and
70
+ // co-emitting is what makes the reference survive CSS Modules: a top-level
71
+ // `@keyframes` gets hashed to a local name while an `animation-name` written
72
+ // elsewhere does not. Emitted together, both are renamed or neither is —
73
+ // the same reason `spinner()` and `skeleton()` already do this.
74
+ // ----------------------------------------------------------------------------
75
+ /// Every animation name `animate()` and `keyframes-for()` accept.
76
+ /// Exposed so the utilities bundle can loop the full set without duplicating
77
+ /// the registry, and so consumers can enumerate what's available.
78
+ @function animation-names() {
79
+ @return map.keys($_anims);
80
+ }
81
+
82
+ // Names already emitted in THIS compilation. Sass resets module state per
83
+ // compiled file, which is exactly the granularity we want: one copy per output
84
+ // file (CSS Modules needs its own hashed copy per module), never 42 copies in
85
+ // one bundle because 30 utility classes each asked for one.
86
+ $_emitted: ();
87
+
88
+ @mixin keyframes-for($name) {
89
+ $keyframe: map.get($_anims, $name);
90
+ @if not $keyframe {
91
+ @error "keyframes-for(): unknown animation '#{$name}'. Known: #{map.keys($_anims)}";
92
+ }
93
+
94
+ @if not list.index($_emitted, $name) {
95
+ $_emitted: list.append($_emitted, $name) !global;
96
+ @include _emit-keyframe($name);
97
+ }
98
+ }
99
+
100
+ @mixin _emit-keyframe($name) {
101
+ @at-root {
102
+ @if $name == fade-in {
103
+ @keyframes cia-fade-in { from { opacity: 0; } to { opacity: 1; } }
104
+ } @else if $name == fade-out {
105
+ @keyframes cia-fade-out { from { opacity: 1; } to { opacity: 0; } }
106
+ } @else if $name == slide-up {
107
+ @keyframes cia-slide-up {
108
+ from { opacity: 0; transform: translateY(8px); }
109
+ to { opacity: 1; transform: translateY(0); }
110
+ }
111
+ } @else if $name == slide-down {
112
+ @keyframes cia-slide-down {
113
+ from { opacity: 0; transform: translateY(-8px); }
114
+ to { opacity: 1; transform: translateY(0); }
115
+ }
116
+ } @else if $name == slide-left {
117
+ @keyframes cia-slide-left {
118
+ from { opacity: 0; transform: translateX(8px); }
119
+ to { opacity: 1; transform: translateX(0); }
120
+ }
121
+ } @else if $name == slide-right {
122
+ @keyframes cia-slide-right {
123
+ from { opacity: 0; transform: translateX(-8px); }
124
+ to { opacity: 1; transform: translateX(0); }
125
+ }
126
+ } @else if $name == scale-in {
127
+ @keyframes cia-scale-in {
128
+ from { opacity: 0; transform: scale(0.96); }
129
+ to { opacity: 1; transform: scale(1); }
130
+ }
131
+ } @else if $name == pop {
132
+ @keyframes cia-pop {
133
+ 0% { transform: scale(1); }
134
+ 40% { transform: scale(1.06); }
135
+ 100% { transform: scale(1); }
136
+ }
137
+ } @else if $name == pulse {
138
+ @keyframes cia-pulse {
139
+ 0%, 100% { opacity: 1; }
140
+ 50% { opacity: 0.55; }
141
+ }
142
+ } @else if $name == shimmer {
143
+ @keyframes cia-shimmer {
144
+ 0% { background-position: -200% 0; }
145
+ 100% { background-position: 200% 0; }
146
+ }
147
+ } @else if $name == spin {
148
+ @keyframes cia-spin {
149
+ from { transform: rotate(0deg); }
150
+ to { transform: rotate(360deg); }
151
+ }
152
+ } @else if $name == wiggle {
153
+ @keyframes cia-wiggle {
154
+ 0%, 100% { transform: rotate(0deg); }
155
+ 25% { transform: rotate(-2deg); }
156
+ 75% { transform: rotate(2deg); }
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ @mixin animate(
163
+ $name,
164
+ $speed: normal,
165
+ $delay: 0s,
166
+ $iteration: 1,
167
+ $fill: both,
168
+ $timing: var(--ease, cubic-bezier(0.33, 0.66, 0.33, 1))
169
+ ) {
170
+ $keyframe: map.get($_anims, $name);
171
+
172
+ // A raw duration (`1s`, `750ms`) passes through. Only an unrecognised
173
+ // *keyword* is an error — rejecting raw values forced consumers to hand-roll
174
+ // the entire animation just to keep a duration the scale doesn't carry, which
175
+ // is exactly what Boiler's spinners did.
176
+ $duration: map.get($_speeds, $speed);
177
+ @if not $duration and meta.type-of($speed) == 'number' {
178
+ $duration: $speed;
179
+ }
180
+
181
+ @if not $keyframe {
182
+ @error "animate(): unknown animation '#{$name}'. Known: #{map.keys($_anims)}";
183
+ }
184
+ @if not $duration {
185
+ @error "animate(): unknown speed '#{$speed}'. Known: fast, normal, slow, or a duration like 1s";
186
+ }
187
+
188
+ // Co-emit the keyframe this rule references. Without it, `animate()` on the
189
+ // documented per-component path (`css-is-awesome/api`, or a theme <link> for
190
+ // tokens) emitted an `animation-name` pointing at nothing — valid CSS, no
191
+ // error, no animation. Duplicate identical @keyframes across files are
192
+ // harmless and gzip away; a dangling reference is not.
193
+ @include keyframes-for($name);
194
+
195
+ animation-name: $keyframe;
196
+ animation-duration: $duration;
197
+ animation-delay: $delay;
198
+ animation-iteration-count: $iteration;
199
+ animation-fill-mode: $fill;
200
+ animation-timing-function: $timing;
201
+
202
+ @media (prefers-reduced-motion: reduce) {
203
+ /* stylelint-disable declaration-no-important -- !important is the canonical pattern for honoring prefers-reduced-motion: it must override any inline animation declared by component author or third-party CSS. */
204
+ animation-duration: 0.01ms !important;
205
+ animation-iteration-count: 1 !important;
206
+ /* stylelint-enable declaration-no-important */
207
+ }
208
+ }
209
+
210
+ // ----------------------------------------------------------------------------
211
+ // HOVER / INTERACTION HELPERS
212
+ // ----------------------------------------------------------------------------
213
+ // @include animate-on(hover, lift);
214
+ // @include animate-on(hover, glow);
215
+ // ----------------------------------------------------------------------------
216
+
217
+ @mixin animate-on($event: hover, $effect: lift) {
218
+ transition:
219
+ transform var(--duration-fast, 180ms) var(--ease, ease),
220
+ box-shadow var(--duration-fast, 180ms) var(--ease, ease),
221
+ opacity var(--duration-fast, 180ms) var(--ease, ease);
222
+
223
+ @if $effect == lift {
224
+ &:#{$event} {
225
+ transform: translateY(-2px);
226
+ box-shadow: var(--shadow-md);
227
+ }
228
+ } @else if $effect == glow {
229
+ &:#{$event} {
230
+ box-shadow: var(--glow-md);
231
+ }
232
+ } @else if $effect == press {
233
+ &:#{$event} {
234
+ transform: translateY(1px) scale(0.99);
235
+ }
236
+ } @else if $effect == fade {
237
+ &:#{$event} {
238
+ opacity: 0.7;
239
+ }
240
+ } @else {
241
+ @error "animate-on(): unknown effect '#{$effect}'. Known: lift, glow, press, fade";
242
+ }
243
+
244
+ @media (prefers-reduced-motion: reduce) {
245
+ transition: none;
246
+ &:#{$event} { transform: none; }
247
+ }
248
+ }
@@ -0,0 +1,15 @@
1
+ // ============================================================================
2
+ // APP STYLES — Project-Owned Custom Styles
3
+ // ============================================================================
4
+ // This file belongs to YOUR PROJECT, not the boilerplate.
5
+ // The boilerplate will NEVER overwrite this file during /update-project syncs.
6
+ //
7
+ // Use this file for:
8
+ // - Custom animations specific to your app
9
+ // - App-specific layout patterns
10
+ // - Unique styles that don't belong in the shared design system
11
+ // - Project-level utility classes
12
+ //
13
+ // This is NOT an override file — it's for NEW styles your project needs.
14
+ // For theme colors, use _theme.scss instead.
15
+ // ============================================================================
@@ -0,0 +1,174 @@
1
+ // ============================================================================
2
+ // GENERATOR - SCSS Maps → CSS Custom Properties
3
+ // ============================================================================
4
+ // Loops over theme maps and system maps to generate CSS custom properties.
5
+ // This is the bridge: SCSS data → :root { --token: value; }
6
+ // Replaces the old _tokens.scss
7
+ // ============================================================================
8
+ @use 'sass:map';
9
+ @use 'sass:list';
10
+ @use 'sass:meta';
11
+ @use 'sass:string';
12
+ @use './theme' as t;
13
+ @use './system' as s;
14
+
15
+ // ----------------------------------------------------------------------------
16
+ // HELPER: Generic map-to-vars generator
17
+ // ----------------------------------------------------------------------------
18
+ @mixin _vars($map, $prefix: '') {
19
+ @each $key, $value in $map {
20
+ @if $prefix != '' {
21
+ --#{$prefix}-#{$key}: #{$value};
22
+ } @else {
23
+ --#{$key}: #{$value};
24
+ }
25
+ }
26
+ }
27
+
28
+ // ----------------------------------------------------------------------------
29
+ // VALIDATION: Ensure theme maps have required keys
30
+ // ----------------------------------------------------------------------------
31
+ $_required-light-keys: (
32
+ background-default, surface-default, text-primary, text-secondary,
33
+ text-muted, text-inverse, text-link, border-default, border-focus,
34
+ action-primary-default, action-primary-hover, action-primary-active,
35
+ success-default, warning-default, error-default, info-default,
36
+ );
37
+
38
+ @mixin _validate-theme {
39
+ @each $key in $_required-light-keys {
40
+ @if not map.has-key(t.$theme-light, $key) {
41
+ @warn "Theme missing required light color: #{$key}";
42
+ }
43
+ }
44
+ @each $key in $_required-light-keys {
45
+ @if not map.has-key(t.$theme-dark, $key) {
46
+ @warn "Theme missing required dark color: #{$key}";
47
+ }
48
+ }
49
+ }
50
+
51
+ // ----------------------------------------------------------------------------
52
+ // GENERATE: All light mode tokens
53
+ // ----------------------------------------------------------------------------
54
+ @mixin generate-light {
55
+ // Brand colors (from theme)
56
+ @include _vars(t.$theme-brand, 'brand');
57
+
58
+ // Semantic colors - light (from theme)
59
+ @include _vars(t.$theme-light);
60
+
61
+ // Border radius (from theme)
62
+ @include _vars(t.$theme-radius, 'radius');
63
+
64
+ // Shadows - light (from theme)
65
+ @include _vars(t.$theme-shadows-light, 'shadow');
66
+
67
+ // Fonts (from theme)
68
+ @each $key, $stack in t.$theme-fonts {
69
+ --font-#{$key}: #{list.join($stack, (), comma)};
70
+ }
71
+
72
+ // Spacing scale (from system)
73
+ //
74
+ // The numbered scale is the source of truth (locked decision); the t-shirt
75
+ // names are aliases. They now emit as var() REFERENCES to the numbered
76
+ // token rather than as duplicate literals, so a theme that sets --space-4
77
+ // moves --space-md with it.
78
+ //
79
+ // Previously both emitted as independent literals that happened to hold the
80
+ // same value. A theme could only declare the six t-shirt names, while
81
+ // components overwhelmingly call space(4)/space(1) -> var(--space-4). So
82
+ // themes could not change spacing at all: the token they set and the token
83
+ // components read were different variables. That is why swapping a theme
84
+ // repainted colours but never re-proportioned the page.
85
+ @each $key, $value in s.$spacing-scale {
86
+ --space-#{$key}: #{$value};
87
+ }
88
+ @each $key in (0, none, 2xs) {
89
+ --space-#{$key}: #{map.get(s.$spacing, $key)};
90
+ }
91
+ @each $alias, $num in s.$spacing-aliases {
92
+ --space-#{$alias}: var(--space-#{$num});
93
+ }
94
+
95
+ // Z-index (from system)
96
+ @include _vars(s.$z-layers, 'z');
97
+
98
+ // Typography (from system)
99
+ @include _vars(s.$font-sizes, 'font-size');
100
+ @include _vars(s.$font-weights, 'font-weight');
101
+ @include _vars(s.$line-heights, 'line-height');
102
+
103
+ // Durations (from system)
104
+ @include _vars(s.$durations, 'duration');
105
+
106
+ // Opacity (from system)
107
+ @include _vars(s.$opacities, 'opacity');
108
+
109
+ // Component tokens (Feature 1.3) — emit each entry as a CSS custom
110
+ // property on :root. Themes may override these selectively in their
111
+ // own :root block without forking the library.
112
+ @include _vars(t.$theme-components);
113
+ }
114
+
115
+ // ----------------------------------------------------------------------------
116
+ // GENERATE: Dark mode overrides only
117
+ // ----------------------------------------------------------------------------
118
+ @mixin generate-dark {
119
+ // Semantic colors - dark (from theme)
120
+ @include _vars(t.$theme-dark);
121
+
122
+ // Shadows - dark (from theme)
123
+ @include _vars(t.$theme-shadows-dark, 'shadow');
124
+ }
125
+
126
+ // ----------------------------------------------------------------------------
127
+ // GENERATE: Full theme (light base + dark variant)
128
+ // ----------------------------------------------------------------------------
129
+ @mixin generate-theme {
130
+ @include _validate-theme;
131
+
132
+ // :where() drops these library defaults to zero specificity so ANY theme
133
+ // declaration outruns them regardless of load order.
134
+ //
135
+ // Without it, a theme file dropped in as theme.css (which emits a bare
136
+ // `:root` so it works with no markup change) ties this block at (0,1,0) —
137
+ // and the documented load order is theme first, library second, so the
138
+ // LIBRARY won. Colours were unaffected because only themes declare them,
139
+ // but every token the library also emits — the spacing scale, z-layers,
140
+ // font sizes — silently ignored the theme.
141
+ //
142
+ // Specificity only ever decreases here, so nothing that used to win can
143
+ // start losing. Consumers overriding tokens from their own `:root` now win
144
+ // outright instead of depending on source order. Deliberately :where() and
145
+ // not @layer — see the project rule against layered CSS.
146
+ :where(:root) {
147
+ @include generate-light;
148
+ }
149
+
150
+ [data-theme="light"] {
151
+ @include generate-light;
152
+ }
153
+
154
+ [data-theme="dark"] {
155
+ @include generate-dark;
156
+ }
157
+
158
+ @media (prefers-color-scheme: dark) {
159
+ :root:not([data-theme="light"]) {
160
+ @include generate-dark;
161
+ }
162
+ }
163
+ }
164
+
165
+ // ----------------------------------------------------------------------------
166
+ // GENERATE: Scoped theme (for component-level theming)
167
+ // ----------------------------------------------------------------------------
168
+ @mixin generate-scoped($mode: light) {
169
+ @if $mode == light {
170
+ @include generate-light;
171
+ } @else {
172
+ @include generate-dark;
173
+ }
174
+ }