@xenosystem/elements-react 0.0.1 → 0.0.3

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 (49) hide show
  1. package/LICENSE +18 -18
  2. package/README.md +41 -41
  3. package/package.json +58 -58
  4. package/src/chrome-separated.css +132 -132
  5. package/src/chrome-unified.css +72 -72
  6. package/src/containers/Badges.css +116 -116
  7. package/src/containers/avatar.css +70 -70
  8. package/src/containers/card.css +75 -75
  9. package/src/containers/chip.css +103 -103
  10. package/src/containers/list-row.css +132 -132
  11. package/src/containers/message-bubble.css +112 -112
  12. package/src/containers/panel.css +120 -120
  13. package/src/containers/table.css +102 -102
  14. package/src/containers/tile.css +17 -17
  15. package/src/content/callout.css +77 -77
  16. package/src/content/caret.css +30 -30
  17. package/src/content/code-block.css +159 -159
  18. package/src/content/collapsible.css +80 -80
  19. package/src/content/inline-code.css +14 -14
  20. package/src/content/model-picker.css +219 -219
  21. package/src/content/source-card.css +99 -99
  22. package/src/content/sources-disclosure.css +118 -118
  23. package/src/controls.css +453 -453
  24. package/src/fonts.css +34 -34
  25. package/src/forms/Checkbox.css +114 -114
  26. package/src/forms/RadioGroup.css +115 -115
  27. package/src/goo.css +97 -97
  28. package/src/icon-motion.css +1909 -1909
  29. package/src/layout/ResizablePanel.css +79 -79
  30. package/src/nav/sidebar.css +296 -296
  31. package/src/overlays/date-time-picker.css +238 -238
  32. package/src/overlays/menu.css +195 -195
  33. package/src/overlays/modal.css +167 -167
  34. package/src/overlays/picker-field.css +81 -81
  35. package/src/overlays/pill-filter.css +129 -129
  36. package/src/overlays/reveal.css +56 -56
  37. package/src/overlays/segmented-control.css +185 -185
  38. package/src/overlays/tabs.css +143 -143
  39. package/src/overlays/tooltip.css +126 -126
  40. package/src/scrollbar.css +184 -184
  41. package/src/size.css +179 -179
  42. package/src/status/progress-bar.css +88 -88
  43. package/src/status/spinner.css +33 -33
  44. package/src/status/statusindicator.css +65 -65
  45. package/src/status/steptimeline.css +137 -137
  46. package/src/status/thinkingcube.css +75 -75
  47. package/src/xeno-element.css +60 -60
  48. package/src/xeno-elements.css +71 -71
  49. package/src/xeno-theme.css +99 -99
@@ -1,65 +1,65 @@
1
- /*
2
- * StatusIndicator — an inline square mark + label.
3
- *
4
- * CSS-first: the mark's paint is driven by `data-tone`. The mark is a rounded SQUARE (radius-xs),
5
- * never a circle. `neutral`/`active` stay monochrome (theme greys); `success` colours from the injected
6
- * `--xeno-status-tone` var (set from the status token by the renderer); `danger` uses `--xeno-danger`.
7
- * The only motion is the `active` mark's breathing, gated behind `prefers-reduced-motion: no-preference`.
8
- * No literal colours here.
9
- */
10
-
11
- .xeno-statusindicator {
12
- display: inline-flex;
13
- align-items: center;
14
- gap: 6px;
15
- font-family: inherit;
16
- font-size: 13px;
17
- line-height: 1;
18
- color: var(--xeno-text);
19
- }
20
-
21
- .xeno-statusindicator-mark {
22
- flex: none;
23
- width: 8px;
24
- height: 8px;
25
- border-radius: var(--xeno-radius-xs);
26
- background: var(--xeno-muted);
27
- }
28
-
29
- .xeno-statusindicator[data-tone='active'] .xeno-statusindicator-mark {
30
- background: var(--xeno-active);
31
- }
32
-
33
- .xeno-statusindicator[data-tone='success'] .xeno-statusindicator-mark {
34
- background: var(--xeno-status-tone);
35
- }
36
-
37
- .xeno-statusindicator[data-tone='danger'] .xeno-statusindicator-mark {
38
- background: var(--xeno-danger);
39
- }
40
-
41
- /* neutral label reads a touch dimmer; the tonal ones keep the mark as the accent */
42
- .xeno-statusindicator[data-tone='neutral'] .xeno-statusindicator-label {
43
- color: var(--xeno-muted);
44
- }
45
-
46
- .xeno-statusindicator[data-tone='danger'] .xeno-statusindicator-label {
47
- color: var(--xeno-danger);
48
- }
49
-
50
- @media (prefers-reduced-motion: no-preference) {
51
- @keyframes xeno-statusindicator-breathe {
52
- 0%,
53
- 100% {
54
- transform: scale(1);
55
- opacity: 1;
56
- }
57
- 50% {
58
- transform: scale(0.72);
59
- opacity: 0.6;
60
- }
61
- }
62
- .xeno-statusindicator[data-tone='active'] .xeno-statusindicator-mark {
63
- animation: xeno-statusindicator-breathe 1.6s var(--xeno-ease) infinite;
64
- }
65
- }
1
+ /*
2
+ * StatusIndicator — an inline square mark + label.
3
+ *
4
+ * CSS-first: the mark's paint is driven by `data-tone`. The mark is a rounded SQUARE (radius-xs),
5
+ * never a circle. `neutral`/`active` stay monochrome (theme greys); `success` colours from the injected
6
+ * `--xeno-status-tone` var (set from the status token by the renderer); `danger` uses `--xeno-danger`.
7
+ * The only motion is the `active` mark's breathing, gated behind `prefers-reduced-motion: no-preference`.
8
+ * No literal colours here.
9
+ */
10
+
11
+ .xeno-statusindicator {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ gap: 6px;
15
+ font-family: inherit;
16
+ font-size: 13px;
17
+ line-height: 1;
18
+ color: var(--xeno-text);
19
+ }
20
+
21
+ .xeno-statusindicator-mark {
22
+ flex: none;
23
+ width: 8px;
24
+ height: 8px;
25
+ border-radius: var(--xeno-radius-xs);
26
+ background: var(--xeno-muted);
27
+ }
28
+
29
+ .xeno-statusindicator[data-tone='active'] .xeno-statusindicator-mark {
30
+ background: var(--xeno-active);
31
+ }
32
+
33
+ .xeno-statusindicator[data-tone='success'] .xeno-statusindicator-mark {
34
+ background: var(--xeno-status-tone);
35
+ }
36
+
37
+ .xeno-statusindicator[data-tone='danger'] .xeno-statusindicator-mark {
38
+ background: var(--xeno-danger);
39
+ }
40
+
41
+ /* neutral label reads a touch dimmer; the tonal ones keep the mark as the accent */
42
+ .xeno-statusindicator[data-tone='neutral'] .xeno-statusindicator-label {
43
+ color: var(--xeno-muted);
44
+ }
45
+
46
+ .xeno-statusindicator[data-tone='danger'] .xeno-statusindicator-label {
47
+ color: var(--xeno-danger);
48
+ }
49
+
50
+ @media (prefers-reduced-motion: no-preference) {
51
+ @keyframes xeno-statusindicator-breathe {
52
+ 0%,
53
+ 100% {
54
+ transform: scale(1);
55
+ opacity: 1;
56
+ }
57
+ 50% {
58
+ transform: scale(0.72);
59
+ opacity: 0.6;
60
+ }
61
+ }
62
+ .xeno-statusindicator[data-tone='active'] .xeno-statusindicator-mark {
63
+ animation: xeno-statusindicator-breathe 1.6s var(--xeno-ease) infinite;
64
+ }
65
+ }
@@ -1,137 +1,137 @@
1
- /*
2
- * StepTimeline — a vertical run of steps threaded by a hairline spine.
3
- *
4
- * CSS-first: paint is driven by `data-status` on each step; the size mark is a rounded SQUARE
5
- * (radius-xs), never a circle. Monochrome throughout — spine + marks are greys from `xeno-theme.css`.
6
- * The only motion is the `active` mark's breathing, gated behind `prefers-reduced-motion: no-preference`.
7
- * No literal colours here.
8
- */
9
-
10
- .xeno-steptimeline {
11
- list-style: none;
12
- margin: 0;
13
- padding: 0;
14
- }
15
-
16
- /* Two columns: the rail (fixed mark width) + the body. Rows stack with no gap so the spine of one
17
- step reaches the mark of the next. */
18
- .xeno-steptimeline-step {
19
- display: grid;
20
- grid-template-columns: 16px 1fr;
21
- column-gap: 12px;
22
- }
23
-
24
- /* ── Rail (mark + spine) — decorative, aria-hidden ───────────────────────────────────── */
25
- .xeno-steptimeline-rail {
26
- display: flex;
27
- flex-direction: column;
28
- align-items: center;
29
- /* stretch to the row height so the spine's flex fill spans down to the next mark */
30
- }
31
-
32
- .xeno-steptimeline-mark {
33
- box-sizing: border-box;
34
- display: inline-flex;
35
- align-items: center;
36
- justify-content: center;
37
- flex: none;
38
- width: 16px;
39
- height: 16px;
40
- margin-top: 1px;
41
- border-radius: var(--xeno-radius-xs);
42
- }
43
-
44
- /* pending — an outline square, not yet reached */
45
- .xeno-steptimeline-step[data-status='pending'] .xeno-steptimeline-mark {
46
- background: transparent;
47
- border: 1.5px solid var(--xeno-muted);
48
- }
49
-
50
- /* active — a filled square, breathing (see reduced-motion gate below) */
51
- .xeno-steptimeline-step[data-status='active'] .xeno-steptimeline-mark {
52
- background: var(--xeno-active);
53
- }
54
-
55
- /* done — filled to text colour; the check glyph inherits `--xeno-on-accent` via currentColor */
56
- .xeno-steptimeline-step[data-status='done'] .xeno-steptimeline-mark {
57
- background: var(--xeno-text);
58
- color: var(--xeno-on-accent);
59
- }
60
-
61
- /* the connecting spine — a 1.5px rounded border-coloured bar filling to the next mark */
62
- .xeno-steptimeline-spine {
63
- flex: 1 1 auto;
64
- width: 1.5px;
65
- min-height: 12px;
66
- margin: 2px 0;
67
- background: var(--xeno-border);
68
- border-radius: var(--xeno-radius-hair);
69
- }
70
-
71
- /* ── Body (label + optional time) ────────────────────────────────────────────────────── */
72
- .xeno-steptimeline-body {
73
- display: flex;
74
- align-items: baseline;
75
- gap: 12px;
76
- min-height: 18px;
77
- }
78
-
79
- /* space between steps — every step but the last pads its body so the spine has length */
80
- .xeno-steptimeline-step:not(:last-child) .xeno-steptimeline-body {
81
- padding-bottom: 20px;
82
- }
83
-
84
- .xeno-steptimeline-label {
85
- flex: 1 1 auto;
86
- font-family: inherit;
87
- font-size: 14px;
88
- line-height: 1.3;
89
- color: var(--xeno-text);
90
- }
91
-
92
- .xeno-steptimeline-step[data-status='pending'] .xeno-steptimeline-label {
93
- color: var(--xeno-muted);
94
- }
95
-
96
- .xeno-steptimeline-step[data-status='active'] .xeno-steptimeline-label {
97
- font-weight: 500;
98
- }
99
-
100
- .xeno-steptimeline-time {
101
- flex: none;
102
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
103
- font-size: 12px;
104
- line-height: 1.3;
105
- font-variant-numeric: tabular-nums;
106
- color: var(--xeno-muted);
107
- }
108
-
109
- /* screen-reader-only status word — the visible mark is decorative */
110
- .xeno-steptimeline-sr {
111
- position: absolute;
112
- width: 1px;
113
- height: 1px;
114
- padding: 0;
115
- margin: -1px;
116
- overflow: hidden;
117
- clip: rect(0 0 0 0);
118
- white-space: nowrap;
119
- border: 0;
120
- }
121
-
122
- @media (prefers-reduced-motion: no-preference) {
123
- @keyframes xeno-steptimeline-breathe {
124
- 0%,
125
- 100% {
126
- transform: scale(1);
127
- opacity: 1;
128
- }
129
- 50% {
130
- transform: scale(0.78);
131
- opacity: 0.6;
132
- }
133
- }
134
- .xeno-steptimeline-step[data-status='active'] .xeno-steptimeline-mark {
135
- animation: xeno-steptimeline-breathe 1.8s var(--xeno-ease) infinite;
136
- }
137
- }
1
+ /*
2
+ * StepTimeline — a vertical run of steps threaded by a hairline spine.
3
+ *
4
+ * CSS-first: paint is driven by `data-status` on each step; the size mark is a rounded SQUARE
5
+ * (radius-xs), never a circle. Monochrome throughout — spine + marks are greys from `xeno-theme.css`.
6
+ * The only motion is the `active` mark's breathing, gated behind `prefers-reduced-motion: no-preference`.
7
+ * No literal colours here.
8
+ */
9
+
10
+ .xeno-steptimeline {
11
+ list-style: none;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ /* Two columns: the rail (fixed mark width) + the body. Rows stack with no gap so the spine of one
17
+ step reaches the mark of the next. */
18
+ .xeno-steptimeline-step {
19
+ display: grid;
20
+ grid-template-columns: 16px 1fr;
21
+ column-gap: 12px;
22
+ }
23
+
24
+ /* ── Rail (mark + spine) — decorative, aria-hidden ───────────────────────────────────── */
25
+ .xeno-steptimeline-rail {
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ /* stretch to the row height so the spine's flex fill spans down to the next mark */
30
+ }
31
+
32
+ .xeno-steptimeline-mark {
33
+ box-sizing: border-box;
34
+ display: inline-flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ flex: none;
38
+ width: 16px;
39
+ height: 16px;
40
+ margin-top: 1px;
41
+ border-radius: var(--xeno-radius-xs);
42
+ }
43
+
44
+ /* pending — an outline square, not yet reached */
45
+ .xeno-steptimeline-step[data-status='pending'] .xeno-steptimeline-mark {
46
+ background: transparent;
47
+ border: 1.5px solid var(--xeno-muted);
48
+ }
49
+
50
+ /* active — a filled square, breathing (see reduced-motion gate below) */
51
+ .xeno-steptimeline-step[data-status='active'] .xeno-steptimeline-mark {
52
+ background: var(--xeno-active);
53
+ }
54
+
55
+ /* done — filled to text colour; the check glyph inherits `--xeno-on-accent` via currentColor */
56
+ .xeno-steptimeline-step[data-status='done'] .xeno-steptimeline-mark {
57
+ background: var(--xeno-text);
58
+ color: var(--xeno-on-accent);
59
+ }
60
+
61
+ /* the connecting spine — a 1.5px rounded border-coloured bar filling to the next mark */
62
+ .xeno-steptimeline-spine {
63
+ flex: 1 1 auto;
64
+ width: 1.5px;
65
+ min-height: 12px;
66
+ margin: 2px 0;
67
+ background: var(--xeno-border);
68
+ border-radius: var(--xeno-radius-hair);
69
+ }
70
+
71
+ /* ── Body (label + optional time) ────────────────────────────────────────────────────── */
72
+ .xeno-steptimeline-body {
73
+ display: flex;
74
+ align-items: baseline;
75
+ gap: 12px;
76
+ min-height: 18px;
77
+ }
78
+
79
+ /* space between steps — every step but the last pads its body so the spine has length */
80
+ .xeno-steptimeline-step:not(:last-child) .xeno-steptimeline-body {
81
+ padding-bottom: 20px;
82
+ }
83
+
84
+ .xeno-steptimeline-label {
85
+ flex: 1 1 auto;
86
+ font-family: inherit;
87
+ font-size: 14px;
88
+ line-height: 1.3;
89
+ color: var(--xeno-text);
90
+ }
91
+
92
+ .xeno-steptimeline-step[data-status='pending'] .xeno-steptimeline-label {
93
+ color: var(--xeno-muted);
94
+ }
95
+
96
+ .xeno-steptimeline-step[data-status='active'] .xeno-steptimeline-label {
97
+ font-weight: 500;
98
+ }
99
+
100
+ .xeno-steptimeline-time {
101
+ flex: none;
102
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
103
+ font-size: 12px;
104
+ line-height: 1.3;
105
+ font-variant-numeric: tabular-nums;
106
+ color: var(--xeno-muted);
107
+ }
108
+
109
+ /* screen-reader-only status word — the visible mark is decorative */
110
+ .xeno-steptimeline-sr {
111
+ position: absolute;
112
+ width: 1px;
113
+ height: 1px;
114
+ padding: 0;
115
+ margin: -1px;
116
+ overflow: hidden;
117
+ clip: rect(0 0 0 0);
118
+ white-space: nowrap;
119
+ border: 0;
120
+ }
121
+
122
+ @media (prefers-reduced-motion: no-preference) {
123
+ @keyframes xeno-steptimeline-breathe {
124
+ 0%,
125
+ 100% {
126
+ transform: scale(1);
127
+ opacity: 1;
128
+ }
129
+ 50% {
130
+ transform: scale(0.78);
131
+ opacity: 0.6;
132
+ }
133
+ }
134
+ .xeno-steptimeline-step[data-status='active'] .xeno-steptimeline-mark {
135
+ animation: xeno-steptimeline-breathe 1.8s var(--xeno-ease) infinite;
136
+ }
137
+ }
@@ -1,75 +1,75 @@
1
- /*
2
- * ThinkingCube — the LOCKED branded model avatar. A single rounded SQUARE that either BREATHES
3
- * (thinking: one continuous rotate + scale loop) or LANDS (settled: a one-shot grow while the fill
4
- * grows opaque canvas → muted). Monochrome shell: muted hairline outline, canvas/muted fill only.
5
- * No circles, no coloured glow. Motion is transform/opacity + background-color; a reduced-motion
6
- * override stills the spin and leaves `settled` as a static filled square.
7
- */
8
-
9
- .xeno-cube {
10
- display: inline-flex;
11
- align-items: center;
12
- justify-content: center;
13
- width: var(--xeno-cube-size, 24px);
14
- height: var(--xeno-cube-size, 24px);
15
- line-height: 0;
16
- flex: none;
17
- }
18
-
19
- .xeno-cube-body {
20
- box-sizing: border-box;
21
- width: 100%;
22
- height: 100%;
23
- border: 1.5px solid var(--xeno-muted);
24
- border-radius: var(--xeno-radius-xs);
25
- background-color: var(--xeno-canvas);
26
- transform-origin: center;
27
- will-change: transform, background-color;
28
- }
29
-
30
- /* thinking — ONE continuous loop: rotate 0→360° while the scale breathes 1 → .52 → 1. */
31
- .xeno-cube[data-state='thinking'] .xeno-cube-body {
32
- background-color: var(--xeno-canvas);
33
- animation: xeno-cube-life 1.5s cubic-bezier(0.45, 0, 0.55, 1) infinite;
34
- }
35
-
36
- /* settled — runs ONCE: grow .8 → 1.14 → 1 while the SAME square fills canvas → muted, then holds. */
37
- .xeno-cube[data-state='settled'] .xeno-cube-body {
38
- background-color: var(--xeno-muted);
39
- animation: xeno-cube-settle 0.52s var(--xeno-ease) both;
40
- }
41
-
42
- @keyframes xeno-cube-life {
43
- 0% {
44
- transform: rotate(0deg) scale(1);
45
- }
46
- 50% {
47
- transform: rotate(180deg) scale(0.52);
48
- }
49
- 100% {
50
- transform: rotate(360deg) scale(1);
51
- }
52
- }
53
-
54
- @keyframes xeno-cube-settle {
55
- 0% {
56
- transform: scale(0.8);
57
- background-color: var(--xeno-canvas);
58
- }
59
- 60% {
60
- transform: scale(1.14);
61
- }
62
- 100% {
63
- transform: scale(1);
64
- background-color: var(--xeno-muted);
65
- }
66
- }
67
-
68
- /* Reduced motion: no spin, no grow. `settled` still rests as its filled muted square (base rule above);
69
- `thinking` rests as a still, muted-outlined canvas square. */
70
- @media (prefers-reduced-motion: reduce) {
71
- .xeno-cube[data-state='thinking'] .xeno-cube-body,
72
- .xeno-cube[data-state='settled'] .xeno-cube-body {
73
- animation: none;
74
- }
75
- }
1
+ /*
2
+ * ThinkingCube — the LOCKED branded model avatar. A single rounded SQUARE that either BREATHES
3
+ * (thinking: one continuous rotate + scale loop) or LANDS (settled: a one-shot grow while the fill
4
+ * grows opaque canvas → muted). Monochrome shell: muted hairline outline, canvas/muted fill only.
5
+ * No circles, no coloured glow. Motion is transform/opacity + background-color; a reduced-motion
6
+ * override stills the spin and leaves `settled` as a static filled square.
7
+ */
8
+
9
+ .xeno-cube {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ width: var(--xeno-cube-size, 24px);
14
+ height: var(--xeno-cube-size, 24px);
15
+ line-height: 0;
16
+ flex: none;
17
+ }
18
+
19
+ .xeno-cube-body {
20
+ box-sizing: border-box;
21
+ width: 100%;
22
+ height: 100%;
23
+ border: 1.5px solid var(--xeno-muted);
24
+ border-radius: var(--xeno-radius-xs);
25
+ background-color: var(--xeno-canvas);
26
+ transform-origin: center;
27
+ will-change: transform, background-color;
28
+ }
29
+
30
+ /* thinking — ONE continuous loop: rotate 0→360° while the scale breathes 1 → .52 → 1. */
31
+ .xeno-cube[data-state='thinking'] .xeno-cube-body {
32
+ background-color: var(--xeno-canvas);
33
+ animation: xeno-cube-life 1.5s cubic-bezier(0.45, 0, 0.55, 1) infinite;
34
+ }
35
+
36
+ /* settled — runs ONCE: grow .8 → 1.14 → 1 while the SAME square fills canvas → muted, then holds. */
37
+ .xeno-cube[data-state='settled'] .xeno-cube-body {
38
+ background-color: var(--xeno-muted);
39
+ animation: xeno-cube-settle 0.52s var(--xeno-ease) both;
40
+ }
41
+
42
+ @keyframes xeno-cube-life {
43
+ 0% {
44
+ transform: rotate(0deg) scale(1);
45
+ }
46
+ 50% {
47
+ transform: rotate(180deg) scale(0.52);
48
+ }
49
+ 100% {
50
+ transform: rotate(360deg) scale(1);
51
+ }
52
+ }
53
+
54
+ @keyframes xeno-cube-settle {
55
+ 0% {
56
+ transform: scale(0.8);
57
+ background-color: var(--xeno-canvas);
58
+ }
59
+ 60% {
60
+ transform: scale(1.14);
61
+ }
62
+ 100% {
63
+ transform: scale(1);
64
+ background-color: var(--xeno-muted);
65
+ }
66
+ }
67
+
68
+ /* Reduced motion: no spin, no grow. `settled` still rests as its filled muted square (base rule above);
69
+ `thinking` rests as a still, muted-outlined canvas square. */
70
+ @media (prefers-reduced-motion: reduce) {
71
+ .xeno-cube[data-state='thinking'] .xeno-cube-body,
72
+ .xeno-cube[data-state='settled'] .xeno-cube-body {
73
+ animation: none;
74
+ }
75
+ }
@@ -1,60 +1,60 @@
1
- /*
2
- * Base presentation for a XENO element. CSS-FIRST, 0 kb JS: an element's discrete state lives in
3
- * `data-<axis>` attributes on the root, and this stylesheet is where any transition attaches — never a
4
- * JavaScript animation runtime (SPEC §6, so elements survive sandboxed iframes and web export).
5
- *
6
- * Geometry variant swaps (bookmark off↔on) are re-rendered from `state` by the renderer. Channel
7
- * transitions declared as BINDINGS (opacity/scale/fill over ms) land here as they are added; the axis
8
- * attributes below are the selectors they will hang off. `currentColor` keeps every glyph monochrome
9
- * and inheriting the surrounding text colour.
10
- */
11
-
12
- .xeno-element {
13
- display: inline-block;
14
- vertical-align: middle;
15
- line-height: 0;
16
- color: inherit;
17
- }
18
-
19
- /* An element the product marks unavailable reads as dimmed everywhere, from one rule. */
20
- .xeno-element[data-availability='disabled'] {
21
- opacity: 0.4;
22
- }
23
- .xeno-element[data-availability='busy'] {
24
- cursor: progress;
25
- }
26
-
27
- @media (prefers-reduced-motion: no-preference) {
28
- .xeno-element,
29
- .xeno-element * {
30
- transition:
31
- opacity 160ms ease,
32
- transform 160ms ease,
33
- fill 160ms ease,
34
- stroke 160ms ease;
35
- }
36
- }
37
-
38
-
39
- /* ── Morphing between geometry variants ───────────────────────────────────────────────
40
- * When a declaration's active variant is interpolable with `base` — same primitive count, same kind at
41
- * every index — the renderer marks the icon `data-morph="on"` and hands each path its own `d` a second
42
- * time, as `--part-d`. That is all the seam needs: `d` is an animatable CSS property, a custom property
43
- * changing is a computed-value change, so the swap becomes a transition and the icon draws itself from
44
- * one shape into the other.
45
- *
46
- * Still no animation runtime. The renderer never animates; it states that an animation is possible and
47
- * the stylesheet decides whether to take it — the same division the rest of this system runs on.
48
- *
49
- * Behind `@supports` because `d` as a CSS property is not universal. Where it is missing, the `d`
50
- * ATTRIBUTE is still correct and still swapped on re-render, so the icon changes instantly instead of
51
- * morphing: the worse animation, never the wrong picture. */
52
- @supports (d: path('M0 0')) {
53
- .xeno-element[data-morph='on'] .xeno-part {
54
- d: var(--part-d);
55
- transition: d var(--xeno-dur-morph, 320ms) var(--xeno-ease-morph, cubic-bezier(0.22, 0.7, 0.2, 1));
56
- }
57
- @media (prefers-reduced-motion: reduce) {
58
- .xeno-element[data-morph='on'] .xeno-part { transition: none; }
59
- }
60
- }
1
+ /*
2
+ * Base presentation for a XENO element. CSS-FIRST, 0 kb JS: an element's discrete state lives in
3
+ * `data-<axis>` attributes on the root, and this stylesheet is where any transition attaches — never a
4
+ * JavaScript animation runtime (SPEC §6, so elements survive sandboxed iframes and web export).
5
+ *
6
+ * Geometry variant swaps (bookmark off↔on) are re-rendered from `state` by the renderer. Channel
7
+ * transitions declared as BINDINGS (opacity/scale/fill over ms) land here as they are added; the axis
8
+ * attributes below are the selectors they will hang off. `currentColor` keeps every glyph monochrome
9
+ * and inheriting the surrounding text colour.
10
+ */
11
+
12
+ .xeno-element {
13
+ display: inline-block;
14
+ vertical-align: middle;
15
+ line-height: 0;
16
+ color: inherit;
17
+ }
18
+
19
+ /* An element the product marks unavailable reads as dimmed everywhere, from one rule. */
20
+ .xeno-element[data-availability='disabled'] {
21
+ opacity: 0.4;
22
+ }
23
+ .xeno-element[data-availability='busy'] {
24
+ cursor: progress;
25
+ }
26
+
27
+ @media (prefers-reduced-motion: no-preference) {
28
+ .xeno-element,
29
+ .xeno-element * {
30
+ transition:
31
+ opacity 160ms ease,
32
+ transform 160ms ease,
33
+ fill 160ms ease,
34
+ stroke 160ms ease;
35
+ }
36
+ }
37
+
38
+
39
+ /* ── Morphing between geometry variants ───────────────────────────────────────────────
40
+ * When a declaration's active variant is interpolable with `base` — same primitive count, same kind at
41
+ * every index — the renderer marks the icon `data-morph="on"` and hands each path its own `d` a second
42
+ * time, as `--part-d`. That is all the seam needs: `d` is an animatable CSS property, a custom property
43
+ * changing is a computed-value change, so the swap becomes a transition and the icon draws itself from
44
+ * one shape into the other.
45
+ *
46
+ * Still no animation runtime. The renderer never animates; it states that an animation is possible and
47
+ * the stylesheet decides whether to take it — the same division the rest of this system runs on.
48
+ *
49
+ * Behind `@supports` because `d` as a CSS property is not universal. Where it is missing, the `d`
50
+ * ATTRIBUTE is still correct and still swapped on re-render, so the icon changes instantly instead of
51
+ * morphing: the worse animation, never the wrong picture. */
52
+ @supports (d: path('M0 0')) {
53
+ .xeno-element[data-morph='on'] .xeno-part {
54
+ d: var(--part-d);
55
+ transition: d var(--xeno-dur-morph, 320ms) var(--xeno-ease-morph, cubic-bezier(0.22, 0.7, 0.2, 1));
56
+ }
57
+ @media (prefers-reduced-motion: reduce) {
58
+ .xeno-element[data-morph='on'] .xeno-part { transition: none; }
59
+ }
60
+ }