@zeyos/zx 4.1.1 → 4.3.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 (102) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/DESIGN-SYSTEM.md +33 -3
  3. package/README.md +22 -5
  4. package/dist/types/components/app-icon/app-icon.d.ts +8 -1
  5. package/dist/types/components/aurora/aurora.d.ts +72 -0
  6. package/dist/types/components/calendar/calendar-model.d.ts +202 -0
  7. package/dist/types/components/calendar/calendar.d.ts +517 -0
  8. package/dist/types/components/card-view/card-view.d.ts +303 -0
  9. package/dist/types/components/card-view/record-card.d.ts +242 -0
  10. package/dist/types/components/dialog/dialog.d.ts +6 -1
  11. package/dist/types/components/dropdown/dropdown.d.ts +5 -0
  12. package/dist/types/components/kanban-view/kanban-view.d.ts +572 -0
  13. package/dist/types/components/launcher/launcher.d.ts +5 -0
  14. package/dist/types/components/message/message.d.ts +9 -1
  15. package/dist/types/components/modal/modal.d.ts +44 -7
  16. package/dist/types/components/sheet/sheet.d.ts +22 -12
  17. package/dist/types/components/table/table.d.ts +120 -31
  18. package/dist/types/components/table-view/table-view.d.ts +192 -0
  19. package/dist/types/components/view/record-view.d.ts +464 -0
  20. package/dist/types/core/overlay-host.d.ts +12 -0
  21. package/dist/types/index.d.ts +7 -0
  22. package/dist/types/zeyos/calendar.d.ts +91 -0
  23. package/dist/types/zeyos/icons.d.ts +18 -2
  24. package/dist/types/zeyos/index.d.ts +4 -0
  25. package/dist/types/zeyos/legacy-saved-views.d.ts +66 -0
  26. package/dist/types/zeyos/saved-views.d.ts +331 -0
  27. package/dist/types/zeyos/view.d.ts +305 -0
  28. package/dist/zx-zeyos.esm.js +4742 -1470
  29. package/dist/zx-zeyos.esm.min.js +1 -1
  30. package/dist/zx.css +2536 -471
  31. package/dist/zx.esm.js +5193 -223
  32. package/dist/zx.esm.min.js +3 -3
  33. package/dist/zx.global.js +5193 -223
  34. package/dist/zx.global.min.js +3 -3
  35. package/dist/zx.min.css +1 -1
  36. package/docs/api.json +1729 -182
  37. package/docs/llms.md +236 -32
  38. package/docs/llms.txt +16 -6
  39. package/package.json +1 -1
  40. package/src/components/account-menu/account-menu.css +1 -1
  41. package/src/components/app-icon/app-icon.css +77 -51
  42. package/src/components/app-icon/app-icon.js +28 -4
  43. package/src/components/app-rail/app-rail.css +12 -3
  44. package/src/components/app-sidebar/app-sidebar.css +11 -2
  45. package/src/components/aurora/aurora.css +143 -0
  46. package/src/components/aurora/aurora.js +146 -0
  47. package/src/components/avatar/avatar.css +1 -1
  48. package/src/components/button/button.css +2 -2
  49. package/src/components/calendar/calendar-model.js +402 -0
  50. package/src/components/calendar/calendar.css +609 -0
  51. package/src/components/calendar/calendar.js +1619 -0
  52. package/src/components/card/card.css +2 -2
  53. package/src/components/card-view/card-view.css +389 -0
  54. package/src/components/card-view/card-view.js +527 -0
  55. package/src/components/card-view/record-card.js +392 -0
  56. package/src/components/context-menu/context-menu.css +4 -4
  57. package/src/components/context-menu/context-menu.js +5 -1
  58. package/src/components/date-picker/date-picker.css +31 -12
  59. package/src/components/datebox/date-range-box.css +17 -2
  60. package/src/components/datebox/datebox.css +18 -3
  61. package/src/components/dialog/dialog.css +4 -3
  62. package/src/components/dialog/dialog.js +2 -1
  63. package/src/components/dropdown/dropdown.css +17 -3
  64. package/src/components/dropdown/dropdown.js +7 -2
  65. package/src/components/kanban-view/kanban-view.css +216 -0
  66. package/src/components/kanban-view/kanban-view.js +1228 -0
  67. package/src/components/launcher/launcher.css +24 -9
  68. package/src/components/launcher/launcher.js +9 -2
  69. package/src/components/master-panel/master-panel.css +14 -3
  70. package/src/components/menu-button/menu-button.css +4 -4
  71. package/src/components/message/message.css +13 -21
  72. package/src/components/message/message.js +17 -11
  73. package/src/components/modal/modal.css +19 -5
  74. package/src/components/modal/modal.js +99 -25
  75. package/src/components/navigation-bar/navigation-bar.css +17 -5
  76. package/src/components/panel/panel.css +10 -2
  77. package/src/components/select/select.css +26 -8
  78. package/src/components/sheet/sheet.css +28 -7
  79. package/src/components/sheet/sheet.js +43 -23
  80. package/src/components/table/table.css +83 -5
  81. package/src/components/table/table.js +322 -37
  82. package/src/components/table-view/table-view.css +18 -0
  83. package/src/components/table-view/table-view.js +347 -0
  84. package/src/components/tag-picker/tag-picker.css +18 -4
  85. package/src/components/tooltip/tooltip.css +17 -3
  86. package/src/components/tooltip/tooltip.js +4 -1
  87. package/src/components/view/record-view.css +114 -0
  88. package/src/components/view/record-view.js +770 -0
  89. package/src/core/overlay-host.js +23 -0
  90. package/src/index.js +15 -0
  91. package/src/zeyos/calendar.js +115 -0
  92. package/src/zeyos/icons.js +11 -4
  93. package/src/zeyos/index.js +13 -0
  94. package/src/zeyos/launcher.js +2 -2
  95. package/src/zeyos/legacy-saved-views.js +155 -0
  96. package/src/zeyos/saved-views.js +815 -0
  97. package/src/zeyos/table.js +3 -1
  98. package/src/zeyos/view.js +543 -0
  99. package/styles/icons.css +3 -10
  100. package/styles/tokens/dark.css +4 -0
  101. package/styles/tokens/semantic.css +142 -3
  102. package/styles/zx.css +6 -0
@@ -2,6 +2,12 @@
2
2
  --zx-app-icon-size: 36px;
3
3
  --zx-app-icon-glyph-size: 52%;
4
4
  --zx-app-icon-color: var(--zx-color-accent);
5
+ --zx-app-icon-core: color-mix(in srgb, var(--zx-app-icon-color) var(--zx-app-icon-core-strength), var(--zx-color-app-icon-shade));
6
+ --zx-app-icon-rim: color-mix(in srgb, var(--zx-app-icon-color) var(--zx-app-icon-rim-strength), var(--zx-app-icon-rim-base));
7
+ --zx-app-icon-bloom: color-mix(in srgb, var(--zx-app-icon-color) var(--zx-app-icon-bloom-strength), transparent);
8
+ --zx-app-icon-specular: color-mix(in srgb, var(--zx-color-glass-highlight-strong) var(--zx-app-icon-specular-strength), transparent);
9
+ --zx-app-icon-halo: 0 0 var(--zx-app-icon-halo-size) color-mix(in srgb, var(--zx-app-icon-color) var(--zx-app-icon-halo-strength), transparent);
10
+ --zx-app-icon-selected-ring: color-mix(in srgb, var(--zx-app-icon-color) 54%, transparent);
5
11
  position: relative;
6
12
  isolation: isolate;
7
13
  display: inline-grid;
@@ -9,63 +15,81 @@
9
15
  flex: none;
10
16
  inline-size: var(--zx-app-icon-size);
11
17
  block-size: var(--zx-app-icon-size);
12
- overflow: hidden;
13
- border: 1px solid var(--zx-color-glass-border);
18
+ overflow: visible;
14
19
  border-radius: max(var(--zx-radius-sm), calc(var(--zx-app-icon-size) * .12));
15
- background: var(--zx-app-icon-color);
16
- box-shadow: var(--zx-app-icon-shadow);
17
20
  color: var(--zx-app-icon-glyph, var(--zx-color-app-icon-glyph));
18
21
  font-size: var(--zx-app-icon-size);
19
22
  vertical-align: middle;
20
- transform: translateZ(0);
21
23
  }
22
24
 
23
- .zx-app-icon[data-glass="subtle"],
24
- .zx-app-icon[data-glass="strong"] {
25
+ .zx-app-icon[data-shape="circle"] {
26
+ border-radius: var(--zx-radius-full);
27
+ }
28
+
29
+ .zx-app-icon__surface {
30
+ position: absolute;
31
+ z-index: 0;
32
+ inset: 0;
33
+ display: grid;
34
+ overflow: hidden;
35
+ place-items: center;
36
+ border: 1px solid var(--zx-app-icon-rim);
37
+ border-radius: inherit;
38
+ background: var(--zx-app-icon-core);
39
+ box-shadow: var(--zx-app-icon-shadow);
40
+ }
41
+
42
+ .zx-app-icon[data-glass="subtle"] .zx-app-icon__surface,
43
+ .zx-app-icon[data-glass="strong"] .zx-app-icon__surface {
25
44
  background:
26
- linear-gradient(145deg, var(--zx-color-glass-highlight-strong), transparent 38%),
27
- linear-gradient(to bottom right, transparent 42%, var(--zx-color-app-icon-depth)),
28
- color-mix(in srgb, var(--zx-app-icon-color) var(--zx-glass-color-strength), transparent);
29
- -webkit-backdrop-filter: blur(calc(var(--zx-glass-blur) * .55)) saturate(var(--zx-glass-saturation));
30
- backdrop-filter: blur(calc(var(--zx-glass-blur) * .55)) saturate(var(--zx-glass-saturation));
45
+ radial-gradient(112% 92% at 50% 122%, var(--zx-app-icon-bloom), transparent 64%),
46
+ var(--zx-app-icon-core);
47
+ -webkit-backdrop-filter: none;
48
+ backdrop-filter: none;
31
49
  }
32
50
 
33
- .zx-app-icon[data-glass="strong"] {
34
- -webkit-backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
35
- backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
51
+ .zx-app-icon[data-glass="strong"] .zx-app-icon__surface {
52
+ box-shadow: var(--zx-app-icon-shadow-hover), var(--zx-app-icon-halo);
36
53
  }
37
54
 
38
- .zx-app-icon[data-glass="none"] {
39
- border-color: var(--zx-color-border);
55
+ .zx-app-icon[data-glass="none"] .zx-app-icon__surface {
56
+ border-color: color-mix(in srgb, var(--zx-app-icon-color) 62%, var(--zx-color-border));
57
+ background: color-mix(in srgb, var(--zx-app-icon-color) 72%, var(--zx-color-app-icon-shade));
40
58
  box-shadow: none;
41
59
  }
42
60
 
43
61
  .zx-app-icon[data-glass="none"] .zx-app-icon__shine { display: none; }
44
- .zx-app-icon[data-glass="none"]::after { display: none; }
62
+ .zx-app-icon[data-glass="none"] .zx-app-icon__surface::after { display: none; }
45
63
 
46
- .zx-app-icon[data-selected] {
47
- outline: 2px solid var(--zx-color-bg-page);
48
- box-shadow: 0 0 0 3px var(--zx-color-ring), var(--zx-overlay-shadow);
64
+ .zx-app-icon[data-selected] .zx-app-icon__surface {
65
+ outline: none;
66
+ box-shadow: 0 0 0 1px var(--zx-color-bg-page),
67
+ 0 0 0 3px var(--zx-app-icon-selected-ring),
68
+ var(--zx-app-icon-shadow-hover),
69
+ var(--zx-app-icon-halo);
49
70
  }
50
71
 
51
72
  .zx-app-icon__shine {
52
73
  position: absolute;
53
74
  z-index: 1;
54
- inset: 1px 13% auto;
55
- block-size: 38%;
56
- border-radius: 999px;
57
- background: linear-gradient(to bottom, var(--zx-color-glass-highlight-strong), transparent);
58
- filter: blur(1px);
75
+ inset: 2px 18% auto;
76
+ block-size: 1px;
77
+ border-radius: var(--zx-radius-full);
78
+ background: linear-gradient(90deg, transparent, var(--zx-app-icon-specular), transparent);
79
+ opacity: .72;
59
80
  pointer-events: none;
60
81
  }
61
82
 
62
- .zx-app-icon::after {
83
+ .zx-app-icon[data-glass="strong"] .zx-app-icon__shine { opacity: .9; }
84
+
85
+ .zx-app-icon__surface::after {
63
86
  content: "";
64
87
  position: absolute;
65
88
  z-index: 1;
66
89
  inset: 0;
67
90
  border-radius: inherit;
68
- box-shadow: inset 0 0 0 1px var(--zx-color-glass-highlight);
91
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--zx-color-glass-highlight) 56%, transparent);
92
+ opacity: .56;
69
93
  pointer-events: none;
70
94
  }
71
95
 
@@ -82,7 +106,7 @@
82
106
  padding: 0;
83
107
  font-size: var(--zx-app-icon-glyph-size);
84
108
  line-height: 1;
85
- filter: drop-shadow(var(--zx-glass-glyph-shadow));
109
+ filter: none;
86
110
  transform: translate(-50%, -50%);
87
111
  }
88
112
 
@@ -117,8 +141,8 @@
117
141
  .zx-app-icon__badge {
118
142
  position: absolute;
119
143
  z-index: 3;
120
- inset-block-end: 1px;
121
- inset-inline-end: 1px;
144
+ inset-block-start: -3px;
145
+ inset-inline-end: -3px;
122
146
  display: grid;
123
147
  place-items: center;
124
148
  min-inline-size: 14px;
@@ -133,47 +157,43 @@
133
157
  line-height: 1;
134
158
  }
135
159
 
136
- .zx-app-icon:hover {
137
- border-color: var(--zx-color-glass-highlight-strong);
138
- box-shadow: var(--zx-app-icon-shadow-hover);
139
- filter: brightness(1.06) saturate(1.08);
160
+ .zx-app-icon:is(a, button, [role="button"]):not(:disabled):not([aria-disabled="true"]):hover .zx-app-icon__surface,
161
+ :is(a, button, [role="button"]):not(:disabled):not([aria-disabled="true"]):hover .zx-app-icon__surface {
162
+ border-color: color-mix(in srgb, var(--zx-app-icon-color) 76%, var(--zx-color-glass-highlight-strong));
163
+ box-shadow: var(--zx-app-icon-shadow-hover), var(--zx-app-icon-halo);
164
+ filter: brightness(1.06) saturate(1.04);
140
165
  }
141
166
 
142
- .zx-app-icon:focus-visible,
143
- :is(a, button, [role="button"]):focus-visible > .zx-app-icon {
167
+ .zx-app-icon:is(a, button, [role="button"]):not(:disabled):not([aria-disabled="true"]):focus-visible .zx-app-icon__surface,
168
+ :is(a, button, [role="button"]):not(:disabled):not([aria-disabled="true"]):focus-visible .zx-app-icon__surface {
144
169
  outline: none;
145
- box-shadow: var(--zx-focus-ring), var(--zx-app-icon-shadow-hover);
170
+ box-shadow: var(--zx-focus-ring), var(--zx-app-icon-shadow-hover), var(--zx-app-icon-halo);
146
171
  }
147
172
 
148
173
  @media (prefers-reduced-motion: no-preference) {
149
- .zx-app-icon {
174
+ .zx-app-icon__surface {
150
175
  transition: border-color var(--zx-dur-fast) var(--zx-ease),
151
176
  box-shadow var(--zx-dur-fast) var(--zx-ease),
152
- filter var(--zx-dur-fast) var(--zx-ease),
153
- transform var(--zx-dur-fast) var(--zx-ease);
154
- }
155
-
156
- .zx-app-icon:hover {
157
- transform: translateY(-2px) scale(1.035);
177
+ filter var(--zx-dur-fast) var(--zx-ease);
158
178
  }
159
179
  }
160
180
 
161
181
  @media (prefers-reduced-transparency: reduce) {
162
- .zx-app-icon[data-glass="strong"] {
163
- background: var(--zx-app-icon-color);
182
+ .zx-app-icon[data-glass="strong"] .zx-app-icon__surface {
183
+ background: var(--zx-app-icon-core);
164
184
  -webkit-backdrop-filter: none;
165
185
  backdrop-filter: none;
166
186
  }
167
187
 
168
- .zx-app-icon[data-glass="subtle"] {
169
- background: var(--zx-app-icon-color);
188
+ .zx-app-icon[data-glass="subtle"] .zx-app-icon__surface {
189
+ background: var(--zx-app-icon-core);
170
190
  -webkit-backdrop-filter: none;
171
191
  backdrop-filter: none;
172
192
  }
173
193
  }
174
194
 
175
195
  @media (forced-colors: active) {
176
- .zx-app-icon {
196
+ .zx-app-icon__surface {
177
197
  border-color: ButtonText;
178
198
  background: ButtonFace;
179
199
  color: ButtonText;
@@ -184,6 +204,12 @@
184
204
  backdrop-filter: none;
185
205
  }
186
206
 
187
- .zx-app-icon::after,
207
+ .zx-app-icon__surface::after,
188
208
  .zx-app-icon__shine { display: none; }
209
+
210
+ .zx-app-icon__badge {
211
+ border-color: Canvas;
212
+ background: Highlight;
213
+ color: HighlightText;
214
+ }
189
215
  }
@@ -12,12 +12,13 @@ import { isCssColor } from '../../core/util.js';
12
12
  * @property {string|null} [label=null] Accessible image label; null makes the icon decorative.
13
13
  * @property {string|number|null} [badge=null] Optional corner badge.
14
14
  * @property {boolean} [selected=false] Whether the icon represents the active application.
15
+ * @property {'tile'|'circle'} [shape='tile'] Identity vessel geometry.
15
16
  * @property {false|'subtle'|'strong'} [glass='subtle'] CSS-first glass treatment.
16
17
  * @property {string|string[]} [class] Additional class names.
17
18
  */
18
19
 
19
20
  /**
20
- * Stable application-identity tile with progressive CSS glass treatment.
21
+ * Stable application-identity surface with progressive CSS glass treatment.
21
22
  * @extends {Component<AppIconOptions>}
22
23
  */
23
24
  export class AppIcon extends Component {
@@ -32,6 +33,7 @@ export class AppIcon extends Component {
32
33
  label: null,
33
34
  badge: null,
34
35
  selected: false,
36
+ shape: 'tile',
35
37
  glass: 'subtle',
36
38
  class: []
37
39
  };
@@ -42,6 +44,7 @@ export class AppIcon extends Component {
42
44
  const root = /** @type {HTMLElement} */ (this.el ?? h('span'));
43
45
  this.el = root;
44
46
  this._snapshot = this._createdRoot ? null : snapshotTarget(root);
47
+ this._interactiveRoot = root.matches('a[href], button, input, select, textarea, [role="button"], [role="link"]');
45
48
  this._state = { ...this.options };
46
49
  this._appliedClasses = [];
47
50
  this._sync();
@@ -78,10 +81,20 @@ export class AppIcon extends Component {
78
81
  if (isCssColor(state.color)) this.el.style.setProperty('--zx-app-icon-color', String(state.color));
79
82
  else this.el.style.removeProperty('--zx-app-icon-color');
80
83
  this.el.dataset.glass = normalizeGlass(state.glass);
84
+ this.el.dataset.shape = normalizeShape(state.shape);
81
85
  this.el.toggleAttribute('data-selected', Boolean(state.selected));
82
86
 
83
87
  const label = state.label == null ? '' : String(state.label).trim();
84
- if (label) {
88
+ if (this._interactiveRoot) {
89
+ const originalRole = originalAttribute(this._snapshot, 'role');
90
+ const originalLabel = originalAttribute(this._snapshot, 'aria-label');
91
+ if (originalRole === null) this.el.removeAttribute('role');
92
+ else this.el.setAttribute('role', originalRole);
93
+ if (label) this.el.setAttribute('aria-label', label);
94
+ else if (originalLabel === null) this.el.removeAttribute('aria-label');
95
+ else this.el.setAttribute('aria-label', originalLabel);
96
+ this.el.removeAttribute('aria-hidden');
97
+ } else if (label) {
85
98
  this.el.setAttribute('role', 'img');
86
99
  this.el.setAttribute('aria-label', label);
87
100
  this.el.removeAttribute('aria-hidden');
@@ -95,10 +108,11 @@ export class AppIcon extends Component {
95
108
  const badge = state.badge == null || state.badge === '' ? null : h('span', {
96
109
  class: 'zx-app-icon__badge', ariaHidden: 'true'
97
110
  }, String(state.badge));
98
- const children = [
111
+ const surface = h('span', { class: 'zx-app-icon__surface', ariaHidden: 'true' },
99
112
  h('span', { class: 'zx-app-icon__shine', ariaHidden: 'true' }),
100
113
  h('span', { class: 'zx-app-icon__glyph', ariaHidden: 'true' }, visual)
101
- ];
114
+ );
115
+ const children = [surface];
102
116
  // Native replaceChildren() stringifies nullish arguments. Only append a badge when it exists,
103
117
  // otherwise the icon gets a visible "null" text node that can also disturb optical centring.
104
118
  if (badge) children.push(badge);
@@ -135,3 +149,13 @@ function normalizeGlass(value) {
135
149
  if (value === false || value === 'none') return 'none';
136
150
  return value === 'strong' ? 'strong' : 'subtle';
137
151
  }
152
+
153
+ /** @param {unknown} value @returns {'tile'|'circle'} */
154
+ function normalizeShape(value) {
155
+ return value === 'circle' ? 'circle' : 'tile';
156
+ }
157
+
158
+ /** @param {{attributes: [string, string][]}|null} snapshot @param {string} name @returns {string|null} */
159
+ function originalAttribute(snapshot, name) {
160
+ return snapshot?.attributes.find(([attribute]) => attribute === name)?.[1] ?? null;
161
+ }
@@ -1,8 +1,9 @@
1
1
  .zx-app-rail {
2
2
  display: flex;
3
3
  min-inline-size: 0;
4
- border: 1px solid var(--zx-color-border);
5
- background: var(--zx-color-bg-raised);
4
+ border: 1px solid var(--zx-color-glass-border);
5
+ background: var(--zx-color-glass-chrome);
6
+ box-shadow: var(--zx-glass-chrome-shadow);
6
7
  color: var(--zx-color-text);
7
8
  }
8
9
 
@@ -100,8 +101,16 @@
100
101
  }
101
102
  .zx-app-rail__flyout-item:hover,
102
103
  .zx-app-rail__flyout-item:focus-visible,
103
- .zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-bg-hover); color: var(--zx-color-accent); }
104
+ .zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-overlay-hover); color: var(--zx-color-accent); }
104
105
  .zx-app-rail__flyout-item-icon { display: inline-flex; grid-column: 1; }
105
106
  .zx-app-rail__flyout-item-label { grid-column: 2; }
106
107
  .zx-app-rail__flyout-item-badge { grid-column: 3; color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
107
108
  .zx-app-rail__flyout-item-disclosure { display: inline-flex; grid-column: 4; color: var(--zx-color-text-muted); }
109
+
110
+ @media (prefers-reduced-transparency: reduce) {
111
+ .zx-app-rail {
112
+ border-color: var(--zx-color-border);
113
+ background: var(--zx-color-bg-surface);
114
+ box-shadow: none;
115
+ }
116
+ }
@@ -1,8 +1,9 @@
1
1
  .zx-app-sidebar {
2
2
  display: flex;
3
3
  min-block-size: 0;
4
- border: 1px solid var(--zx-color-border);
5
- background: var(--zx-color-bg-raised);
4
+ border: 1px solid var(--zx-color-glass-border);
5
+ background: var(--zx-color-glass-chrome);
6
+ box-shadow: var(--zx-glass-chrome-shadow);
6
7
  color: var(--zx-color-text);
7
8
  }
8
9
 
@@ -78,3 +79,11 @@
78
79
  }
79
80
  .zx-app-sidebar__footer[hidden] { display: none; }
80
81
  .zx-app-sidebar__rail-header { display: grid; gap: var(--zx-space-1); }
82
+
83
+ @media (prefers-reduced-transparency: reduce) {
84
+ .zx-app-sidebar {
85
+ border-color: var(--zx-color-border);
86
+ background: var(--zx-color-bg-surface);
87
+ box-shadow: none;
88
+ }
89
+ }
@@ -0,0 +1,143 @@
1
+ .zx-aurora {
2
+ --zx-aurora-color-1: var(--zx-color-accent);
3
+ --zx-aurora-color-2: var(--zx-color-info);
4
+ --zx-aurora-color-3: var(--zx-color-success);
5
+ --zx-aurora-color-4: var(--zx-color-warning);
6
+ --zx-aurora-core: 7%;
7
+ --zx-aurora-wash: 4%;
8
+ --zx-aurora-faint: 2%;
9
+ --zx-aurora-core-1: color-mix(in srgb, var(--zx-aurora-color-1) var(--zx-aurora-core), transparent);
10
+ --zx-aurora-core-2: color-mix(in srgb, var(--zx-aurora-color-2) var(--zx-aurora-core), transparent);
11
+ --zx-aurora-core-3: color-mix(in srgb, var(--zx-aurora-color-3) var(--zx-aurora-core), transparent);
12
+ --zx-aurora-core-4: color-mix(in srgb, var(--zx-aurora-color-4) var(--zx-aurora-core), transparent);
13
+ --zx-aurora-wash-1: color-mix(in srgb, var(--zx-aurora-color-1) var(--zx-aurora-wash), transparent);
14
+ --zx-aurora-wash-2: color-mix(in srgb, var(--zx-aurora-color-2) var(--zx-aurora-wash), transparent);
15
+ --zx-aurora-wash-3: color-mix(in srgb, var(--zx-aurora-color-3) var(--zx-aurora-wash), transparent);
16
+ --zx-aurora-wash-4: color-mix(in srgb, var(--zx-aurora-color-4) var(--zx-aurora-wash), transparent);
17
+ --zx-aurora-faint-1: color-mix(in srgb, var(--zx-aurora-color-1) var(--zx-aurora-faint), transparent);
18
+ --zx-aurora-faint-2: color-mix(in srgb, var(--zx-aurora-color-2) var(--zx-aurora-faint), transparent);
19
+ --zx-aurora-faint-3: color-mix(in srgb, var(--zx-aurora-color-3) var(--zx-aurora-faint), transparent);
20
+ --zx-aurora-faint-4: color-mix(in srgb, var(--zx-aurora-color-4) var(--zx-aurora-faint), transparent);
21
+ position: relative;
22
+ isolation: isolate;
23
+ }
24
+
25
+ .zx-aurora[data-intensity="balanced"] {
26
+ --zx-aurora-core: 11%;
27
+ --zx-aurora-wash: 6.5%;
28
+ --zx-aurora-faint: 3%;
29
+ }
30
+
31
+ .zx-aurora[data-intensity="vivid"] {
32
+ --zx-aurora-core: 16%;
33
+ --zx-aurora-wash: 9%;
34
+ --zx-aurora-faint: 4.5%;
35
+ }
36
+
37
+ :is([data-zx-theme="dark"] .zx-aurora, .zx-aurora[data-zx-theme="dark"]) {
38
+ --zx-aurora-core: 12%;
39
+ --zx-aurora-wash: 7%;
40
+ --zx-aurora-faint: 3%;
41
+ }
42
+
43
+ :is([data-zx-theme="dark"] .zx-aurora, .zx-aurora[data-zx-theme="dark"])[data-intensity="balanced"] {
44
+ --zx-aurora-core: 17%;
45
+ --zx-aurora-wash: 10%;
46
+ --zx-aurora-faint: 5%;
47
+ }
48
+
49
+ :is([data-zx-theme="dark"] .zx-aurora, .zx-aurora[data-zx-theme="dark"])[data-intensity="vivid"] {
50
+ --zx-aurora-core: 24%;
51
+ --zx-aurora-wash: 14%;
52
+ --zx-aurora-faint: 7%;
53
+ }
54
+
55
+ .zx-aurora::before {
56
+ content: "";
57
+ position: absolute;
58
+ z-index: -1;
59
+ inset: 0;
60
+ border-radius: inherit;
61
+ background-repeat: no-repeat;
62
+ pointer-events: none;
63
+ }
64
+
65
+ .zx-aurora[data-preset="source"]::before {
66
+ background-image:
67
+ radial-gradient(94% 78% at 0% 0%, var(--zx-aurora-core-1) 0%, var(--zx-aurora-wash-1) 34%, transparent 72%),
68
+ radial-gradient(68% 54% at 72% 0%, var(--zx-aurora-wash-2) 0%, var(--zx-aurora-faint-2) 42%, transparent 74%),
69
+ radial-gradient(62% 74% at 100% 82%, var(--zx-aurora-wash-3) 0%, transparent 68%),
70
+ radial-gradient(58% 42% at 38% 100%, var(--zx-aurora-faint-4) 0%, transparent 72%);
71
+ }
72
+
73
+ .zx-aurora[data-preset="confluence"]::before {
74
+ background-image:
75
+ radial-gradient(108% 84% at 0% 0%, var(--zx-aurora-core-1) 0%, var(--zx-aurora-wash-1) 34%, transparent 70%),
76
+ radial-gradient(92% 72% at 100% 0%, var(--zx-aurora-core-2) 0%, var(--zx-aurora-wash-2) 36%, transparent 72%),
77
+ radial-gradient(78% 62% at 0% 100%, var(--zx-aurora-wash-3) 0%, transparent 70%),
78
+ radial-gradient(72% 64% at 100% 100%, var(--zx-aurora-wash-4) 0%, transparent 72%);
79
+ }
80
+
81
+ .zx-aurora[data-preset="horizon"]::before {
82
+ background-image:
83
+ radial-gradient(72% 28% at 8% 19%, var(--zx-aurora-core-1) 0%, var(--zx-aurora-wash-1) 48%, transparent 78%),
84
+ radial-gradient(68% 24% at 42% 23%, var(--zx-aurora-core-2) 0%, var(--zx-aurora-wash-2) 44%, transparent 78%),
85
+ radial-gradient(72% 26% at 76% 18%, var(--zx-aurora-core-3) 0%, var(--zx-aurora-wash-3) 46%, transparent 80%),
86
+ radial-gradient(54% 22% at 100% 26%, var(--zx-aurora-wash-4) 0%, transparent 78%);
87
+ }
88
+
89
+ .zx-aurora[data-preset="diagonal"]::before {
90
+ background-image:
91
+ linear-gradient(126deg, var(--zx-aurora-core-1) 0%, var(--zx-aurora-wash-1) 18%, transparent 44%),
92
+ linear-gradient(126deg, transparent 18%, var(--zx-aurora-wash-2) 42%, transparent 63%),
93
+ linear-gradient(126deg, transparent 44%, var(--zx-aurora-wash-3) 67%, transparent 84%),
94
+ linear-gradient(126deg, transparent 68%, var(--zx-aurora-wash-4) 90%, transparent 100%);
95
+ }
96
+
97
+ .zx-aurora[data-preset="edge"]::before {
98
+ background-image:
99
+ radial-gradient(38% 108% at 0% 18%, var(--zx-aurora-core-1) 0%, var(--zx-aurora-faint-1) 52%, transparent 78%),
100
+ radial-gradient(72% 34% at 72% 0%, var(--zx-aurora-wash-2) 0%, transparent 74%),
101
+ radial-gradient(38% 106% at 100% 84%, var(--zx-aurora-wash-3) 0%, transparent 74%),
102
+ radial-gradient(76% 32% at 32% 100%, var(--zx-aurora-wash-4) 0%, transparent 76%);
103
+ }
104
+
105
+ .zx-aurora[data-preset="curtain"]::before {
106
+ background-image:
107
+ radial-gradient(28% 112% at 10% 0%, var(--zx-aurora-core-1) 0%, var(--zx-aurora-faint-1) 52%, transparent 78%),
108
+ radial-gradient(26% 104% at 38% 0%, var(--zx-aurora-core-2) 0%, var(--zx-aurora-faint-2) 48%, transparent 76%),
109
+ radial-gradient(28% 110% at 67% 0%, var(--zx-aurora-core-3) 0%, var(--zx-aurora-faint-3) 50%, transparent 78%),
110
+ radial-gradient(26% 106% at 92% 0%, var(--zx-aurora-core-4) 0%, var(--zx-aurora-faint-4) 48%, transparent 76%);
111
+ }
112
+
113
+ @media (prefers-color-scheme: dark) {
114
+ :is([data-zx-theme="auto"] .zx-aurora, .zx-aurora[data-zx-theme="auto"]) {
115
+ --zx-aurora-core: 12%;
116
+ --zx-aurora-wash: 7%;
117
+ --zx-aurora-faint: 3%;
118
+ }
119
+
120
+ :is([data-zx-theme="auto"] .zx-aurora, .zx-aurora[data-zx-theme="auto"])[data-intensity="balanced"] {
121
+ --zx-aurora-core: 17%;
122
+ --zx-aurora-wash: 10%;
123
+ --zx-aurora-faint: 5%;
124
+ }
125
+
126
+ :is([data-zx-theme="auto"] .zx-aurora, .zx-aurora[data-zx-theme="auto"])[data-intensity="vivid"] {
127
+ --zx-aurora-core: 24%;
128
+ --zx-aurora-wash: 14%;
129
+ --zx-aurora-faint: 7%;
130
+ }
131
+ }
132
+
133
+ @media (prefers-reduced-transparency: reduce), (forced-colors: active) {
134
+ .zx-aurora::before {
135
+ display: none;
136
+ }
137
+ }
138
+
139
+ @media (prefers-contrast: more) {
140
+ .zx-aurora::before {
141
+ opacity: .35;
142
+ }
143
+ }
@@ -0,0 +1,146 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
+ import { isCssColor } from '../../core/util.js';
4
+
5
+ const PRESETS = Object.freeze(['source', 'confluence', 'horizon', 'diagonal', 'edge', 'curtain']);
6
+ const INTENSITIES = Object.freeze(['subtle', 'balanced', 'vivid']);
7
+ const COLOR_PROPERTIES = Object.freeze([
8
+ '--zx-aurora-color-1', '--zx-aurora-color-2',
9
+ '--zx-aurora-color-3', '--zx-aurora-color-4'
10
+ ]);
11
+
12
+ /**
13
+ * @typedef {Object} AuroraOptions
14
+ * @property {'source'|'confluence'|'horizon'|'diagonal'|'edge'|'curtain'} [preset='source']
15
+ * Geometry of the ambient light field.
16
+ * @property {string[]} [colors=[]] Zero to four concrete CSS colours. An empty list uses semantic
17
+ * Zx colours; shorter palettes repeat across the four gradient fields.
18
+ * @property {'subtle'|'balanced'|'vivid'} [intensity='subtle'] Strength of the light field.
19
+ */
20
+
21
+ /**
22
+ * Decorative multicolour ambient light for an existing application surface.
23
+ * Aurora owns no application content or interaction: it enhances the target in place and renders
24
+ * its pointer-transparent light field behind the target's existing children.
25
+ * @extends {Component<AuroraOptions>}
26
+ */
27
+ export class Aurora extends Component {
28
+ static cssName = 'aurora';
29
+
30
+ /** @type {Readonly<AuroraOptions>} */
31
+ static defaults = {
32
+ preset: 'source',
33
+ colors: [],
34
+ intensity: 'subtle'
35
+ };
36
+
37
+ /**
38
+ * Creates or enhances an Aurora surface.
39
+ * @param {Element|string|null} [target=null] Existing surface, selector, or null.
40
+ * @param {AuroraOptions} [options={}] Aurora options.
41
+ */
42
+ constructor(target = null, options = {}) {
43
+ super(target, options);
44
+ }
45
+
46
+ /** @returns {HTMLElement} */
47
+ render() {
48
+ const created = !this.el;
49
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
50
+ this.el = root;
51
+ this._createdRoot = created;
52
+ this._original = created ? null : snapshotTarget(root);
53
+ this._cleaned = false;
54
+ this._initialColors = COLOR_PROPERTIES.map((property) => ({
55
+ property,
56
+ value: root.style.getPropertyValue(property),
57
+ priority: root.style.getPropertyPriority(property)
58
+ }));
59
+ this._colors = [];
60
+
61
+ this.setPreset(this.options.preset);
62
+ this.setIntensity(this.options.intensity);
63
+ this.setColors(this.options.colors);
64
+ return root;
65
+ }
66
+
67
+ /** Sets the light-field geometry without replacing surface content. @param {string} preset @returns {this} */
68
+ setPreset(preset) {
69
+ this.el.dataset.preset = normalizeAuroraPreset(preset);
70
+ return this;
71
+ }
72
+
73
+ /** Sets zero to four concrete colours without replacing surface content. @param {string[]|null} colors @returns {this} */
74
+ setColors(colors) {
75
+ const palette = normalizeAuroraColors(colors);
76
+ const fields = expandAuroraColors(palette);
77
+ this._colors = [...palette];
78
+ this.el.dataset.colors = palette.length === 0 ? 'semantic' : String(palette.length);
79
+
80
+ if (fields.length === 0) {
81
+ for (const initial of this._initialColors) {
82
+ if (initial.value) this.el.style.setProperty(initial.property, initial.value, initial.priority);
83
+ else this.el.style.removeProperty(initial.property);
84
+ }
85
+ return this;
86
+ }
87
+ for (let index = 0; index < COLOR_PROPERTIES.length; index += 1) {
88
+ this.el.style.setProperty(COLOR_PROPERTIES[index], fields[index]);
89
+ }
90
+ return this;
91
+ }
92
+
93
+ /** Sets the light-field strength without replacing surface content. @param {string} intensity @returns {this} */
94
+ setIntensity(intensity) {
95
+ this.el.dataset.intensity = normalizeAuroraIntensity(intensity);
96
+ return this;
97
+ }
98
+
99
+ /** Restores an enhanced target exactly, or removes an owned surface. @returns {void} */
100
+ destroy() {
101
+ if (this._cleaned) return;
102
+ this._cleaned = true;
103
+ super.destroy();
104
+ if (!this._createdRoot) restoreTarget(this.el, this._original);
105
+ }
106
+ }
107
+
108
+ /** @param {unknown} value @returns {'source'|'confluence'|'horizon'|'diagonal'|'edge'|'curtain'} */
109
+ export function normalizeAuroraPreset(value) {
110
+ const preset = String(value ?? '').trim().toLowerCase();
111
+ if (!PRESETS.includes(preset)) throw new TypeError(`Unknown Aurora preset: ${value}`);
112
+ return /** @type {'source'|'confluence'|'horizon'|'diagonal'|'edge'|'curtain'} */ (preset);
113
+ }
114
+
115
+ /** @param {unknown} value @returns {'subtle'|'balanced'|'vivid'} */
116
+ export function normalizeAuroraIntensity(value) {
117
+ const intensity = String(value ?? '').trim().toLowerCase();
118
+ if (!INTENSITIES.includes(intensity)) throw new TypeError(`Unknown Aurora intensity: ${value}`);
119
+ return /** @type {'subtle'|'balanced'|'vivid'} */ (intensity);
120
+ }
121
+
122
+ /** @param {unknown} value @returns {string[]} */
123
+ export function normalizeAuroraColors(value) {
124
+ if (value == null) return [];
125
+ if (!Array.isArray(value)) throw new TypeError('Aurora colors must be an array');
126
+ if (value.length > COLOR_PROPERTIES.length) {
127
+ throw new TypeError('Aurora accepts at most four colors');
128
+ }
129
+ return value.map((color) => {
130
+ if (!isCssColor(color)) throw new TypeError(`Invalid Aurora color: ${color}`);
131
+ return color.trim();
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Repeats a one-to-three-colour palette deterministically across four gradient fields.
137
+ * @param {string[]} colors
138
+ * @returns {string[]}
139
+ */
140
+ export function expandAuroraColors(colors) {
141
+ if (colors.length === 0) return [];
142
+ if (colors.length === 1) return [colors[0], colors[0], colors[0], colors[0]];
143
+ if (colors.length === 2) return [colors[0], colors[1], colors[0], colors[1]];
144
+ if (colors.length === 3) return [colors[0], colors[1], colors[2], colors[1]];
145
+ return [...colors];
146
+ }
@@ -39,7 +39,7 @@
39
39
  display: grid;
40
40
  place-items: center;
41
41
  border: 1px solid var(--zx-color-border);
42
- background: var(--zx-color-bg-subtle);
42
+ background: var(--zx-color-bg-muted);
43
43
  font-size: calc(var(--zx-avatar-size) * .34);
44
44
  font-weight: 700;
45
45
  line-height: 1;
@@ -14,8 +14,8 @@
14
14
  font-weight: var(--zx-control-font-weight);
15
15
  line-height: 1;
16
16
  cursor: pointer;
17
- -webkit-backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
18
- backdrop-filter: blur(var(--zx-glass-blur)) saturate(var(--zx-glass-saturation));
17
+ -webkit-backdrop-filter: var(--zx-glass-filter-control);
18
+ backdrop-filter: var(--zx-glass-filter-control);
19
19
  box-shadow: var(--zx-glass-control-shadow);
20
20
  transition: color var(--zx-dur-fast) var(--zx-ease),
21
21
  background-color var(--zx-dur-fast) var(--zx-ease),