@cueplusplus/ui 0.18.2 → 0.20.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/CHANGELOG.md +100 -0
- package/dist/brand/_motif-seed.js +20 -0
- package/dist/brand/gloss-object.d.ts +23 -0
- package/dist/brand/gloss-object.js +108 -0
- package/dist/brand/halftone.d.ts +23 -0
- package/dist/brand/halftone.js +54 -0
- package/dist/brand/index.d.ts +8 -1
- package/dist/brand/motif.d.ts +32 -0
- package/dist/brand/motif.js +71 -0
- package/dist/brand/particle-logo.d.ts +50 -0
- package/dist/brand/particle-logo.js +437 -0
- package/dist/brand/particle-mark.d.ts +29 -0
- package/dist/brand/particle-mark.js +137 -0
- package/dist/brand/ribbon.d.ts +20 -0
- package/dist/brand/ribbon.js +54 -0
- package/dist/brand/text-scramble.d.ts +42 -0
- package/dist/brand/text-scramble.js +160 -0
- package/dist/chat/delegation-card.js +2 -1
- package/dist/chat/message.js +12 -13
- package/dist/chrome/announcement-bar.d.ts +30 -0
- package/dist/chrome/announcement-bar.js +38 -0
- package/dist/chrome/app-bar.d.ts +4 -1
- package/dist/chrome/app-bar.js +7 -2
- package/dist/chrome/app-shell.js +8 -1
- package/dist/chrome/band.d.ts +3 -1
- package/dist/chrome/band.js +4 -3
- package/dist/chrome/hero.d.ts +61 -0
- package/dist/chrome/hero.js +108 -0
- package/dist/chrome/index.d.ts +6 -1
- package/dist/chrome/index.js +6 -1
- package/dist/chrome/keyword.d.ts +22 -0
- package/dist/chrome/keyword.js +28 -0
- package/dist/chrome/logo-strip.d.ts +23 -0
- package/dist/chrome/logo-strip.js +43 -0
- package/dist/chrome/page-shell.js +13 -1
- package/dist/chrome/stat-band.d.ts +32 -0
- package/dist/chrome/stat-band.js +39 -0
- package/dist/color/color-area.js +2 -1
- package/dist/dmx/channel-matrix.js +5 -3
- package/dist/dmx/dmx-strip.js +5 -3
- package/dist/elements/animated-number.js +4 -1
- package/dist/elements/replay.d.ts +1 -1
- package/dist/elements/replay.js +5 -2
- package/dist/elements/scroll-anchor.js +4 -1
- package/dist/forms/_chassis.d.ts +1 -1
- package/dist/forms/_chassis.js +2 -2
- package/dist/forms/env-var-input.js +2 -1
- package/dist/forms/file-upload.js +2 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +18 -2
- package/dist/instruments/data-table.js +3 -2
- package/dist/instruments/index.js +1 -1
- package/dist/instruments/stat.js +1 -1
- package/dist/instruments/table.js +1 -1
- package/dist/instruments/terminal-frame.js +5 -2
- package/dist/layout/grid.js +4 -2
- package/dist/layout/index.d.ts +2 -1
- package/dist/layout/index.js +2 -1
- package/dist/layout/preview.js +4 -188
- package/dist/layout/reveal.d.ts +44 -0
- package/dist/layout/reveal.js +106 -0
- package/dist/layout/sidebar.js +88 -52
- package/dist/lib/cn.js +23 -5
- package/dist/lib/particle-field-shaders.js +236 -0
- package/dist/lib/particle-field.js +210 -0
- package/dist/midi/spectrum-visualizer.js +2 -1
- package/dist/overlays/_surface.d.ts +2 -2
- package/dist/primitives/button.js +22 -3
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/primitives/chip.js +63 -6
- package/dist/styles.css +26 -0
- package/dist/system/_scoped-pairing.js +18 -0
- package/dist/system/_stage-scope.js +199 -0
- package/dist/system/_style-check.js +1 -1
- package/dist/system/_style-chip.js +1 -1
- package/dist/system/_style-field-chassis.js +14 -1
- package/dist/system/_style-group-label.js +7 -3
- package/dist/system/_style-hero-title.js +41 -0
- package/dist/system/_style-kbd.js +1 -1
- package/dist/system/_style-label-ink.js +12 -5
- package/dist/system/_style-menu.js +1 -1
- package/dist/system/_style-page-title.js +1 -1
- package/dist/system/_style-segmented-control.js +1 -1
- package/dist/system/_style-skeleton.js +1 -1
- package/dist/system/_style-tab.js +1 -1
- package/dist/system/font-scope.d.ts +38 -0
- package/dist/system/font-scope.js +69 -0
- package/dist/system/frame-clock.d.ts +33 -0
- package/dist/system/frame-clock.js +55 -0
- package/dist/system/index.d.ts +4 -1
- package/dist/system/index.js +4 -1
- package/dist/system/portal.js +7 -1
- package/dist/system/still.d.ts +32 -0
- package/dist/system/still.js +60 -0
- package/manifest/components/alert-dialog.json +1 -1
- package/manifest/components/announcement-bar.json +82 -0
- package/manifest/components/app-bar.json +3 -2
- package/manifest/components/app-shell.json +3 -0
- package/manifest/components/autocomplete.json +2 -0
- package/manifest/components/band.json +2 -1
- package/manifest/components/bento.json +13 -13
- package/manifest/components/button.json +1 -0
- package/manifest/components/channel-matrix.json +1 -0
- package/manifest/components/chart-tooltip-content.json +1 -0
- package/manifest/components/checkbox-group.json +1 -1
- package/manifest/components/checkbox.json +1 -1
- package/manifest/components/chip.json +1 -0
- package/manifest/components/color-area.json +10 -0
- package/manifest/components/color-field.json +3 -0
- package/manifest/components/color-picker.json +3 -0
- package/manifest/components/combobox.json +2 -0
- package/manifest/components/command-palette.json +1 -0
- package/manifest/components/composer.json +3 -0
- package/manifest/components/context-menu.json +1 -1
- package/manifest/components/data-table-pagination.json +3 -0
- package/manifest/components/data-table-toolbar.json +3 -0
- package/manifest/components/date-field.json +3 -0
- package/manifest/components/date-picker.json +3 -0
- package/manifest/components/date-range-picker.json +3 -0
- package/manifest/components/delegation-card.json +6 -0
- package/manifest/components/density.json +6 -2
- package/manifest/components/dialog.json +1 -1
- package/manifest/components/dmx-bar.json +1 -1
- package/manifest/components/dmx-strip.json +1 -1
- package/manifest/components/drawer.json +1 -1
- package/manifest/components/dropdown-menu.json +1 -0
- package/manifest/components/env-var-input.json +3 -0
- package/manifest/components/file-upload.json +5 -0
- package/manifest/components/font-scope.json +82 -0
- package/manifest/components/footer.json +0 -1
- package/manifest/components/gloss-object.json +81 -0
- package/manifest/components/grid.json +6 -6
- package/manifest/components/halftone.json +79 -0
- package/manifest/components/hero.json +204 -0
- package/manifest/components/hover-card.json +1 -1
- package/manifest/components/input-group.json +3 -0
- package/manifest/components/input.json +3 -0
- package/manifest/components/kbd.json +1 -0
- package/manifest/components/keyword.json +67 -0
- package/manifest/components/ledger.json +5 -5
- package/manifest/components/logo-strip.json +93 -0
- package/manifest/components/markdown-text.json +3 -3
- package/manifest/components/menubar.json +1 -1
- package/manifest/components/message.json +8 -0
- package/manifest/components/motif.json +90 -0
- package/manifest/components/multi-select.json +2 -0
- package/manifest/components/musical-time-input.json +3 -0
- package/manifest/components/navigation-menu.json +1 -0
- package/manifest/components/number-field.json +3 -0
- package/manifest/components/otp-field.json +3 -0
- package/manifest/components/page-shell.json +5 -0
- package/manifest/components/particle-logo.json +140 -0
- package/manifest/components/particle-mark.json +75 -0
- package/manifest/components/password-input.json +3 -0
- package/manifest/components/popover.json +1 -1
- package/manifest/components/preview.json +10 -4
- package/manifest/components/radio-group.json +0 -1
- package/manifest/components/radio.json +0 -1
- package/manifest/components/reveal.json +106 -0
- package/manifest/components/ribbon.json +65 -0
- package/manifest/components/scrollable-tabs-list.json +1 -0
- package/manifest/components/scrub-input.json +3 -0
- package/manifest/components/search-input.json +3 -0
- package/manifest/components/segmented-control.json +1 -0
- package/manifest/components/select.json +3 -0
- package/manifest/components/sheet.json +1 -1
- package/manifest/components/sidebar.json +1 -0
- package/manifest/components/skeleton.json +1 -0
- package/manifest/components/spectrum-visualizer.json +10 -0
- package/manifest/components/stack.json +4 -4
- package/manifest/components/stat-band.json +68 -0
- package/manifest/components/stat.json +1 -1
- package/manifest/components/still-provider.json +60 -0
- package/manifest/components/switch.json +0 -1
- package/manifest/components/table.json +2 -2
- package/manifest/components/tabs.json +1 -0
- package/manifest/components/tags-input.json +2 -0
- package/manifest/components/text-scramble.json +90 -0
- package/manifest/components/textarea.json +3 -0
- package/manifest/components/theme-configurator.json +1 -0
- package/manifest/components/theme-provider.json +10 -4
- package/manifest/components/time-field.json +3 -0
- package/manifest/components/toast.json +1 -0
- package/manifest/components/tooltip.json +1 -1
- package/manifest/fixtures.json +18 -0
- package/manifest/manifest.json +368 -80
- package/manifest/tokens.json +93 -3
- package/package.json +6 -6
- package/dist/system/_paint-ground-pattern.js +0 -30
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//#region src/lib/particle-field-shaders.ts
|
|
2
|
+
/**
|
|
3
|
+
* cueplusplus.com's particle-logo shaders, verbatim (`site/src/components/particle-logo/shaders.ts`
|
|
4
|
+
* at 08c6f26), as GLSL ES 1.00 with `highp`, which WebGL2 compiles unchanged.
|
|
5
|
+
*
|
|
6
|
+
* One deviation (spec 2026-10-04-cue-site-verbatim §9, D2): the two `smoothstep` calls the source
|
|
7
|
+
* wrote with their edges reversed — undefined in GLSL — are written as `1.0 - smoothstep(lo, hi, x)`,
|
|
8
|
+
* which is the value every GPU the site was seen on already computed for them.
|
|
9
|
+
*
|
|
10
|
+
* @internal Not part of the public API.
|
|
11
|
+
*/
|
|
12
|
+
const VERTEX_SHADER = `
|
|
13
|
+
precision highp float;
|
|
14
|
+
|
|
15
|
+
// Built-in uniforms (must be declared for RawShaderMaterial)
|
|
16
|
+
uniform mat4 modelViewMatrix;
|
|
17
|
+
uniform mat4 projectionMatrix;
|
|
18
|
+
|
|
19
|
+
// Custom attributes
|
|
20
|
+
attribute vec3 position;
|
|
21
|
+
attribute vec2 uv;
|
|
22
|
+
attribute float pindex;
|
|
23
|
+
attribute vec3 offset;
|
|
24
|
+
attribute float angle;
|
|
25
|
+
|
|
26
|
+
// Custom uniforms
|
|
27
|
+
uniform float uTime;
|
|
28
|
+
uniform float uRandom;
|
|
29
|
+
uniform float uDepth;
|
|
30
|
+
uniform float uSize;
|
|
31
|
+
uniform vec2 uTextureSize;
|
|
32
|
+
uniform sampler2D uTexture;
|
|
33
|
+
uniform sampler2D uTouch;
|
|
34
|
+
uniform vec2 uWaveDirection;
|
|
35
|
+
uniform float uWavePosition;
|
|
36
|
+
uniform float uWaveTime;
|
|
37
|
+
uniform vec3 uWaveColor;
|
|
38
|
+
uniform vec3 uPrevColor;
|
|
39
|
+
|
|
40
|
+
varying vec2 vPUv;
|
|
41
|
+
varying vec2 vUv;
|
|
42
|
+
varying float vTouch;
|
|
43
|
+
varying float vFlipProgress;
|
|
44
|
+
varying float vIsFlipping;
|
|
45
|
+
|
|
46
|
+
// Simplex 2D noise
|
|
47
|
+
vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
|
|
48
|
+
|
|
49
|
+
float snoise(vec2 v) {
|
|
50
|
+
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
|
|
51
|
+
-0.577350269189626, 0.024390243902439);
|
|
52
|
+
vec2 i = floor(v + dot(v, C.yy) );
|
|
53
|
+
vec2 x0 = v - i + dot(i, C.xx);
|
|
54
|
+
vec2 i1;
|
|
55
|
+
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
|
56
|
+
vec4 x12 = x0.xyxy + C.xxzz;
|
|
57
|
+
x12.xy -= i1;
|
|
58
|
+
i = mod(i, 289.0);
|
|
59
|
+
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
|
|
60
|
+
+ i.x + vec3(0.0, i1.x, 1.0 ));
|
|
61
|
+
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
|
|
62
|
+
dot(x12.zw,x12.zw)), 0.0);
|
|
63
|
+
m = m*m ;
|
|
64
|
+
m = m*m ;
|
|
65
|
+
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
|
66
|
+
vec3 h = abs(x) - 0.5;
|
|
67
|
+
vec3 ox = floor(x + 0.5);
|
|
68
|
+
vec3 a0 = x - ox;
|
|
69
|
+
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
|
70
|
+
vec3 g;
|
|
71
|
+
g.x = a0.x * x0.x + h.x * x0.y;
|
|
72
|
+
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
|
73
|
+
return 130.0 * dot(m, g);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
float random(float n) {
|
|
77
|
+
return fract(sin(n) * 43758.5453123);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void main() {
|
|
81
|
+
vUv = uv;
|
|
82
|
+
|
|
83
|
+
// particle uv
|
|
84
|
+
vec2 puv = offset.xy / uTextureSize;
|
|
85
|
+
vPUv = puv;
|
|
86
|
+
|
|
87
|
+
// pixel color
|
|
88
|
+
vec4 colA = texture2D(uTexture, puv);
|
|
89
|
+
float grey = colA.r * 0.21 + colA.g * 0.71 + colA.b * 0.07;
|
|
90
|
+
|
|
91
|
+
// displacement
|
|
92
|
+
vec3 displaced = offset;
|
|
93
|
+
|
|
94
|
+
// Random base offset - scattered positioning
|
|
95
|
+
float rndX = random(pindex) - 0.5;
|
|
96
|
+
float rndY = random(offset.x + pindex) - 0.5;
|
|
97
|
+
displaced.xy += vec2(rndX, rndY) * uRandom;
|
|
98
|
+
|
|
99
|
+
float rndz = (random(pindex) + snoise(vec2(pindex * 0.1, uTime * 0.1)));
|
|
100
|
+
displaced.z += rndz * (random(pindex) * 2.0 * uDepth);
|
|
101
|
+
|
|
102
|
+
// Constant atom-like wiggle animation (always active)
|
|
103
|
+
float wiggleSpeed = 2.0 + random(pindex) * 2.0; // Varying speeds per particle
|
|
104
|
+
float wiggleAmount = 1.5 + random(pindex + 100.0) * 1.0; // Varying amplitudes
|
|
105
|
+
float wigglePhase = random(pindex * 2.0) * 6.28; // Random phase offset
|
|
106
|
+
|
|
107
|
+
displaced.x += sin(uTime * wiggleSpeed + wigglePhase) * wiggleAmount;
|
|
108
|
+
displaced.y += cos(uTime * wiggleSpeed * 0.8 + wigglePhase) * wiggleAmount;
|
|
109
|
+
displaced.z += sin(uTime * wiggleSpeed * 0.6 + wigglePhase * 0.5) * wiggleAmount * 0.5;
|
|
110
|
+
|
|
111
|
+
// center
|
|
112
|
+
displaced.xy -= uTextureSize * 0.5;
|
|
113
|
+
|
|
114
|
+
// Jagged wave effect - sweeps from a random direction with noise
|
|
115
|
+
// Project particle position onto wave direction
|
|
116
|
+
float particlePos = dot(displaced.xy, uWaveDirection);
|
|
117
|
+
|
|
118
|
+
// Add noise to create jagged wave front based on particle's perpendicular position
|
|
119
|
+
float perpPos = dot(displaced.xy, vec2(-uWaveDirection.y, uWaveDirection.x));
|
|
120
|
+
float noiseOffset = snoise(vec2(perpPos * 0.02, pindex * 0.1)) * 0.15;
|
|
121
|
+
noiseOffset += snoise(vec2(pindex * 0.05, perpPos * 0.01)) * 0.1;
|
|
122
|
+
|
|
123
|
+
// Normalize to 0-1 range based on texture size
|
|
124
|
+
float maxDist = length(uTextureSize) * 0.6;
|
|
125
|
+
float normalizedPos = (particlePos + maxDist) / (maxDist * 2.0);
|
|
126
|
+
|
|
127
|
+
// Add the noise offset to create jagged edge
|
|
128
|
+
normalizedPos += noiseOffset;
|
|
129
|
+
|
|
130
|
+
// Wave front position (0 to 1 sweep across)
|
|
131
|
+
float waveFront = uWavePosition;
|
|
132
|
+
float waveWidth = 0.08; // Narrower transition
|
|
133
|
+
|
|
134
|
+
// Has the wave passed this particle? (with noise for randomness)
|
|
135
|
+
float hasFlipped = 1.0 - smoothstep(-waveWidth, waveWidth, normalizedPos - waveFront);
|
|
136
|
+
|
|
137
|
+
// Flip animation for particles in transition zone
|
|
138
|
+
float flipZone = 1.0 - smoothstep(0.0, waveWidth * 2.0, abs(normalizedPos - waveFront));
|
|
139
|
+
|
|
140
|
+
// Subtle pop effect when flipping
|
|
141
|
+
float popEffect = flipZone * 3.0;
|
|
142
|
+
displaced.z += popEffect;
|
|
143
|
+
|
|
144
|
+
// Pass to fragment shader
|
|
145
|
+
vFlipProgress = flipZone;
|
|
146
|
+
vIsFlipping = hasFlipped;
|
|
147
|
+
|
|
148
|
+
// touch - dramatic explosion effect
|
|
149
|
+
float t = texture2D(uTouch, puv).r;
|
|
150
|
+
vTouch = t; // Pass to fragment shader
|
|
151
|
+
float touchStrength = t * 60.0; // Strong displacement on hover
|
|
152
|
+
|
|
153
|
+
// Explosive scatter in all directions
|
|
154
|
+
displaced.z += touchStrength * rndz * 2.0;
|
|
155
|
+
displaced.x += cos(angle) * touchStrength * (0.4 + rndz * 0.8);
|
|
156
|
+
displaced.y += sin(angle) * touchStrength * (0.4 + rndz * 0.8);
|
|
157
|
+
|
|
158
|
+
// Add swirl effect on touch
|
|
159
|
+
displaced.x += sin(uTime * 2.0 + pindex * 0.02) * touchStrength * 0.2;
|
|
160
|
+
displaced.y += cos(uTime * 2.0 + pindex * 0.02) * touchStrength * 0.2;
|
|
161
|
+
|
|
162
|
+
// particle size - grows when touched for dramatic effect
|
|
163
|
+
float psize = (snoise(vec2(uTime, pindex) * 0.5) + 2.0);
|
|
164
|
+
psize *= max(grey, 0.4);
|
|
165
|
+
psize *= uSize;
|
|
166
|
+
|
|
167
|
+
// Particles grow slightly when touched
|
|
168
|
+
psize *= (1.0 + t * 0.8);
|
|
169
|
+
|
|
170
|
+
// final position
|
|
171
|
+
vec4 mvPosition = modelViewMatrix * vec4(displaced, 1.0);
|
|
172
|
+
mvPosition.xyz += position * psize;
|
|
173
|
+
vec4 finalPosition = projectionMatrix * mvPosition;
|
|
174
|
+
|
|
175
|
+
gl_Position = finalPosition;
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
/** @internal */
|
|
179
|
+
const FRAGMENT_SHADER = `
|
|
180
|
+
precision highp float;
|
|
181
|
+
|
|
182
|
+
uniform sampler2D uTexture;
|
|
183
|
+
uniform vec3 uWaveColor;
|
|
184
|
+
uniform vec3 uPrevColor;
|
|
185
|
+
uniform float uColorFade;
|
|
186
|
+
|
|
187
|
+
varying vec2 vPUv;
|
|
188
|
+
varying vec2 vUv;
|
|
189
|
+
varying float vTouch;
|
|
190
|
+
varying float vFlipProgress;
|
|
191
|
+
varying float vIsFlipping;
|
|
192
|
+
|
|
193
|
+
void main() {
|
|
194
|
+
vec2 uv = vUv;
|
|
195
|
+
vec2 puv = vPUv;
|
|
196
|
+
|
|
197
|
+
// pixel color - white for the logo
|
|
198
|
+
vec4 colA = texture2D(uTexture, puv);
|
|
199
|
+
float grey = colA.r * 0.21 + colA.g * 0.71 + colA.b * 0.07;
|
|
200
|
+
|
|
201
|
+
// Circle shape for particles
|
|
202
|
+
float border = 0.3;
|
|
203
|
+
float radius = 0.5;
|
|
204
|
+
float dist = radius - distance(uv, vec2(0.5));
|
|
205
|
+
float t = smoothstep(0.0, border, dist);
|
|
206
|
+
|
|
207
|
+
float brightness = max(grey, 0.3);
|
|
208
|
+
|
|
209
|
+
// Color transition: smoothly flip between previous and new color
|
|
210
|
+
// vIsFlipping smoothly transitions from 0 to 1 as wave passes
|
|
211
|
+
vec3 currentColor = mix(uPrevColor, uWaveColor, vIsFlipping);
|
|
212
|
+
|
|
213
|
+
// During active flip zone, flash slightly brighter
|
|
214
|
+
float flipBrightness = 1.0 + vFlipProgress * 0.3;
|
|
215
|
+
|
|
216
|
+
// Fade back to white over time
|
|
217
|
+
vec3 white = vec3(1.0, 1.0, 1.0);
|
|
218
|
+
vec3 particleColor = mix(currentColor, white, uColorFade);
|
|
219
|
+
|
|
220
|
+
// Apply brightness
|
|
221
|
+
vec3 finalColor = particleColor * brightness * flipBrightness;
|
|
222
|
+
|
|
223
|
+
// Touch glow - bright white flash
|
|
224
|
+
finalColor = mix(finalColor, white, vTouch * 0.8);
|
|
225
|
+
|
|
226
|
+
// Brightness boost on touch
|
|
227
|
+
finalColor *= (1.0 + vTouch * 0.5);
|
|
228
|
+
|
|
229
|
+
vec4 color = vec4(finalColor, 1.0);
|
|
230
|
+
color.a = t * brightness * (1.0 + vTouch * 0.3);
|
|
231
|
+
|
|
232
|
+
gl_FragColor = color;
|
|
233
|
+
}
|
|
234
|
+
`;
|
|
235
|
+
//#endregion
|
|
236
|
+
export { FRAGMENT_SHADER, VERTEX_SHADER };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
//#region src/lib/particle-field.ts
|
|
2
|
+
/** A pixel is a particle when its alpha or its red is over this. */
|
|
3
|
+
const THRESHOLD = 200;
|
|
4
|
+
/**
|
|
5
|
+
* The source's sampling, exactly: every `skip`-th pixel of the **flattened** raster, kept when
|
|
6
|
+
* `alpha > 200 || red > 200`. The source rasterises its logo onto opaque black, so every sampled
|
|
7
|
+
* pixel passes the alpha test and the black ground becomes a field of dim particles around the
|
|
8
|
+
* mark — which is what the site looks like, and is kept.
|
|
9
|
+
*/
|
|
10
|
+
function sampleParticles({ data, width, height }, skip, random = Math.random) {
|
|
11
|
+
const total = width * height;
|
|
12
|
+
let count = 0;
|
|
13
|
+
for (let i = 0; i < total; i += 1) {
|
|
14
|
+
if (i % skip !== 0) continue;
|
|
15
|
+
if (data[i * 4 + 3] > THRESHOLD || data[i * 4] > THRESHOLD) count += 1;
|
|
16
|
+
}
|
|
17
|
+
const indices = new Uint16Array(count);
|
|
18
|
+
const offsets = new Float32Array(count * 3);
|
|
19
|
+
const angles = new Float32Array(count);
|
|
20
|
+
for (let i = 0, j = 0; i < total; i += 1) {
|
|
21
|
+
if (i % skip !== 0) continue;
|
|
22
|
+
if (data[i * 4 + 3] <= THRESHOLD && data[i * 4] <= THRESHOLD) continue;
|
|
23
|
+
offsets[j * 3] = i % width;
|
|
24
|
+
offsets[j * 3 + 1] = Math.floor(i / width);
|
|
25
|
+
offsets[j * 3 + 2] = 0;
|
|
26
|
+
indices[j] = i;
|
|
27
|
+
angles[j] = random() * Math.PI * 2;
|
|
28
|
+
j += 1;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
count,
|
|
32
|
+
indices,
|
|
33
|
+
offsets,
|
|
34
|
+
angles
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An sRGB hex colour in linear light, as three's `Color` converts it with colour management on
|
|
39
|
+
* (r152+): a `RawShaderMaterial` uniform receives these values, and the shader writes them out
|
|
40
|
+
* unconverted. Ported so a hex reaches the GPU exactly as it did on the site.
|
|
41
|
+
*/
|
|
42
|
+
function srgbToLinear(hex) {
|
|
43
|
+
const value = Number.parseInt(hex.replace("#", ""), 16);
|
|
44
|
+
const channel = (c) => {
|
|
45
|
+
const s = c / 255;
|
|
46
|
+
return s <= .04045 ? s / 12.92 : ((s + .055) / 1.055) ** 2.4;
|
|
47
|
+
};
|
|
48
|
+
return [
|
|
49
|
+
channel(value >> 16 & 255),
|
|
50
|
+
channel(value >> 8 & 255),
|
|
51
|
+
channel(value & 255)
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
const same = (a, b) => a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
55
|
+
/**
|
|
56
|
+
* The next wave's colour: a random one that is not the current colour, as the source's
|
|
57
|
+
* `do … while` picks it — guarded, so a palette with nothing different to offer returns at once
|
|
58
|
+
* instead of looping for ever.
|
|
59
|
+
*/
|
|
60
|
+
function pickWaveColor(palette, current, random = Math.random) {
|
|
61
|
+
if (!palette.some((colour) => !same(colour, current))) return palette[0] ?? current;
|
|
62
|
+
let next;
|
|
63
|
+
do
|
|
64
|
+
next = palette[Math.floor(random() * palette.length)];
|
|
65
|
+
while (same(next, current) && palette.length > 1);
|
|
66
|
+
return next;
|
|
67
|
+
}
|
|
68
|
+
const easeOutSine = (t) => Math.sin(t * Math.PI / 2);
|
|
69
|
+
/**
|
|
70
|
+
* The source's `TouchTexture` trail, without its canvas: points are added once per rendered
|
|
71
|
+
* update, aged before they are drawn, and dropped once their age passes 80. A point's force is how
|
|
72
|
+
* far the pointer moved since the last one, squared and scaled, `min((dx² + dy²) × 10000, 1)`; the
|
|
73
|
+
* first point has none.
|
|
74
|
+
*/
|
|
75
|
+
var TouchTrail = class TouchTrail {
|
|
76
|
+
static SIZE = 64;
|
|
77
|
+
static MAX_AGE = 80;
|
|
78
|
+
static RADIUS = .25;
|
|
79
|
+
points = [];
|
|
80
|
+
add(x, y) {
|
|
81
|
+
const last = this.points[this.points.length - 1];
|
|
82
|
+
let force = 0;
|
|
83
|
+
if (last !== void 0) {
|
|
84
|
+
const dx = last.x - x;
|
|
85
|
+
const dy = last.y - y;
|
|
86
|
+
force = Math.min((dx * dx + dy * dy) * 1e4, 1);
|
|
87
|
+
}
|
|
88
|
+
this.points.push({
|
|
89
|
+
x,
|
|
90
|
+
y,
|
|
91
|
+
age: 0,
|
|
92
|
+
force
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
age() {
|
|
96
|
+
this.points = this.points.filter((point) => {
|
|
97
|
+
point.age += 1;
|
|
98
|
+
return point.age <= TouchTrail.MAX_AGE;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** A point's intensity at an age: rising over the first 30% of its life, falling over the rest. */
|
|
102
|
+
static intensity(age) {
|
|
103
|
+
const rise = TouchTrail.MAX_AGE * .3;
|
|
104
|
+
if (age < rise) return easeOutSine(age / rise);
|
|
105
|
+
return easeOutSine(1 - (age - rise) / (TouchTrail.MAX_AGE * .7));
|
|
106
|
+
}
|
|
107
|
+
/** Paint the trail onto its 64px canvas, as the source's `drawTouch` does. */
|
|
108
|
+
draw(context) {
|
|
109
|
+
const size = TouchTrail.SIZE;
|
|
110
|
+
context.fillStyle = "black";
|
|
111
|
+
context.fillRect(0, 0, size, size);
|
|
112
|
+
for (const point of this.points) {
|
|
113
|
+
const x = point.x * size;
|
|
114
|
+
const y = (1 - point.y) * size;
|
|
115
|
+
const radius = size * TouchTrail.RADIUS * TouchTrail.intensity(point.age) * point.force;
|
|
116
|
+
if (radius <= 0) continue;
|
|
117
|
+
const gradient = context.createRadialGradient(x, y, radius * .25, x, y, radius);
|
|
118
|
+
gradient.addColorStop(0, "rgba(255, 255, 255, 0.8)");
|
|
119
|
+
gradient.addColorStop(1, "rgba(0, 0, 0, 0.0)");
|
|
120
|
+
context.beginPath();
|
|
121
|
+
context.fillStyle = gradient;
|
|
122
|
+
context.arc(x, y, radius, 0, Math.PI * 2);
|
|
123
|
+
context.fill();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
/** R3F 9.6.1's default clipping planes, which the source inherits. */
|
|
128
|
+
const NEAR = .1;
|
|
129
|
+
const FAR = 1e3;
|
|
130
|
+
/** The world-space size the camera sees at z = 0, as R3F's `viewport` reports it. */
|
|
131
|
+
function viewportAt(aspect) {
|
|
132
|
+
const height = 600 * Math.tan(25 * Math.PI / 180);
|
|
133
|
+
return {
|
|
134
|
+
width: height * aspect,
|
|
135
|
+
height
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** A column-major perspective matrix, as three's `PerspectiveCamera` builds it. */
|
|
139
|
+
function perspective(fov, aspect, near, far) {
|
|
140
|
+
const f = 1 / Math.tan(fov / 2 * Math.PI / 180);
|
|
141
|
+
const m = /* @__PURE__ */ new Float32Array(16);
|
|
142
|
+
m[0] = f / aspect;
|
|
143
|
+
m[5] = f;
|
|
144
|
+
m[10] = (far + near) / (near - far);
|
|
145
|
+
m[11] = -1;
|
|
146
|
+
m[14] = 2 * far * near / (near - far);
|
|
147
|
+
return m;
|
|
148
|
+
}
|
|
149
|
+
/** The model-view matrix: the mesh scaled `[s, s, 1]`, seen from `z = 300`. */
|
|
150
|
+
function modelView(scale) {
|
|
151
|
+
const m = /* @__PURE__ */ new Float32Array(16);
|
|
152
|
+
m[0] = scale;
|
|
153
|
+
m[5] = scale;
|
|
154
|
+
m[10] = 1;
|
|
155
|
+
m[14] = -300;
|
|
156
|
+
m[15] = 1;
|
|
157
|
+
return m;
|
|
158
|
+
}
|
|
159
|
+
/** The mesh's scale, as the source computes it for each fit. */
|
|
160
|
+
function logoScale(fit, aspect, width, height, containFill) {
|
|
161
|
+
const viewport = viewportAt(aspect);
|
|
162
|
+
if (fit === "contain") return Math.min(viewport.width / width, viewport.height / height) * containFill;
|
|
163
|
+
return viewport.height * .26 / height;
|
|
164
|
+
}
|
|
165
|
+
/** The raster the source samples: 600px wide, at its image's aspect. */
|
|
166
|
+
const TARGET_WIDTH = 600;
|
|
167
|
+
/** Rasterise an image as the source does: twice the size with high-quality smoothing, then down onto opaque black. */
|
|
168
|
+
function rasterise(image) {
|
|
169
|
+
const scale = TARGET_WIDTH / image.width;
|
|
170
|
+
const width = Math.round(image.width * scale);
|
|
171
|
+
const height = Math.round(image.height * scale);
|
|
172
|
+
const large = document.createElement("canvas");
|
|
173
|
+
large.width = width * 2;
|
|
174
|
+
large.height = height * 2;
|
|
175
|
+
const big = large.getContext("2d", { willReadFrequently: true });
|
|
176
|
+
big.imageSmoothingEnabled = true;
|
|
177
|
+
big.imageSmoothingQuality = "high";
|
|
178
|
+
big.fillStyle = "black";
|
|
179
|
+
big.fillRect(0, 0, large.width, large.height);
|
|
180
|
+
big.drawImage(image, 0, 0, large.width, large.height);
|
|
181
|
+
const small = document.createElement("canvas");
|
|
182
|
+
small.width = width;
|
|
183
|
+
small.height = height;
|
|
184
|
+
const context = small.getContext("2d", { willReadFrequently: true });
|
|
185
|
+
context.imageSmoothingEnabled = true;
|
|
186
|
+
context.imageSmoothingQuality = "high";
|
|
187
|
+
context.fillStyle = "black";
|
|
188
|
+
context.fillRect(0, 0, width, height);
|
|
189
|
+
context.drawImage(large, 0, 0, width, height);
|
|
190
|
+
const { data } = context.getImageData(0, 0, width, height);
|
|
191
|
+
return {
|
|
192
|
+
data,
|
|
193
|
+
width,
|
|
194
|
+
height
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A CSS colour as a `#rrggbb` string, normalised through a canvas — the one parser every
|
|
199
|
+
* browser ships for whatever a theme writes (hex, `rgb()`, `oklch()`, `color-mix()`).
|
|
200
|
+
*/
|
|
201
|
+
function normaliseColour(colour) {
|
|
202
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
203
|
+
if (context === null) return null;
|
|
204
|
+
context.fillStyle = "#000000";
|
|
205
|
+
context.fillStyle = colour;
|
|
206
|
+
const out = String(context.fillStyle);
|
|
207
|
+
return /^#[0-9a-f]{6}$/iu.test(out) ? out : null;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { FAR, NEAR, TouchTrail, logoScale, modelView, normaliseColour, perspective, pickWaveColor, rasterise, sampleParticles, srgbToLinear, viewportAt };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { FIELD_RADIUS } from "../system/_style-field-chassis.js";
|
|
3
4
|
import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
|
|
4
5
|
import { useComposedRefs } from "../lib/compose.js";
|
|
5
6
|
import { toRem } from "../instruments/_rem.js";
|
|
@@ -141,7 +142,7 @@ const SpectrumVisualizer = React.forwardRef(function SpectrumVisualizer({ classN
|
|
|
141
142
|
"aria-label": summary,
|
|
142
143
|
"data-slot": "spectrum-visualizer-canvas",
|
|
143
144
|
"data-mode": mode,
|
|
144
|
-
className:
|
|
145
|
+
className: `block w-full ${FIELD_RADIUS} border border-border bg-(--cue-data-ground)`,
|
|
145
146
|
style: { height: toRem(height) }
|
|
146
147
|
}), labels ? /* @__PURE__ */ jsx("div", {
|
|
147
148
|
"data-slot": "spectrum-visualizer-legend",
|
|
@@ -117,9 +117,9 @@ declare const overlayFooterClasses = "flex items-center justify-end gap-(--cue-s
|
|
|
117
117
|
* and the highlight are the style set's, with those as their defaults
|
|
118
118
|
* (`_style-menu.ts`).
|
|
119
119
|
*/
|
|
120
|
-
declare const menuItemClasses = "flex h-control-sm cursor-default items-center gap-(--cue-space-2) rounded-[var(--cue-style-menu-item-radius,calc(var(--cue-radius-control)*var(--cue-radius-scale)))] px-(--cue-space-3) font-[family-name:var(--cue-style-control-face,var(--cue-font-mono))] text-(length:--cue-text-ui) text-[color:var(--cue-style-quiet-ink,var(--cue-fg-muted))] data-[highlighted]:bg-[color:var(--cue-style-menu-highlight,var(--cue-accent-soft))] outline-none select-none data-[highlighted]:text-fg data-[disabled]:pointer-events-none data-[disabled]:opacity-[var(--cue-style-row-disabled-opacity,0.4)]";
|
|
120
|
+
declare const menuItemClasses = "flex h-control-sm cursor-default items-center gap-(--cue-space-2) rounded-[var(--cue-style-menu-item-radius,calc(var(--cue-style-control-radius,var(--cue-radius-control))*var(--cue-radius-scale)))] px-(--cue-space-3) font-[family-name:var(--cue-style-control-face,var(--cue-font-mono))] text-(length:--cue-text-ui) text-[color:var(--cue-style-quiet-ink,var(--cue-fg-muted))] data-[highlighted]:bg-[color:var(--cue-style-menu-highlight,var(--cue-accent-soft))] outline-none select-none data-[highlighted]:text-fg data-[disabled]:pointer-events-none data-[disabled]:opacity-[var(--cue-style-row-disabled-opacity,0.4)]";
|
|
121
121
|
/** The uppercase mono caption over a group of menu rows — by default. */
|
|
122
|
-
declare const menuLabelClasses = "flex h-control-sm items-center px-(--cue-space-3) py-[var(--cue-style-group-label-pad,0px)] font-[family-name:var(--cue-style-label-face,var(--cue-font-mono))] text-[length:var(--cue-style-group-label-size,var(--cue-style-label-size,var(--cue-text-label)))] tracking-[var(--cue-style-label-tracking,var(--cue-tracking-caps))] text-[color:var(--cue-style-group-label-ink,var(--cue-style-label-ink,var(--cue-fg-
|
|
122
|
+
declare const menuLabelClasses = "flex h-control-sm items-center px-(--cue-space-3) py-[var(--cue-style-group-label-pad,0px)] font-[family-name:var(--cue-style-label-face,var(--cue-font-mono))] text-[length:var(--cue-style-group-label-size,var(--cue-style-label-size,var(--cue-text-label)))] tracking-[var(--cue-style-label-tracking,var(--cue-tracking-caps))] text-[color:var(--cue-style-group-label-ink,var(--cue-style-label-ink,var(--cue-fg-muted)))] font-[number:var(--cue-style-label-weight,inherit)] [text-transform:var(--cue-style-label-case,uppercase)] select-none";
|
|
123
123
|
/** The hairline between menu groups. */
|
|
124
124
|
declare const menuSeparatorClasses = "my-(--cue-space-1) mx-[var(--cue-style-menu-separator-inset,0px)] h-px bg-border";
|
|
125
125
|
/** Padding of a menu popup — a single space unit so rows reach the rim. */
|
|
@@ -40,6 +40,25 @@ import { useRender } from "@base-ui/react/use-render";
|
|
|
40
40
|
*/
|
|
41
41
|
const buttonChassis = `inline-flex select-none items-center justify-center ${CONTROL_GAP} whitespace-nowrap rounded-control ${CONTROL_FACE} ${CONTROL_WEIGHT} ${CONTROL_TRACKING} ${CONTROL_TRIM} ${CONTROL_CASE} transition-colors duration-150 outline-none ${FOCUS_RING_PAINT} focus-visible:outline-offset-[var(--cue-style-focus-offset,1px)] disabled:pointer-events-none ${DISABLED_OPACITY}`;
|
|
42
42
|
/**
|
|
43
|
+
* A style set's chevron cut (structural Looks, spec 2026-10-02 §3.3), for the tones
|
|
44
|
+
* that are a filled shape: primary and danger always, secondary only where the set
|
|
45
|
+
* fills its ground (the build derives `--cue-style-secondary-cut` as `none` for a set
|
|
46
|
+
* whose secondary ground is `transparent`). Outline, ghost and danger-outline are
|
|
47
|
+
* drawn by their edge or their ink, and a hollow cut is geometry this chassis does
|
|
48
|
+
* not take on, so they keep the rectangle.
|
|
49
|
+
*
|
|
50
|
+
* The cut is a `clip-path` on the element: a filled tone's border is transparent,
|
|
51
|
+
* so nothing is cut but fill and exterior glow. Two things give way, on purpose:
|
|
52
|
+
* the glow outside a cut button is clipped (a set with a cut is worn with a pack
|
|
53
|
+
* that has none), and under `:focus-visible` and in forced colours the clip is
|
|
54
|
+
* removed, so the focus ring and the system border are never cut. The element stays
|
|
55
|
+
* a rectangle throughout: hit area, ring, `render`, refs, sizing and keyboard.
|
|
56
|
+
* Unset, the clip is `none` and the button renders exactly as before. The rules
|
|
57
|
+
* live in `styles.css` (`.cue-cut`, `.cue-cut-secondary`): as arbitrary utilities
|
|
58
|
+
* they were ~105 characters on every button a page renders.
|
|
59
|
+
*/
|
|
60
|
+
const CUT_FILLED = "cue-cut";
|
|
61
|
+
/**
|
|
43
62
|
* The shared colour vocabulary. `accent` is theme-controlled, so `primary`
|
|
44
63
|
* reads as white-on-black in `cue` and cyan-on-near-black in `terminal` with no
|
|
45
64
|
* per-theme branching here.
|
|
@@ -57,11 +76,11 @@ const buttonChassis = `inline-flex select-none items-center justify-center ${CON
|
|
|
57
76
|
* @internal Not part of the public API; imported by `icon-button.tsx`.
|
|
58
77
|
*/
|
|
59
78
|
const buttonTones = {
|
|
60
|
-
primary: `bg-accent-fill text-accent-fill-fg hover:bg-accent-fill-hover border border-transparent ${GLOW_CLASSES} ${DISABLED_TONES.primary}`,
|
|
61
|
-
secondary: `border ${SECONDARY_EDGE} ${SECONDARY_INK} hover:text-[color:var(--cue-style-secondary-hover-ink,var(--cue-fg))] hover:border-[color:var(--cue-style-secondary-hover-edge,var(--cue-border-strong))] ${SECONDARY_GROUND} hover:bg-[color:var(--cue-style-secondary-hover-ground,var(--cue-style-secondary-ground,var(--cue-surface-2)))] ${DISABLED_TONES.secondary}`,
|
|
79
|
+
primary: `bg-accent-fill [background-image:var(--cue-style-primary-fill,none)] text-accent-fill-fg hover:bg-accent-fill-hover hover:[background-image:none] disabled:[background-image:none] border border-transparent ${GLOW_CLASSES} ${CUT_FILLED} ${DISABLED_TONES.primary}`,
|
|
80
|
+
secondary: `border ${SECONDARY_EDGE} ${SECONDARY_INK} hover:text-[color:var(--cue-style-secondary-hover-ink,var(--cue-fg))] hover:border-[color:var(--cue-style-secondary-hover-edge,var(--cue-border-strong))] ${SECONDARY_GROUND} hover:bg-[color:var(--cue-style-secondary-hover-ground,var(--cue-style-secondary-ground,var(--cue-surface-2)))] cue-cut-secondary ${DISABLED_TONES.secondary}`,
|
|
62
81
|
ghost: `${GHOST_INK} hover:text-fg hover:bg-surface-2 border border-transparent ${DISABLED_TONES.ghost}`,
|
|
63
82
|
outline: `border ${OUTLINE_EDGE} ${OUTLINE_INK} ${OUTLINE_HOVER_EDGE} hover:text-accent bg-transparent ${OUTLINE_HOVER_GROUND} ${REST_SHADE_CLASS} ${DISABLED_TONES.outline}`,
|
|
64
|
-
danger: `bg-danger text-danger-fg hover:opacity-90 border border-transparent ${GLOW_DANGER_CLASSES} ${DISABLED_TONES.danger}`,
|
|
83
|
+
danger: `bg-danger text-danger-fg hover:opacity-90 border border-transparent ${GLOW_DANGER_CLASSES} ${CUT_FILLED} ${DISABLED_TONES.danger}`,
|
|
65
84
|
"danger-outline": `border text-danger bg-transparent hover:bg-danger/10 border-danger/40 ${DISABLED_TONES["danger-outline"]}`
|
|
66
85
|
};
|
|
67
86
|
/**
|
|
@@ -2,7 +2,7 @@ import { ClassProp, VariantProps } from "../lib/cva.js";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
//#region src/primitives/chip.d.ts
|
|
4
4
|
declare const chipVariants: (props?: ({
|
|
5
|
-
tone?: "
|
|
5
|
+
tone?: "info" | "warn" | "danger" | "accent" | "ok" | "busy" | "neutral" | null | undefined;
|
|
6
6
|
variant?: "outline" | "tinted" | "tag" | null | undefined;
|
|
7
7
|
interactive?: boolean | null | undefined;
|
|
8
8
|
} & ClassProp) | undefined) => string;
|
package/dist/primitives/chip.js
CHANGED
|
@@ -7,16 +7,73 @@ import { LABEL_FACE } from "../system/_style-label-face.js";
|
|
|
7
7
|
import { LABEL_WEIGHT } from "../system/_style-label-weight.js";
|
|
8
8
|
import * as React from "react";
|
|
9
9
|
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
//#region src/primitives/chip.tsx
|
|
11
|
+
/**
|
|
12
|
+
* The tinted-chip recipe: `bg-<tone>/10 border-<tone>/35` and the tone's ink
|
|
13
|
+
* (mixed toward `fg`, see `CHIP_TONE_INK`), and the
|
|
14
|
+
* two quieter readings of it — `outline`, which drops the fill, and `tag`,
|
|
15
|
+
* which drops the fill and the voice with it.
|
|
16
|
+
*
|
|
17
|
+
* Every property that varies is emitted from exactly one place: one rim, one
|
|
18
|
+
* ink, one fill, one gutter, one type size, always. Emitting `bg-<tone>/10`
|
|
19
|
+
* from the tone map and then cancelling it with `bg-transparent` would leave
|
|
20
|
+
* two competing utilities in the class string, and `chipVariants` is exported —
|
|
21
|
+
* a caller who uses it without `cn` gets a raw string where the winner is
|
|
22
|
+
* decided by stylesheet order rather than by which utility was written last.
|
|
23
|
+
* `h-chip h-4` proved the point at the other end: `tailwind-merge` could not
|
|
24
|
+
* even see that as a conflict until `cn` was taught the bridge's spacing names
|
|
25
|
+
* (2026-10-04), and a caller who skips `cn` still cannot. So nothing here
|
|
26
|
+
* cancels anything. What varies moves into `variants` or
|
|
27
|
+
* `compoundVariants` until only one of it is ever emitted.
|
|
28
|
+
*
|
|
29
|
+
* That is why `neutral` carries no rim of its own. The other six tones rim
|
|
30
|
+
* themselves at `/35` in all three variants, but the neutral line has two
|
|
31
|
+
* weights in this system: `--cue-border` is a box edge, and a chip is a box;
|
|
32
|
+
* `--cue-hair-strong` is the rule between things, and a tag is a mark on the
|
|
33
|
+
* page rather than a container. The rim is therefore a compound of tone and
|
|
34
|
+
* variant, exactly as the fill is.
|
|
35
|
+
*
|
|
36
|
+
* **The recipe reads the style set** (spec 2026-09-26-looks-fidelity §3.6,
|
|
37
|
+
* `system/_style-chip.ts`): the six tones' fill and rim are
|
|
38
|
+
* `color-mix(in oklab, var(--cue-<tone>) var(--cue-style-chip-fill, 10%), transparent)`
|
|
39
|
+
* and the same with `--cue-style-chip-edge` at `35%` — exactly `bg-<tone>/10`
|
|
40
|
+
* and `border-<tone>/35` under no set, and a set's own shares inside one. The
|
|
41
|
+
* case, tracking, radius, size and weight read `--cue-style-chip-case`,
|
|
42
|
+
* `-tracking`, `-radius`, `-size` and `--cue-style-label-weight` the same way.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* <span className={chipVariants({ tone: "danger", variant: "outline" })}>fault</span>
|
|
46
|
+
* @example
|
|
47
|
+
* <span className={chipVariants({ variant: "tag" })}>mcp</span>
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* A toned chip's ink: its tone, mixed 30% toward the page's own `fg`.
|
|
51
|
+
*
|
|
52
|
+
* A status ink is cut to be seen as a dot or a rim (3:1), and a chip sets it
|
|
53
|
+
* as text: small, uppercase and on its own 10% tint, where the bare tone fell
|
|
54
|
+
* to 4.2–4.4:1 in light under most themes and 3.6:1 under Kiln (structural
|
|
55
|
+
* Looks §4.1's rendered sweep, phase 5). Mixing toward `fg` raises it in both
|
|
56
|
+
* modes, because `fg` is the ink furthest from the ground in either, and
|
|
57
|
+
* keeps the hue a reader reads the tone by.
|
|
58
|
+
*/
|
|
59
|
+
const CHIP_TONE_INK = {
|
|
60
|
+
accent: "text-[color:color-mix(in_oklch,var(--cue-accent)_70%,var(--cue-fg))]",
|
|
61
|
+
ok: "text-[color:color-mix(in_oklch,var(--cue-ok)_70%,var(--cue-fg))]",
|
|
62
|
+
busy: "text-[color:color-mix(in_oklch,var(--cue-busy)_70%,var(--cue-fg))]",
|
|
63
|
+
warn: "text-[color:color-mix(in_oklch,var(--cue-warn)_70%,var(--cue-fg))]",
|
|
64
|
+
danger: "text-[color:color-mix(in_oklch,var(--cue-danger)_70%,var(--cue-fg))]",
|
|
65
|
+
info: "text-[color:color-mix(in_oklch,var(--cue-info)_70%,var(--cue-fg))]"
|
|
66
|
+
};
|
|
10
67
|
const chipVariants = cva(`inline-flex ${CHIP_HEIGHT} select-none items-center justify-center ${CHIP_GAP} whitespace-nowrap ${CHIP_RADIUS} border ${LABEL_FACE} ${LABEL_WEIGHT} ${CHIP_CASE} outline-none ${FOCUS_RING}`, {
|
|
11
68
|
variants: {
|
|
12
69
|
tone: {
|
|
13
70
|
neutral: CHIP_INK_NEUTRAL,
|
|
14
|
-
accent: `${CHIP_EDGE.accent}
|
|
15
|
-
ok: `${CHIP_EDGE.ok}
|
|
16
|
-
busy: `${CHIP_EDGE.busy}
|
|
17
|
-
warn: `${CHIP_EDGE.warn}
|
|
18
|
-
danger: `${CHIP_EDGE.danger}
|
|
19
|
-
info: `${CHIP_EDGE.info}
|
|
71
|
+
accent: `${CHIP_EDGE.accent} ${CHIP_TONE_INK.accent}`,
|
|
72
|
+
ok: `${CHIP_EDGE.ok} ${CHIP_TONE_INK.ok}`,
|
|
73
|
+
busy: `${CHIP_EDGE.busy} ${CHIP_TONE_INK.busy}`,
|
|
74
|
+
warn: `${CHIP_EDGE.warn} ${CHIP_TONE_INK.warn}`,
|
|
75
|
+
danger: `${CHIP_EDGE.danger} ${CHIP_TONE_INK.danger}`,
|
|
76
|
+
info: `${CHIP_EDGE.info} ${CHIP_TONE_INK.info}`
|
|
20
77
|
},
|
|
21
78
|
variant: {
|
|
22
79
|
tinted: `${CHIP_PAD} ${CHIP_SIZE} ${CHIP_LEADING} ${CHIP_TRACKING}`,
|
package/dist/styles.css
CHANGED
|
@@ -95,3 +95,29 @@
|
|
|
95
95
|
animation: none;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/* A style set's chevron cut on filled buttons (structural Looks, spec 2026-10-02
|
|
100
|
+
* §3.3), as two bespoke classes for the plussie's reason and one more: written as
|
|
101
|
+
* Tailwind arbitrary properties it was some 105 characters on every button a page
|
|
102
|
+
* renders, and `/kitchen-sink`'s document grew 1.5% for it. Unset, the clip is
|
|
103
|
+
* `none` and nothing changes. Focus and forced colours drop the clip, so the ring
|
|
104
|
+
* and the system border are never cut; unlayered, so a utility cannot undo it. */
|
|
105
|
+
.cue-cut {
|
|
106
|
+
clip-path: var(--cue-style-control-cut-clip, none);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.cue-cut-secondary {
|
|
110
|
+
clip-path: var(--cue-style-secondary-cut, none);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.cue-cut:focus-visible,
|
|
114
|
+
.cue-cut-secondary:focus-visible {
|
|
115
|
+
clip-path: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@media (forced-colors: active) {
|
|
119
|
+
.cue-cut,
|
|
120
|
+
.cue-cut-secondary {
|
|
121
|
+
clip-path: none;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/system/_scoped-pairing.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether the subtree's pairing is a scope's own rather than the root's.
|
|
5
|
+
*
|
|
6
|
+
* A portal mounts on `<body>`, whose `font-family` was computed from the root's
|
|
7
|
+
* pairing, and a computed family does not follow a token. So a portal opened
|
|
8
|
+
* under a scope that names a pairing asks for the family again
|
|
9
|
+
* (`portal.tsx`), and one opened anywhere else leaves it alone. Set by
|
|
10
|
+
* `StageScope` (`_stage-scope.tsx`) for `Preview` and `FontScope`. Its own
|
|
11
|
+
* module, with nothing but React behind it, so the portal can read it without
|
|
12
|
+
* importing the provider it is stamped from.
|
|
13
|
+
*
|
|
14
|
+
* @internal Not part of the public API.
|
|
15
|
+
*/
|
|
16
|
+
const ScopedPairingContext = React.createContext(false);
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ScopedPairingContext };
|