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.
- package/AGENTS.md +343 -0
- package/CHANGELOG.md +1036 -0
- package/CLAUDE.md +9 -0
- package/CONTRACT.md +580 -0
- package/GEMINI.md +9 -0
- package/LICENSE +21 -0
- package/LICENSE-third-party +88 -0
- package/MIGRATION.md +312 -0
- package/README.md +380 -0
- package/ROADMAP.md +714 -0
- package/THEMING.md +290 -0
- package/THREE-TIERS.md +158 -0
- package/VERSIONING.md +200 -0
- package/_index.scss +34 -0
- package/api.scss +16 -0
- package/bin/README.md +57 -0
- package/bin/cia.cjs +114 -0
- package/bin/migrate-bootstrap.cjs +427 -0
- package/bin/migrate-tailwind.cjs +832 -0
- package/css-is-awesome.instructions.md +533 -0
- package/dist/css-is-awesome.core.css +598 -0
- package/dist/css-is-awesome.core.min.css +1 -0
- package/dist/css-is-awesome.css +3733 -0
- package/dist/css-is-awesome.min.css +1 -0
- package/dist/css-is-awesome.utilities.css +2445 -0
- package/dist/css-is-awesome.utilities.min.css +1 -0
- package/dist/tokens.css +508 -0
- package/dist/tokens.d.ts +266 -0
- package/dist/tokens.min.css +1 -0
- package/figma-tokens/README.md +57 -0
- package/figma-tokens/primitives-brand.scss +121 -0
- package/figma-tokens/semantic-dark.scss +61 -0
- package/figma-tokens/semantic-light.scss +61 -0
- package/figma-tokens/tokens.json +144 -0
- package/llm.txt +183 -0
- package/mcp/server.cjs +1495 -0
- package/package.json +199 -0
- package/public/icons/README.md +278 -0
- package/public/icons/arrow-right.svg +4 -0
- package/public/icons/check.svg +4 -0
- package/public/icons/chevron-down.svg +4 -0
- package/public/icons/close.svg +4 -0
- package/public/icons/core/arrow-down.svg +13 -0
- package/public/icons/core/arrow-left.svg +13 -0
- package/public/icons/core/arrow-right.svg +13 -0
- package/public/icons/core/arrow-up.svg +13 -0
- package/public/icons/core/bell.svg +13 -0
- package/public/icons/core/calendar.svg +15 -0
- package/public/icons/core/check.svg +12 -0
- package/public/icons/core/chevron-down.svg +12 -0
- package/public/icons/core/chevron-left.svg +12 -0
- package/public/icons/core/chevron-right.svg +12 -0
- package/public/icons/core/chevron-up.svg +12 -0
- package/public/icons/core/clock.svg +13 -0
- package/public/icons/core/close.svg +13 -0
- package/public/icons/core/copy.svg +13 -0
- package/public/icons/core/download.svg +14 -0
- package/public/icons/core/edit.svg +13 -0
- package/public/icons/core/error.svg +14 -0
- package/public/icons/core/external-link.svg +14 -0
- package/public/icons/core/eye-off.svg +15 -0
- package/public/icons/core/eye.svg +13 -0
- package/public/icons/core/filter.svg +12 -0
- package/public/icons/core/heart.svg +12 -0
- package/public/icons/core/help.svg +14 -0
- package/public/icons/core/home.svg +13 -0
- package/public/icons/core/info.svg +14 -0
- package/public/icons/core/loading.svg +12 -0
- package/public/icons/core/lock.svg +13 -0
- package/public/icons/core/mail.svg +13 -0
- package/public/icons/core/menu.svg +14 -0
- package/public/icons/core/message.svg +12 -0
- package/public/icons/core/minus.svg +12 -0
- package/public/icons/core/more-horizontal.svg +14 -0
- package/public/icons/core/pause.svg +13 -0
- package/public/icons/core/play.svg +12 -0
- package/public/icons/core/plus.svg +13 -0
- package/public/icons/core/refresh.svg +15 -0
- package/public/icons/core/save.svg +14 -0
- package/public/icons/core/search.svg +13 -0
- package/public/icons/core/settings.svg +13 -0
- package/public/icons/core/share.svg +14 -0
- package/public/icons/core/sort.svg +15 -0
- package/public/icons/core/star.svg +12 -0
- package/public/icons/core/success.svg +13 -0
- package/public/icons/core/trash.svg +16 -0
- package/public/icons/core/unlock.svg +13 -0
- package/public/icons/core/upload.svg +14 -0
- package/public/icons/core/user.svg +13 -0
- package/public/icons/core/users.svg +15 -0
- package/public/icons/core/warning.svg +14 -0
- package/public/icons/download.svg +4 -0
- package/public/icons/edit.svg +4 -0
- package/public/icons/menu.svg +4 -0
- package/public/icons/search.svg +4 -0
- package/public/theme.css +4179 -0
- package/public/themes/README.md +102 -0
- package/public/themes/boilerplate/theme.css +154 -0
- package/public/themes/boilerplate-dark/theme.css +171 -0
- package/public/themes/boilerplate-light/theme.css +171 -0
- package/public/themes/cupertino/theme.css +165 -0
- package/public/themes/cupertino-dark/theme.css +200 -0
- package/public/themes/cupertino-light/theme.css +227 -0
- package/public/themes/glass/theme.css +190 -0
- package/public/themes/glass-dark/theme.css +201 -0
- package/public/themes/glass-light/theme.css +229 -0
- package/public/themes/graphite/theme.css +155 -0
- package/public/themes/graphite-dark/theme.css +223 -0
- package/public/themes/graphite-light/theme.css +191 -0
- package/public/themes/press/theme.css +189 -0
- package/public/themes/press-dark/theme.css +199 -0
- package/public/themes/press-light/theme.css +235 -0
- package/public/themes/prism/theme.css +159 -0
- package/public/themes/prism-dark/theme.css +185 -0
- package/public/themes/prism-light/theme.css +188 -0
- package/public/themes/sketchbook/theme.css +152 -0
- package/public/themes/sketchbook-dark/theme.css +194 -0
- package/public/themes/sketchbook-light/theme.css +188 -0
- package/public/themes/terminal/theme.css +159 -0
- package/public/themes/terminal-dark/theme.css +229 -0
- package/public/themes/terminal-light/theme.css +152 -0
- package/scripts/README.md +70 -0
- package/scripts/icon-contract.json +67 -0
- package/scripts/icon-validator.js +205 -0
- package/scripts/prepare-dist.mjs +65 -0
- package/scripts/theme-a11y.js +415 -0
- package/scripts/theme-contract.json +171 -0
- package/scripts/theme-validator.js +674 -0
- package/scss/README.md +55 -0
- package/scss/_animations-utilities.scss +107 -0
- package/scss/_animations.scss +248 -0
- package/scss/_app-styles.scss +15 -0
- package/scss/_generator.scss +174 -0
- package/scss/_icons.scss +475 -0
- package/scss/_index.scss +17 -0
- package/scss/_layout.scss +597 -0
- package/scss/_mixins.scss +1036 -0
- package/scss/_system.scss +291 -0
- package/scss/_utilities.scss +377 -0
- package/scss/api.scss +61 -0
- package/scss/components/_accordion.scss +72 -0
- package/scss/components/_buttons.scss +171 -0
- package/scss/components/_copy-button.scss +69 -0
- package/scss/components/_data.scss +190 -0
- package/scss/components/_feedback.scss +197 -0
- package/scss/components/_forms.scss +272 -0
- package/scss/components/_index.scss +17 -0
- package/scss/components/_navigation.scss +186 -0
- package/scss/components/_overlay.scss +259 -0
- package/scss/components/_stepper.scss +159 -0
- package/scss/components/_tabs.scss +72 -0
- package/scss/core.scss +54 -0
- package/scss/examples/_theming.scss +107 -0
- package/scss/examples/_usage.scss +317 -0
- package/scss/main.scss +64 -0
- package/scss/recipes/README.md +110 -0
- package/scss/recipes/_bare-tags.scss +232 -0
- package/scss/recipes/_recipe-template.md +119 -0
- package/scss/recipes/combobox.md +509 -0
- package/scss/recipes/dialog.md +265 -0
- package/scss/recipes/print-to-pdf.md +263 -0
- package/scss/theme/README.md +67 -0
- package/scss/theme/_brand.scss +18 -0
- package/scss/theme/_colors-dark.scss +68 -0
- package/scss/theme/_colors-light.scss +70 -0
- package/scss/theme/_components.scss +94 -0
- package/scss/theme/_icons.scss +104 -0
- package/scss/theme/_index.scss +62 -0
- package/scss/theme/_shadows.scss +65 -0
- package/scss/theme/_shape.scss +13 -0
- package/scss/theme/_typography.scss +9 -0
- package/scss/theme/registry.json +447 -0
- package/scss/themes/boilerplate-dark.scss +212 -0
- package/scss/themes/boilerplate-light.scss +209 -0
- package/scss/themes/boilerplate.scss +181 -0
- package/scss/themes/cupertino-dark.scss +238 -0
- package/scss/themes/cupertino-light.scss +266 -0
- package/scss/themes/cupertino.scss +194 -0
- package/scss/themes/glass-dark.scss +238 -0
- package/scss/themes/glass-light.scss +266 -0
- package/scss/themes/glass.scss +220 -0
- package/scss/themes/graphite-dark.scss +260 -0
- package/scss/themes/graphite-light.scss +228 -0
- package/scss/themes/graphite.scss +182 -0
- package/scss/themes/press-dark.scss +237 -0
- package/scss/themes/press-light.scss +273 -0
- package/scss/themes/press.scss +241 -0
- package/scss/themes/prism-dark.scss +217 -0
- package/scss/themes/prism-light.scss +220 -0
- package/scss/themes/prism.scss +190 -0
- package/scss/themes/sketchbook-dark.scss +231 -0
- package/scss/themes/sketchbook-light.scss +231 -0
- package/scss/themes/sketchbook.scss +178 -0
- package/scss/themes/terminal-dark.scss +267 -0
- package/scss/themes/terminal-light.scss +175 -0
- package/scss/themes/terminal.scss +185 -0
- package/scss/tokens.scss +23 -0
- 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
|
+
}
|