@ambientcss/components 1.2.0 → 2.0.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/dist/styles.css CHANGED
@@ -35,51 +35,264 @@
35
35
  letter-spacing: var(--ambx-type-tracking);
36
36
  }
37
37
 
38
+ /* Button: a chamfered key cap seated in a clearance well — the referent
39
+ is components/button.py's "flush" style (cap seated into a hole with a
40
+ shadow gap ring around it). The button element IS the well: a shallow
41
+ groove (seat depth ~1.2mm = thickness 0.27) whose lume interior shows
42
+ as the gap ring around the cap. Pressing sinks the cap by the
43
+ referent's 0.7mm travel (thickness 1 -> 0.84): the chamfer bands and
44
+ the swept shadow shrink with it for free. */
38
45
  .amb-button {
39
46
  cursor: pointer;
40
- border: medium solid var(--amb-lume);
41
- background-color: inherit;
42
- padding: calc(var(--ambx-grid) * 2);
47
+ border: none;
48
+ padding: var(--ambx-grid-half); /* the clearance gap ring */
49
+ --amb-thickness: 0.27; /* seat depth of the well */
50
+ background-color: var(--amb-lume);
51
+ border-radius: calc(var(--ambx-grid) * 3 + var(--ambx-grid-half));
52
+ min-width: calc(var(--ambx-grid) * 16);
53
+ }
54
+
55
+ .amb-button-cap {
56
+ display: grid;
57
+ place-items: center;
58
+ width: 100%;
59
+ padding: calc(var(--ambx-grid) * 1.5) calc(var(--ambx-grid) * 2);
43
60
  border-radius: calc(var(--ambx-grid) * 3);
44
61
  text-transform: uppercase;
45
- min-width: calc(var(--ambx-grid) * 16);
46
62
  font-weight: 600;
47
- color: var(--amb-lume);
63
+ color: var(--amb-label);
48
64
  }
49
65
 
50
66
  .amb-button:focus {
51
67
  outline-offset: var(--ambx-grid);
52
68
  }
53
69
 
54
- .amb-button:hover,
55
- .amb-knob:hover {
56
- border-color: var(--amb-highlight-color);
70
+ .amb-button:hover .amb-button-cap {
71
+ color: var(--amb-highlight-color);
72
+ }
73
+
74
+ .amb-button:active .amb-button-cap {
75
+ --amb-thickness: 0.84;
76
+ }
77
+
78
+ /* Button shapes, matching the referent lineup (ambient3d/generate.py):
79
+ the default stadium key above, a round key (superellipse exponent 2 —
80
+ pair with amb-mat-shiny for the machined metal-button look), and a
81
+ squarer, flatter EP-133-style pad (exponent 6: tighter corners and a
82
+ 3.6mm cap instead of the key's 4.5mm — thickness 0.8 — with the same
83
+ 0.7mm press travel). */
84
+ .amb-button.amb-button-round {
85
+ min-width: calc(var(--ambx-grid) * 12);
86
+ aspect-ratio: 1;
87
+ border-radius: 50%;
88
+ }
89
+
90
+ .amb-button-round .amb-button-cap {
91
+ height: 100%;
92
+ padding: var(--ambx-grid);
93
+ border-radius: 50%;
94
+ }
95
+
96
+ .amb-button.amb-button-square {
97
+ min-width: calc(var(--ambx-grid) * 14);
98
+ aspect-ratio: 1;
99
+ border-radius: calc(var(--ambx-grid) * 2);
100
+ }
101
+
102
+ .amb-button-square .amb-button-cap {
103
+ height: 100%;
104
+ padding: var(--ambx-grid);
105
+ border-radius: calc(var(--ambx-grid) * 1.5);
106
+ --amb-thickness: 0.8;
57
107
  }
58
108
 
59
- .amb-button:active {
60
- --amb-elevation: 0;
109
+ .amb-button.amb-button-square:active .amb-button-cap {
110
+ --amb-thickness: 0.64;
61
111
  }
62
112
 
113
+ /* Knob: a knob-scale body (thickness 2 = the referent knob.py's 9mm
114
+ height) resting on the panel. The rotating face carries the knurl — a
115
+ repeating-conic rib pattern, physically the referent's ribs catching
116
+ key light on one flank and shading on the other — under a smooth
117
+ opaque top disc (the OP-1-style cap), with an accent indicator dot.
118
+ The whole face rotates with the value, so the ribs move under the
119
+ fingers like the real thing. */
63
120
  .amb-knob {
64
121
  border-radius: 50%;
65
122
  height: calc(var(--ambx-grid) * 16);
66
123
  width: calc(var(--ambx-grid) * 16);
67
- border: medium solid var(--amb-lume);
68
124
  }
69
125
 
70
- .amb-knob-indicator {
126
+ /* The body is the smooth circle at the tooth-root radius: it carries
127
+ the ambient drop shadow (kept circular — the teeth are too fine to
128
+ read in a penumbra) while the clipped face above it supplies the
129
+ knurled silhouette. */
130
+ .amb-knob-body {
71
131
  position: absolute;
72
- top: 6px;
132
+ inset: calc(var(--ambx-grid) / 2);
133
+ border-radius: 50%;
134
+ }
135
+
136
+ /* The rotating face: clipped to the straight-knurl silhouette (the
137
+ inline SVG clipPath in AmbientKnob), opaque at the knob's surface
138
+ color, with per-tooth flank shading — a lit leading flank and a
139
+ shaded trailing flank every 10deg, phase-aligned with the clip's
140
+ teeth (conic 0 is rotated to the +x axis where the tooth path
141
+ starts) — under the smooth top disc. */
142
+ .amb-knob-face {
143
+ position: absolute;
144
+ inset: 0;
145
+ background-color: hsl(
146
+ var(--amb-light-hue)
147
+ var(--amb-light-saturation)
148
+ calc(
149
+ var(--amb-key-light-intensity) * 31.5% +
150
+ var(--amb-fill-light-intensity) * 26.4% + 43.6%
151
+ )
152
+ );
153
+ background-image:
154
+ radial-gradient(
155
+ circle,
156
+ hsl(
157
+ var(--amb-light-hue)
158
+ var(--amb-light-saturation)
159
+ calc(
160
+ var(--amb-key-light-intensity) * 31.5% +
161
+ var(--amb-fill-light-intensity) * 26.4% + 43.6%
162
+ )
163
+ ) 0 60%,
164
+ transparent 61%
165
+ ),
166
+ repeating-conic-gradient(
167
+ from 90deg,
168
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 100% /
169
+ calc(var(--amb-key-light-intensity) * 0.35)) 0deg 1.4deg,
170
+ transparent 2.2deg 4.6deg,
171
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 0% /
172
+ calc((var(--amb-key-light-intensity) - var(--amb-fill-light-intensity))
173
+ * 0.3 + 0.16)) 5deg 6.4deg,
174
+ transparent 7.2deg 10deg
175
+ );
176
+ }
177
+
178
+ /* Fluted face (flute variant — the OP-Z-style referent: 14 broad
179
+ ridges split by narrow grooves, pitch 25.714deg). Flank bands sit on
180
+ the clip's rising (0–2.1deg) and falling (18.5–20.6deg) flanks;
181
+ deeper flutes catch more key light, so both bands run stronger than
182
+ the 36-rib knurl's. */
183
+ .amb-knob-face-flute {
184
+ background-image:
185
+ radial-gradient(
186
+ circle,
187
+ hsl(
188
+ var(--amb-light-hue)
189
+ var(--amb-light-saturation)
190
+ calc(
191
+ var(--amb-key-light-intensity) * 31.5% +
192
+ var(--amb-fill-light-intensity) * 26.4% + 43.6%
193
+ )
194
+ ) 0 60%,
195
+ transparent 61%
196
+ ),
197
+ repeating-conic-gradient(
198
+ from 90deg,
199
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 100% /
200
+ calc(var(--amb-key-light-intensity) * 0.45)) 0deg 2.1deg,
201
+ transparent 3.2deg 17.4deg,
202
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 0% /
203
+ calc((var(--amb-key-light-intensity) - var(--amb-fill-light-intensity))
204
+ * 0.35 + 0.2)) 18.5deg 20.6deg,
205
+ transparent 21.6deg 25.7143deg
206
+ );
207
+ }
208
+
209
+ /* Fine knurl (cap and wheel variants: 48 ribs, pitch 7.5deg) — the
210
+ 36-rib pattern's angles scaled by 3/4, same flank alphas. */
211
+ .amb-knob-face-fine {
212
+ background-image:
213
+ radial-gradient(
214
+ circle,
215
+ hsl(
216
+ var(--amb-light-hue)
217
+ var(--amb-light-saturation)
218
+ calc(
219
+ var(--amb-key-light-intensity) * 31.5% +
220
+ var(--amb-fill-light-intensity) * 26.4% + 43.6%
221
+ )
222
+ ) 0 60%,
223
+ transparent 61%
224
+ ),
225
+ repeating-conic-gradient(
226
+ from 90deg,
227
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 100% /
228
+ calc(var(--amb-key-light-intensity) * 0.35)) 0deg 1.05deg,
229
+ transparent 1.65deg 3.45deg,
230
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 0% /
231
+ calc((var(--amb-key-light-intensity) - var(--amb-fill-light-intensity))
232
+ * 0.3 + 0.16)) 3.75deg 4.8deg,
233
+ transparent 5.4deg 7.5deg
234
+ );
235
+ }
236
+
237
+ /* Encoder cap (cap variant — the OP-1-style referent's smooth
238
+ contrasting top disc, 0.35mm proud of the knurl): the accent disc
239
+ replaces the surface-colored center disc over the fine knurl. */
240
+ .amb-knob-face-cap {
241
+ background-image:
242
+ radial-gradient(circle, var(--amb-highlight-color) 0 65%, transparent 66%),
243
+ repeating-conic-gradient(
244
+ from 90deg,
245
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 100% /
246
+ calc(var(--amb-key-light-intensity) * 0.35)) 0deg 1.05deg,
247
+ transparent 1.65deg 3.45deg,
248
+ hsl(var(--amb-light-hue) var(--amb-light-saturation) 0% /
249
+ calc((var(--amb-key-light-intensity) - var(--amb-fill-light-intensity))
250
+ * 0.3 + 0.16)) 3.75deg 4.8deg,
251
+ transparent 5.4deg 7.5deg
252
+ );
253
+ }
254
+
255
+ .amb-knob-indicator-dot {
256
+ position: absolute;
257
+ top: 16%;
73
258
  left: 50%;
74
259
  transform: translateX(-50%);
260
+ width: calc(var(--ambx-grid) * 2);
261
+ height: calc(var(--ambx-grid) * 2);
262
+ border-radius: 50%;
263
+ background: var(--amb-highlight-color);
75
264
  }
76
265
 
77
- .amb-knob-grip {
78
- display: flex;
79
- flex-direction: column;
80
- gap: 2px;
266
+ /* Centered dot (flute variant): the OP-Z referent's dot sits in the
267
+ middle of the cap (dot_frac 0.32), not at the rim. */
268
+ .amb-knob-indicator-dot-center {
269
+ top: 50%;
270
+ transform: translate(-50%, -50%);
271
+ width: calc(var(--ambx-grid) * 4.5);
272
+ height: calc(var(--ambx-grid) * 4.5);
81
273
  }
82
274
 
275
+ /* Radial indicator line (line variant): the referent's accent bar,
276
+ center to ~85% of the cap radius, pointing at 12 o'clock. */
277
+ .amb-knob-indicator-line {
278
+ position: absolute;
279
+ top: 12%;
280
+ left: 50%;
281
+ transform: translateX(-50%);
282
+ width: calc(var(--ambx-grid) * 0.75);
283
+ height: 34%;
284
+ border-radius: calc(var(--ambx-grid) * 0.375);
285
+ background: var(--amb-highlight-color);
286
+ }
287
+
288
+ .amb-knob:hover .amb-knob-indicator-dot,
289
+ .amb-knob:hover .amb-knob-indicator-line {
290
+ background: color-mix(in oklab, var(--amb-highlight-color), white 25%);
291
+ }
292
+
293
+ /* Tracks are grounded grooves (.amb-groove supplies the recess: lit-wall
294
+ shadow band + far-wall bounce) with a lume interior — dark in bright
295
+ light, glowing in low light — overriding the groove's neutral floor. */
83
296
  .amb-fader {
84
297
  position: relative;
85
298
  width: var(--ambx-grid);
@@ -88,6 +301,9 @@
88
301
  background-color: var(--amb-lume);
89
302
  }
90
303
 
304
+ /* Fader thumb: the referent (fader.py) is a pill cap on a stem — 7mm
305
+ tall (thickness 1.5) riding 2.2mm above the plate (elevation 0.28) —
306
+ with a single grip line across the top. */
91
307
  .amb-fader-thumb {
92
308
  position: absolute;
93
309
  width: calc(var(--ambx-grid) * 6);
@@ -95,55 +311,41 @@
95
311
  left: 50%;
96
312
  top: 50%;
97
313
  transform: translate(-50%, -50%);
98
- border-radius: calc(var(--ambx-grid) * 3);
314
+ border-radius: calc(var(--ambx-grid) * 2);
99
315
  display: grid;
100
316
  place-items: center;
317
+ --amb-thickness: 1.5;
318
+ --amb-elevation: 0.28;
101
319
  }
102
320
 
103
- .amb-fader-grip {
104
- display: flex;
105
- gap: 2px;
106
- }
107
-
108
- .amb-fader-dot {
109
- width: 3px;
110
- height: 3px;
111
- border-radius: 50%;
112
- background: radial-gradient(
113
- circle at 30% 30%,
114
- color-mix(in oklab, var(--amb-lume), white 30%) 0%,
115
- var(--amb-lume) 55%,
116
- color-mix(in oklab, var(--amb-lume), black 35%) 100%
117
- );
321
+ .amb-fader-gripline {
322
+ width: 70%;
323
+ height: 2px;
324
+ border-radius: 1px;
325
+ background: var(--amb-lume);
118
326
  }
119
327
 
328
+ /* Slider: the referent (slider.py) is a shallow concave channel (1mm
329
+ deep = thickness 0.22), not a through-slot, with a domed disc thumb
330
+ gliding over it. */
120
331
  .amb-slider {
121
332
  position: relative;
122
- height: var(--ambx-grid);
333
+ height: calc(var(--ambx-grid) * 1.5);
123
334
  width: calc(var(--ambx-grid) * 30);
124
335
  border-radius: var(--ambx-grid);
125
336
  background-color: var(--amb-lume);
337
+ --amb-thickness: 0.22;
126
338
  }
127
339
 
128
340
  .amb-slider-thumb {
129
341
  position: absolute;
130
- width: calc(var(--ambx-grid) * 9);
342
+ width: calc(var(--ambx-grid) * 6);
131
343
  height: calc(var(--ambx-grid) * 6);
132
344
  top: 50%;
133
345
  left: 50%;
134
346
  transform: translate(-50%, -50%);
135
- border-radius: calc(var(--ambx-grid) * 3);
136
- display: grid;
137
- place-items: center;
138
- }
139
-
140
- .amb-slider-grip {
141
- display: flex;
142
- flex-direction: column;
143
- gap: 2px;
144
- box-shadow:
145
- inset 0 var(--ambx-grid-quarter) var(--ambx-grid-quarter) rgb(255 255 255 / 0.28),
146
- inset 0 calc(var(--ambx-grid-quarter) * -1) var(--ambx-grid-quarter) rgb(0 0 0 / 0.22);
347
+ border-radius: 50%;
348
+ --amb-thickness: 0.7;
147
349
  }
148
350
 
149
351
  .amb-led {
@@ -164,16 +366,65 @@
164
366
  box-shadow: none;
165
367
  }
166
368
 
369
+ /* Switch: a slide switch like the referent (switch.py) — a pill riding
370
+ in a dark stadium recess (well 1.5mm deep = thickness 0.33; the pill
371
+ stands 2.6mm above the recess floor = thickness 0.58), with an
372
+ optional LED above. The pill slides, nothing presses. */
167
373
  .amb-switch {
168
- position: relative;
169
- width: calc(var(--ambx-grid) * 6);
170
- height: calc(var(--ambx-grid) * 6);
171
- border-radius: 50%;
172
374
  cursor: pointer;
173
- border: medium solid var(--amb-lume);
375
+ border: none;
376
+ padding: 0;
377
+ background: none;
378
+ display: inline-grid;
379
+ justify-items: center;
380
+ gap: var(--ambx-grid);
174
381
  margin: 0;
175
382
  }
176
383
 
384
+ .amb-switch-track {
385
+ position: relative;
386
+ display: block;
387
+ width: var(--ambx-switch-w);
388
+ height: var(--ambx-switch-h);
389
+ border-radius: calc(var(--ambx-switch-h) / 2);
390
+ background-color: var(--amb-lume);
391
+ --amb-thickness: 0.33;
392
+ }
393
+
394
+ .amb-switch-pill {
395
+ position: absolute;
396
+ top: 50%;
397
+ left: 0;
398
+ width: calc(var(--ambx-switch-w) * 0.52);
399
+ height: calc(var(--ambx-switch-h) * 0.78);
400
+ border-radius: calc(var(--ambx-switch-h) * 0.39);
401
+ transform: translate(calc(var(--ambx-switch-h) * 0.11), -50%);
402
+ transition: transform 140ms ease;
403
+ --amb-thickness: 0.58;
404
+ }
405
+
406
+ .amb-switch-on .amb-switch-pill {
407
+ transform: translate(
408
+ calc(var(--ambx-switch-w) - 100% - var(--ambx-switch-h) * 0.11),
409
+ -50%
410
+ );
411
+ }
412
+
413
+ .amb-switch:hover .amb-switch-pill {
414
+ background-color: color-mix(
415
+ in oklab,
416
+ hsl(
417
+ var(--amb-light-hue)
418
+ var(--amb-light-saturation)
419
+ calc(
420
+ var(--amb-key-light-intensity) * 31.5% +
421
+ var(--amb-fill-light-intensity) * 26.4% + 43.6%
422
+ )
423
+ ),
424
+ var(--amb-highlight-color) 12%
425
+ );
426
+ }
427
+
177
428
  .ambx-panel {
178
429
  border-radius: calc(var(--ambx-grid) * 4);
179
430
  padding: calc(var(--ambx-grid) * 4);
@@ -185,29 +436,23 @@
185
436
  }
186
437
 
187
438
  .ambx-switch {
188
- --ambx-switch-size: calc(var(--ambx-grid) * 6);
189
- width: var(--ambx-switch-size);
190
- height: var(--ambx-switch-size);
191
- display: inline-grid;
192
- place-items: center;
193
- padding: 0;
439
+ --ambx-switch-w: calc(var(--ambx-grid) * 12);
440
+ --ambx-switch-h: calc(var(--ambx-grid) * 6);
194
441
  }
195
442
 
196
443
  .ambx-switch-sm {
197
- --ambx-switch-size: calc(var(--ambx-grid) * 5);
444
+ --ambx-switch-w: calc(var(--ambx-grid) * 10);
445
+ --ambx-switch-h: calc(var(--ambx-grid) * 5);
198
446
  }
199
447
 
200
448
  .ambx-switch-md {
201
- --ambx-switch-size: calc(var(--ambx-grid) * 6);
449
+ --ambx-switch-w: calc(var(--ambx-grid) * 12);
450
+ --ambx-switch-h: calc(var(--ambx-grid) * 6);
202
451
  }
203
452
 
204
453
  .ambx-switch-lg {
205
- --ambx-switch-size: calc(var(--ambx-grid) * 8);
206
- }
207
-
208
- .amb-switch-on {
209
- box-shadow:
210
- inset 0 0 0 var(--ambx-grid-quarter) rgb(255 255 255 / 0.25);
454
+ --ambx-switch-w: calc(var(--ambx-grid) * 16);
455
+ --ambx-switch-h: calc(var(--ambx-grid) * 8);
211
456
  }
212
457
 
213
458
  .ambx-stack {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambientcss/components",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "React skeuomorphic components powered by Ambient CSS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,7 +44,7 @@
44
44
  "react-dom": "^18.3.0 || ^19.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@ambientcss/css": "1.2.0"
47
+ "@ambientcss/css": "2.0.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/react": "^19.0.8",
@@ -1,22 +1,45 @@
1
1
  import type { ButtonHTMLAttributes } from "react";
2
2
  import { cn } from "../lib/cn";
3
3
 
4
+ /* Cap silhouettes from the referent lineup (ambient3d/generate.py):
5
+ - "pill": the default wide stadium key (transport-key style)
6
+ - "round": a circular key — pair with material="shiny" for the
7
+ machined metal-button look
8
+ - "square": a squarer, flatter pad (EP-133-style, tighter corners,
9
+ lower cap) */
10
+ export type AmbientButtonShape = "pill" | "round" | "square";
11
+
4
12
  export interface AmbientButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5
13
  material?: "matte" | "shiny" | "glass";
14
+ shape?: AmbientButtonShape;
6
15
  }
7
16
 
8
- export function AmbientButton({ className, children, material = "matte", ...props }: AmbientButtonProps) {
17
+ export function AmbientButton({
18
+ className,
19
+ children,
20
+ material = "matte",
21
+ shape = "pill",
22
+ ...props
23
+ }: AmbientButtonProps) {
9
24
  return (
10
25
  <button
11
26
  type="button"
12
27
  className={cn(
13
- "ambient amb-button amb-chamfer amb-elevation-1 ambx-button amb-heading-3",
14
- material === "matte" ? "amb-mat-matte" : material === "shiny" ? "amb-mat-shiny" : "amb-mat-glass",
28
+ "amb-button amb-groove ambx-button",
29
+ shape === "round" && "amb-button-round",
30
+ shape === "square" && "amb-button-square",
15
31
  className
16
32
  )}
17
33
  {...props}
18
34
  >
19
- {children}
35
+ <span
36
+ className={cn(
37
+ "amb-button-cap ambient amb-chamfer amb-surface amb-heading-3",
38
+ material === "matte" ? "amb-mat-matte" : material === "shiny" ? "amb-mat-shiny" : "amb-mat-glass"
39
+ )}
40
+ >
41
+ {children}
42
+ </span>
20
43
  </button>
21
44
  );
22
45
  }
@@ -91,7 +91,7 @@ export function AmbientFader({
91
91
  return (
92
92
  <div className={cn("ambx-stack", className)} {...props}>
93
93
  <div
94
- className="amb-fader ambx-fader"
94
+ className="amb-fader amb-groove ambx-fader"
95
95
  ref={trackRef}
96
96
  role="slider"
97
97
  aria-label={label}
@@ -109,14 +109,10 @@ export function AmbientFader({
109
109
  onKeyDown={onKeyDown}
110
110
  >
111
111
  <div
112
- className={cn("amb-fader-thumb ambient amb-fillet amb-elevation-1 ambx-fader-thumb", material !== "glass" && "amb-surface-concave", material && `amb-mat-${material}`)}
112
+ className={cn("amb-fader-thumb ambient amb-fillet ambx-fader-thumb", material !== "glass" && "amb-surface-concave", material && `amb-mat-${material}`)}
113
113
  style={{ top: `${100 - percent}%` }}
114
114
  >
115
- <div className="amb-fader-grip">
116
- <span className="amb-fader-dot" />
117
- <span className="amb-fader-dot" />
118
- <span className="amb-fader-dot" />
119
- </div>
115
+ <span className="amb-fader-gripline" />
120
116
  </div>
121
117
  </div>
122
118
  {label ? (