@mks2508/mks-ui 0.5.2 → 0.5.7

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 (72) hide show
  1. package/dist/react-ui/index.js +8 -3
  2. package/dist/react-ui/primitives/index.js +5 -0
  3. package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +120 -0
  4. package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -0
  5. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +10 -0
  6. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -0
  7. package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +190 -0
  8. package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.d.ts +7 -0
  9. package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.d.ts.map +1 -0
  10. package/dist/react-ui/primitives/waapi/Gooey/GooeyFilter.js +78 -0
  11. package/dist/react-ui/primitives/waapi/Gooey/MorphPath.d.ts +7 -0
  12. package/dist/react-ui/primitives/waapi/Gooey/MorphPath.d.ts.map +1 -0
  13. package/dist/react-ui/primitives/waapi/Gooey/MorphPath.js +51 -0
  14. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts +94 -0
  15. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.d.ts.map +1 -0
  16. package/dist/react-ui/primitives/waapi/Gooey/gooey-utils.js +182 -0
  17. package/dist/react-ui/primitives/waapi/Gooey/index.d.ts +28 -0
  18. package/dist/react-ui/primitives/waapi/Gooey/index.d.ts.map +1 -0
  19. package/dist/react-ui/primitives/waapi/Gooey/index.js +5 -0
  20. package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.d.ts +7 -0
  21. package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.d.ts.map +1 -0
  22. package/dist/react-ui/primitives/waapi/Gooey/useMorphPath.js +47 -0
  23. package/dist/react-ui/primitives/waapi/index.d.ts +2 -0
  24. package/dist/react-ui/primitives/waapi/index.d.ts.map +1 -1
  25. package/dist/react-ui/primitives/waapi/index.js +6 -0
  26. package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts +26 -16
  27. package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts.map +1 -1
  28. package/dist/react-ui/ui/DataCard/DataCard.styles.js +36 -74
  29. package/dist/react-ui/ui/DataCard/DataCard.types.d.ts +50 -70
  30. package/dist/react-ui/ui/DataCard/DataCard.types.d.ts.map +1 -1
  31. package/dist/react-ui/ui/DataCard/index.d.ts +24 -93
  32. package/dist/react-ui/ui/DataCard/index.d.ts.map +1 -1
  33. package/dist/react-ui/ui/DataCard/index.js +76 -118
  34. package/dist/react-ui/ui/DynamicToggle/DynamicToggle-DOR3Ld-k.css +376 -0
  35. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +376 -0
  36. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.js +0 -0
  37. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts +20 -8
  38. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts.map +1 -1
  39. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +55 -27
  40. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +69 -14
  41. package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
  42. package/dist/react-ui/ui/DynamicToggle/index.d.ts +22 -20
  43. package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
  44. package/dist/react-ui/ui/DynamicToggle/index.js +133 -96
  45. package/dist/react-ui/ui/Switch/index.js +1 -1
  46. package/dist/react-ui/ui/index.js +2 -2
  47. package/package.json +2 -2
  48. package/src/css.d.ts +1 -0
  49. package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +141 -0
  50. package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +217 -0
  51. package/src/react-ui/primitives/waapi/Gooey/GooeyFilter.tsx +77 -0
  52. package/src/react-ui/primitives/waapi/Gooey/MorphPath.tsx +58 -0
  53. package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +253 -0
  54. package/src/react-ui/primitives/waapi/Gooey/index.ts +50 -0
  55. package/src/react-ui/primitives/waapi/Gooey/useMorphPath.ts +48 -0
  56. package/src/react-ui/primitives/waapi/index.ts +23 -0
  57. package/src/react-ui/ui/DataCard/DataCard.styles.ts +45 -101
  58. package/src/react-ui/ui/DataCard/DataCard.types.ts +52 -73
  59. package/src/react-ui/ui/DataCard/index.tsx +118 -184
  60. package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +320 -94
  61. package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +60 -40
  62. package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +101 -14
  63. package/src/react-ui/ui/DynamicToggle/index.tsx +172 -96
  64. package/src/react-ui/ui/DynamicToggle/prototype-v7-ios.html +413 -0
  65. package/src/react-ui/ui/DynamicToggle/prototype-v7.html +615 -0
  66. package/src/react-ui/ui/DynamicToggle/prototype-v8-gooey-safari.html +560 -0
  67. package/src/react-ui/ui/DynamicToggle/prototype-v8b-react-structure.html +227 -0
  68. package/src/react-ui/ui/DynamicToggle/prototype.html +419 -0
  69. package/src/react-ui/ui/Switch/index.tsx +1 -1
  70. /package/dist/react-ui/blocks/Terminal/panel/{terminal-filter-dropdown.module-DAcl_XQZ.css → terminal-filter-dropdown.module-C6oDcFBS.css} +0 -0
  71. /package/dist/react-ui/blocks/Terminal/panel/{terminal-session-tabs.module-DNAop5e3.css → terminal-session-tabs.module-D_-sgyza.css} +0 -0
  72. /package/dist/react-ui/components/MorphingPopover/{morphing-popover.module-BJrjXisF.css → morphing-popover.module-B1ftlaYj.css} +0 -0
@@ -0,0 +1,413 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>DynamicToggle — Sileo-style gooey (Safari-safe)</title>
7
+ <style>
8
+ :root {
9
+ --fg: #e2e8f0;
10
+ --bg: #0f172a;
11
+ --card: #1e293b;
12
+ --muted: #64748b;
13
+ --border: #334155;
14
+ --duration: 0.22;
15
+ --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
16
+ --drop-off: 0.4;
17
+ }
18
+
19
+ * { box-sizing: border-box; margin: 0; }
20
+ body {
21
+ min-height: 100vh;
22
+ display: flex; flex-direction: column; align-items: center;
23
+ padding: 4rem 1rem; gap: 2rem;
24
+ background: var(--bg);
25
+ font-family: system-ui, -apple-system, sans-serif;
26
+ color: var(--fg);
27
+ }
28
+ h3 { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 2px; }
29
+ .row { display: flex; gap: 2rem; align-items: end; flex-wrap: wrap; justify-content: center; }
30
+ .sr-only {
31
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
32
+ overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border-width: 0;
33
+ }
34
+
35
+ /* ================================================================
36
+ * CONTROL BASE
37
+ * ================================================================ */
38
+ .control {
39
+ --w: 260px;
40
+ --h: 38px;
41
+ --radius: 9999px;
42
+ --font: 0.75rem;
43
+ --bubble-h-pct: 40;
44
+ --bubble-inset-pct: 20;
45
+ --_r: min(var(--radius), calc(var(--h) / 2));
46
+
47
+ position: relative;
48
+ width: var(--w);
49
+ height: var(--h);
50
+ padding: 2px;
51
+ margin-top: calc(var(--h) * 0.5);
52
+ /* Sileo: GPU layer + containment on root */
53
+ transform: translateZ(0);
54
+ contain: layout style;
55
+ overflow: visible;
56
+ }
57
+
58
+ .control__track {
59
+ display: grid; place-items: center;
60
+ grid-template-columns: repeat(4, 1fr);
61
+ width: 100%; height: 100%;
62
+ position: relative;
63
+ z-index: 1;
64
+ }
65
+
66
+ .indicator {
67
+ position: absolute;
68
+ width: 50%; left: 0; top: 0; bottom: 0;
69
+ background: var(--fg);
70
+ border-radius: var(--radius);
71
+ transition: translate calc(var(--duration) * 1s) var(--ease);
72
+ }
73
+
74
+ label {
75
+ display: inline-grid; place-items: center;
76
+ height: 100%; width: 100%;
77
+ cursor: pointer; font-size: var(--font);
78
+ color: var(--fg); z-index: 2; font-weight: 500;
79
+ }
80
+ .control__track > label { color: var(--card); }
81
+
82
+ .group {
83
+ width: 100%; height: 100%;
84
+ display: grid; position: relative;
85
+ grid-template-columns: 1fr 1fr;
86
+ container-type: size;
87
+ overflow: hidden;
88
+ }
89
+ .group, .control__track > label:nth-of-type(1) { grid-column: span 2; }
90
+
91
+ .group .indicator {
92
+ background: var(--fg); left: 50%; top: 0;
93
+ translate: -50% 0;
94
+ pointer-events: none;
95
+ transition: translate calc(var(--duration) * 1s) var(--ease),
96
+ clip-path calc(var(--duration) * 1s) var(--ease),
97
+ background calc(var(--duration) * 1s) var(--ease);
98
+ clip-path: inset(73cqh calc(50% + 1px) calc(27cqh - 2px) calc(50% - 3px) round var(--radius));
99
+ }
100
+
101
+ /* ── Pseudo text ── */
102
+ .group::before {
103
+ content: attr(data-label);
104
+ position: absolute; left: 50%; top: 50%;
105
+ translate: -50% -80%;
106
+ color: var(--fg); font-size: var(--font); font-weight: 500;
107
+ z-index: 2; white-space: nowrap; pointer-events: none;
108
+ transition: scale calc(var(--duration) * 1s) var(--ease),
109
+ translate calc(var(--duration) * 1s) var(--ease),
110
+ opacity calc(var(--duration) * 1s) var(--ease);
111
+ }
112
+ .group::after {
113
+ content: attr(data-opts);
114
+ position: absolute; left: 50%; top: 50%;
115
+ translate: -50% 20%;
116
+ color: var(--muted); font-size: calc(var(--font) * 0.85);
117
+ opacity: 0.6; z-index: 2; white-space: nowrap; pointer-events: none;
118
+ transition: opacity calc(var(--duration) * 1s) var(--ease);
119
+ }
120
+ .group:not([data-opts])::after { content: none; }
121
+
122
+ .group label {
123
+ color: var(--muted); cursor: pointer; z-index: 2;
124
+ transition: color calc(var(--duration) * 1s) var(--ease),
125
+ opacity calc(var(--duration) * 1s) var(--ease),
126
+ translate calc(var(--duration) * 1s) var(--ease);
127
+ }
128
+ .group label span {
129
+ display: grid; place-items: center; height: 100%; width: 100%;
130
+ border-radius: var(--radius);
131
+ transition: scale calc(var(--duration) * 1s) var(--ease);
132
+ }
133
+
134
+ /* Collapsed modes */
135
+ .group[data-collapsed="title"]::before { translate: -50% -50%; }
136
+ .group[data-collapsed="title"]::after { display: none; }
137
+ .group[data-collapsed="title"]:not(:has(:checked)) label { opacity: 0; translate: 0 30%; }
138
+ .group[data-collapsed="title"]:not(:has(:checked)) label span { scale: 0.5; }
139
+
140
+ .group[data-collapsed="opts"]::before { display: none; }
141
+ .group[data-collapsed="opts"]::after { translate: -50% -50%; font-size: var(--font); opacity: 0.7; }
142
+ .group[data-collapsed="opts"]:not(:has(:checked)) label { opacity: 0; translate: 0 30%; }
143
+ .group[data-collapsed="opts"]:not(:has(:checked)) label span { scale: 0.5; }
144
+
145
+ /* Track states */
146
+ .control__track:has(> :checked) > label { color: var(--card); }
147
+ .control__track:not(:has(> :checked)) > label { color: var(--fg); opacity: var(--drop-off); }
148
+ .control__track:not(:has(> :checked)) > .indicator { translate: 100% 0; }
149
+ .control__track:has(> :checked) .group .indicator { background: transparent; }
150
+
151
+ /* Group expanded */
152
+ .control--goo .group:has(:checked)::before { opacity: 0; translate: -50% -80%; scale: 1; }
153
+ .group:has(:checked)::after { opacity: 0; }
154
+ .group:has(:checked) label { opacity: 0.75; color: var(--muted); translate: 0 0; }
155
+ .group:has(:checked) label span { scale: 1; }
156
+ .group:has(:checked) .indicator { background: var(--card); clip-path: inset(0 0 0 0 round var(--radius)); }
157
+ .group:has(:nth-of-type(1):checked) label:nth-of-type(1),
158
+ .group:has(:nth-of-type(2):checked) label:nth-of-type(2) { color: var(--fg); opacity: 1; }
159
+ .group:has(:nth-of-type(1):checked) .indicator { translate: -100% 0; }
160
+ .group:has(:nth-of-type(2):checked) .indicator { translate: 0 0; }
161
+
162
+ /* ================================================================
163
+ * SILEO-STYLE GOOEY: SVG filter on a canvas div, SVG rects inside
164
+ * Key: animated content is SVG <rect>, not HTML div.
165
+ * Safari re-renders SVG filter smoothly when SVG children change.
166
+ * ================================================================ */
167
+ .control--goo {
168
+ background: transparent;
169
+ border: none;
170
+ }
171
+
172
+ .goo-canvas {
173
+ position: absolute;
174
+ inset: 0;
175
+ pointer-events: none;
176
+ z-index: 0;
177
+ /* Sileo's Safari formula */
178
+ transform: translateZ(0);
179
+ contain: layout style;
180
+ overflow: visible;
181
+ }
182
+
183
+ .goo-svg {
184
+ position: absolute;
185
+ top: 0; left: 0;
186
+ overflow: visible;
187
+ pointer-events: none;
188
+ }
189
+
190
+ /* Bubble TEXT — outside the filtered SVG, on top */
191
+ .control--goo .bubble-label {
192
+ position: absolute;
193
+ z-index: 2;
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: center;
197
+ font-size: var(--font);
198
+ font-weight: 500;
199
+ color: var(--fg);
200
+ white-space: nowrap;
201
+ pointer-events: none;
202
+ opacity: 0;
203
+ transition: opacity calc(var(--duration) * 1s) var(--ease);
204
+ }
205
+ .control--goo:has(.group :checked) .bubble-label {
206
+ opacity: 1;
207
+ }
208
+
209
+ /* SIZES */
210
+ .control.sm { --w: 210px; --h: 30px; --font: 10px; }
211
+ .control.lg { --w: 320px; --h: 44px; --font: 14px; }
212
+ .control.xl { --w: 400px; --h: 52px; --font: 16px; }
213
+ .control.rounded { --radius: 12px; }
214
+ .control.square { --radius: 6px; }
215
+ </style>
216
+ </head>
217
+ <body>
218
+
219
+ <h3>Sileo-style gooey — SVG rects + filter (Safari-safe)</h3>
220
+ <p style="color:var(--muted);font-size:12px;max-width:600px;text-align:center">
221
+ SVG filter applied to a canvas div. Pill + bubble are SVG <code>&lt;rect&gt;</code> elements
222
+ animated via WAAPI. Safari renders filter smoothly because changes are SVG-internal.
223
+ </p>
224
+
225
+ <h3>collapsed="title" — Sizes</h3>
226
+ <div class="row">
227
+ <div><h3>SM</h3>
228
+ <div class="control sm control--goo" data-goo>
229
+ <div class="goo-canvas"><svg class="goo-svg"></svg></div>
230
+ <div class="bubble-label">Changes</div>
231
+ <div class="control__track"><div class="indicator"></div>
232
+ <label for="gs-a">Tree</label><input class="sr-only" type="radio" name="gs" id="gs-a" checked>
233
+ <div class="group" data-collapsed="title" data-label="Changes"><div class="indicator"></div>
234
+ <label for="gs-b"><span>Flat</span></label><input class="sr-only" type="radio" name="gs" id="gs-b">
235
+ <label for="gs-c"><span>Grp</span></label><input class="sr-only" type="radio" name="gs" id="gs-c">
236
+ </div></div></div></div>
237
+
238
+ <div><h3>Default</h3>
239
+ <div class="control control--goo" data-goo>
240
+ <div class="goo-canvas"><svg class="goo-svg"></svg></div>
241
+ <div class="bubble-label">Premium</div>
242
+ <div class="control__track"><div class="indicator"></div>
243
+ <label for="gd-a">Free</label><input class="sr-only" type="radio" name="gd" id="gd-a" checked>
244
+ <div class="group" data-collapsed="title" data-label="Premium"><div class="indicator"></div>
245
+ <label for="gd-b"><span>Solo</span></label><input class="sr-only" type="radio" name="gd" id="gd-b">
246
+ <label for="gd-c"><span>Team</span></label><input class="sr-only" type="radio" name="gd" id="gd-c">
247
+ </div></div></div></div>
248
+
249
+ <div><h3>LG</h3>
250
+ <div class="control lg control--goo" data-goo>
251
+ <div class="goo-canvas"><svg class="goo-svg"></svg></div>
252
+ <div class="bubble-label">Billing</div>
253
+ <div class="control__track"><div class="indicator"></div>
254
+ <label for="gl-a">Annual</label><input class="sr-only" type="radio" name="gl" id="gl-a" checked>
255
+ <div class="group" data-collapsed="title" data-label="Billing"><div class="indicator"></div>
256
+ <label for="gl-b"><span>Monthly</span></label><input class="sr-only" type="radio" name="gl" id="gl-b">
257
+ <label for="gl-c"><span>Weekly</span></label><input class="sr-only" type="radio" name="gl" id="gl-c">
258
+ </div></div></div></div>
259
+ </div>
260
+
261
+ <h3>Shapes</h3>
262
+ <div class="row">
263
+ <div class="control rounded control--goo" data-goo>
264
+ <div class="goo-canvas"><svg class="goo-svg"></svg></div>
265
+ <div class="bubble-label">Theme</div>
266
+ <div class="control__track"><div class="indicator"></div>
267
+ <label for="gr-a">System</label><input class="sr-only" type="radio" name="gr" id="gr-a">
268
+ <div class="group" data-collapsed="title" data-label="Theme"><div class="indicator"></div>
269
+ <label for="gr-b"><span>Light</span></label><input class="sr-only" type="radio" name="gr" id="gr-b" checked>
270
+ <label for="gr-c"><span>Dark</span></label><input class="sr-only" type="radio" name="gr" id="gr-c">
271
+ </div></div></div>
272
+
273
+ <div class="control square control--goo" data-goo>
274
+ <div class="goo-canvas"><svg class="goo-svg"></svg></div>
275
+ <div class="bubble-label">Output</div>
276
+ <div class="control__track"><div class="indicator"></div>
277
+ <label for="gsq-a">Input</label><input class="sr-only" type="radio" name="gsq" id="gsq-a">
278
+ <div class="group" data-collapsed="title" data-label="Output"><div class="indicator"></div>
279
+ <label for="gsq-b"><span>JSON</span></label><input class="sr-only" type="radio" name="gsq" id="gsq-b" checked>
280
+ <label for="gsq-c"><span>XML</span></label><input class="sr-only" type="radio" name="gsq" id="gsq-c">
281
+ </div></div></div>
282
+ </div>
283
+
284
+ <h3>collapsed="opts"</h3>
285
+ <div class="row">
286
+ <div class="control control--goo" data-goo>
287
+ <div class="goo-canvas"><svg class="goo-svg"></svg></div>
288
+ <div class="bubble-label">Premium</div>
289
+ <div class="control__track"><div class="indicator"></div>
290
+ <label for="go-a">Free</label><input class="sr-only" type="radio" name="go" id="go-a" checked>
291
+ <div class="group" data-collapsed="opts" data-label="Premium" data-opts="Solo · Team"><div class="indicator"></div>
292
+ <label for="go-b"><span>Solo</span></label><input class="sr-only" type="radio" name="go" id="go-b">
293
+ <label for="go-c"><span>Team</span></label><input class="sr-only" type="radio" name="go" id="go-c">
294
+ </div></div></div>
295
+ </div>
296
+
297
+ <script>
298
+ // ================================================================
299
+ // SILEO-STYLE GOOEY ENGINE
300
+ // SVG filter + animated SVG rects (not HTML divs)
301
+ // ================================================================
302
+
303
+ const BLUR_RATIO = 0.15;
304
+ const DURATION = 0.25; // seconds
305
+ const OUTLINE_COLOR = '#334155';
306
+
307
+ let filterCounter = 0;
308
+
309
+ document.querySelectorAll('[data-goo]').forEach(control => {
310
+ const canvas = control.querySelector('.goo-canvas');
311
+ const svg = control.querySelector('.goo-svg');
312
+ const group = control.querySelector('.group');
313
+ const bubbleLabel = control.querySelector('.bubble-label');
314
+ const style = getComputedStyle(control);
315
+
316
+ const w = parseFloat(style.getPropertyValue('--w'));
317
+ const h = parseFloat(style.getPropertyValue('--h'));
318
+ const r = parseFloat(style.getPropertyValue('--radius'));
319
+ const effectiveR = Math.min(r, h / 2);
320
+ const bubbleHPct = parseFloat(style.getPropertyValue('--bubble-h-pct')) || 40;
321
+ const bubbleInsetPct = parseFloat(style.getPropertyValue('--bubble-inset-pct')) || 20;
322
+ const blur = Math.round(h * BLUR_RATIO);
323
+ const bubbleH = h * bubbleHPct / 100;
324
+ const bubbleInset = w * bubbleInsetPct / 100;
325
+ const bubbleW = w - 2 * bubbleInset;
326
+ const bubbleR = Math.min(effectiveR * 0.6, bubbleH * 0.45, 12);
327
+
328
+ // Create unique filter
329
+ const filterId = `goo-${filterCounter++}`;
330
+
331
+ // Build SVG content: filter defs + pill rect + bubble rect
332
+ const totalH = h + bubbleH;
333
+ svg.setAttribute('width', w);
334
+ svg.setAttribute('height', totalH);
335
+ svg.setAttribute('viewBox', `0 0 ${w} ${totalH}`);
336
+ svg.style.top = `-${bubbleH}px`;
337
+
338
+ svg.innerHTML = `
339
+ <defs>
340
+ <filter id="${filterId}" x="-20%" y="-20%" width="140%" height="140%"
341
+ color-interpolation-filters="sRGB">
342
+ <feGaussianBlur in="SourceGraphic" stdDeviation="${blur}" result="blur"/>
343
+ <feColorMatrix in="blur" mode="matrix"
344
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10" result="goo"/>
345
+ <feComposite in="SourceGraphic" in2="goo" operator="atop"/>
346
+ </filter>
347
+ </defs>
348
+ <rect class="pill-rect"
349
+ x="2" y="${bubbleH}" width="${w - 4}" height="${h - 4}" rx="${effectiveR}" ry="${effectiveR}"
350
+ fill="var(--card)"/>
351
+ <rect class="bubble-rect"
352
+ x="${bubbleInset}" y="${bubbleH}" width="${bubbleW}" height="0" rx="${bubbleR}" ry="${bubbleR}"
353
+ fill="var(--card)"/>
354
+ `;
355
+
356
+ // Apply filter + drop-shadow to canvas
357
+ canvas.style.filter = `url(#${filterId}) drop-shadow(0 0 0.5px ${OUTLINE_COLOR}) drop-shadow(0 0 0.5px ${OUTLINE_COLOR})`;
358
+
359
+ const pillRect = svg.querySelector('.pill-rect');
360
+ const bubbleRect = svg.querySelector('.bubble-rect');
361
+
362
+ // Position bubble label
363
+ bubbleLabel.style.left = bubbleInset + 'px';
364
+ bubbleLabel.style.right = bubbleInset + 'px';
365
+ bubbleLabel.style.height = bubbleH + 'px';
366
+ bubbleLabel.style.bottom = h + 'px';
367
+
368
+ // Animate bubble rect via WAAPI (like Sileo uses Motion)
369
+ let currentAnim = null;
370
+
371
+ function animateBubble(expand) {
372
+ if (currentAnim) currentAnim.cancel();
373
+
374
+ const targetY = expand ? bubbleH - bubbleH : bubbleH;
375
+ const targetH = expand ? bubbleH : 0;
376
+
377
+ currentAnim = bubbleRect.animate([
378
+ { // from current
379
+ y: bubbleRect.getAttribute('y'),
380
+ height: bubbleRect.getAttribute('height'),
381
+ },
382
+ { // to target
383
+ y: targetY,
384
+ height: targetH,
385
+ }
386
+ ], {
387
+ duration: DURATION * 1500,
388
+ easing: 'cubic-bezier(0.22, 0.61, 0.36, 1)',
389
+ fill: 'forwards',
390
+ });
391
+
392
+ currentAnim.onfinish = () => {
393
+ bubbleRect.setAttribute('y', targetY);
394
+ bubbleRect.setAttribute('height', targetH);
395
+ };
396
+ }
397
+
398
+ // Listen for radio changes
399
+ function checkState() {
400
+ const expanded = group.querySelector(':checked') !== null;
401
+ animateBubble(expanded);
402
+ }
403
+
404
+ control.querySelectorAll('input[type="radio"]').forEach(input => {
405
+ input.addEventListener('change', checkState);
406
+ });
407
+
408
+ // Initial state
409
+ checkState();
410
+ });
411
+ </script>
412
+ </body>
413
+ </html>