@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,560 @@
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>Prototype v8 — Gooey Safari vs Chrome</title>
7
+ <style>
8
+ :root {
9
+ --fg: #e2e8f0;
10
+ --bg: #0f172a;
11
+ --card: #1e293b;
12
+ --muted: #64748b;
13
+ --border: #334155;
14
+ }
15
+
16
+ * { box-sizing: border-box; margin: 0; }
17
+ body {
18
+ min-height: 100vh;
19
+ display: flex; flex-direction: column; align-items: center;
20
+ padding: 3rem 1rem; gap: 2rem;
21
+ background: var(--bg);
22
+ font-family: system-ui, -apple-system, sans-serif;
23
+ color: var(--fg);
24
+ }
25
+ h2 { font-size: 1rem; color: var(--fg); }
26
+ h3 { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 2px; }
27
+ .row { display: flex; gap: 3rem; flex-wrap: wrap; justify-content: center; }
28
+ .col { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; }
29
+ #log {
30
+ font-family: monospace; font-size: 12px; color: #22d3ee;
31
+ background: #0c0c1d; border: 1px solid #334155;
32
+ padding: 1rem; border-radius: 8px; width: 600px; max-width: 90vw;
33
+ white-space: pre-wrap;
34
+ }
35
+ button {
36
+ padding: 0.5rem 1.5rem; border-radius: 8px; border: 1px solid var(--border);
37
+ background: var(--card); color: var(--fg); cursor: pointer; font-size: 0.8rem;
38
+ }
39
+ button:hover { background: #334155; }
40
+ button.active { background: #22d3ee; color: #0f172a; border-color: #22d3ee; }
41
+
42
+ /* ── Gooey container ── */
43
+ .gooey-demo {
44
+ position: relative;
45
+ width: 260px;
46
+ height: 38px;
47
+ margin-top: 20px;
48
+ }
49
+ .gooey-canvas {
50
+ position: absolute; inset: 0;
51
+ border-radius: 9999px;
52
+ pointer-events: none;
53
+ transform: translateZ(0);
54
+ contain: layout style;
55
+ overflow: visible;
56
+ }
57
+ .gooey-canvas svg {
58
+ position: absolute;
59
+ top: -15px; /* -bubbleH */
60
+ left: 0;
61
+ overflow: visible;
62
+ }
63
+
64
+ /* ══ React structure CSS (for wrapped variants) ══ */
65
+ .dt-root {
66
+ position: relative; padding: 2px; user-select: none;
67
+ height: 38px; width: 260px; font-size: 12px; border-radius: 9999px;
68
+ background: transparent; border: 1px solid transparent;
69
+ overflow: visible; margin-top: 20px;
70
+ }
71
+ .dt-track {
72
+ position: relative; display: grid;
73
+ grid-template-columns: repeat(4, 1fr);
74
+ place-items: center; width: 100%; height: 100%;
75
+ z-index: 1;
76
+ }
77
+ .dt-group {
78
+ grid-column: span 2; position: relative;
79
+ width: 100%; height: 100%;
80
+ display: grid; grid-template-columns: repeat(2, 1fr);
81
+ container-type: size; overflow: hidden;
82
+ }
83
+ .dt-indicator {
84
+ position: absolute; width: 50%; left: 50%; top: 0; bottom: 0;
85
+ background: #e2e8f0; border-radius: 9999px;
86
+ pointer-events: none; z-index: 0;
87
+ }
88
+ .dt-option {
89
+ display: inline-grid; place-items: center;
90
+ cursor: pointer; font-weight: 500; z-index: 2;
91
+ height: 100%; width: 100%; grid-column: span 2;
92
+ }
93
+ .dt-group label {
94
+ display: inline-grid; place-items: center;
95
+ cursor: pointer; font-weight: 500; z-index: 2;
96
+ height: 100%; width: 100%; color: #64748b;
97
+ }
98
+ p.note { color: var(--muted); font-size: 10px; max-width: 180px; text-align: center; }
99
+ </style>
100
+ </head>
101
+ <body>
102
+
103
+ <h2>Prototype v8 — Gooey Enter Animation: Safari vs Chrome</h2>
104
+
105
+ <div id="log">Detecting...</div>
106
+
107
+ <div class="row">
108
+ <button onclick="toggleAll()">Toggle All (expand/collapse)</button>
109
+ </div>
110
+
111
+ <div class="row">
112
+
113
+ <!-- ═══ A: WAAPI + linear() easing (Chrome path) ═══ -->
114
+ <div class="col">
115
+ <h3>A: WAAPI + linear() easing</h3>
116
+ <div class="gooey-demo" id="demo-waapi">
117
+ <div class="gooey-canvas" id="canvas-waapi"></div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- ═══ B: WAAPI + multi-keyframe spring (baked) ═══ -->
122
+ <div class="col">
123
+ <h3>B: WAAPI + multi-keyframe spring</h3>
124
+ <div class="gooey-demo" id="demo-waapi-baked">
125
+ <div class="gooey-canvas" id="canvas-waapi-baked"></div>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- ═══ C: rAF + setAttribute (Motion-style) ═══ -->
130
+ <div class="col">
131
+ <h3>C: rAF + setAttribute</h3>
132
+ <div class="gooey-demo" id="demo-raf">
133
+ <div class="gooey-canvas" id="canvas-raf"></div>
134
+ </div>
135
+ </div>
136
+
137
+ </div>
138
+
139
+ <div class="row" style="margin-top: 1rem;">
140
+ <!-- ═══ D: CSS transition on SVG rect ═══ -->
141
+ <div class="col">
142
+ <h3>D: CSS transition (Sileo vanilla)</h3>
143
+ <div class="gooey-demo" id="demo-css">
144
+ <div class="gooey-canvas" id="canvas-css"></div>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- ═══ E: WAAPI on CSS transform (scaleY) ═══ -->
149
+ <div class="col">
150
+ <h3>E: WAAPI scaleY transform</h3>
151
+ <div class="gooey-demo" id="demo-scale">
152
+ <div class="gooey-canvas" id="canvas-scale"></div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <hr style="border-color:#334155;width:80%;margin:1rem 0;">
158
+ <h2>React DOM Wrappers — same WAAPI method A</h2>
159
+
160
+ <div class="row">
161
+ <div class="col">
162
+ <h3>F: Bare (= A above)</h3>
163
+ <div class="gooey-demo">
164
+ <div class="gooey-canvas" id="canvas-f"></div>
165
+ </div>
166
+ </div>
167
+
168
+ <div class="col">
169
+ <h3>G: + dt-root</h3>
170
+ <p class="note">Canvas inside dt-root, no track</p>
171
+ <div class="dt-root">
172
+ <div class="gooey-canvas" id="canvas-g"></div>
173
+ </div>
174
+ </div>
175
+
176
+ <div class="col">
177
+ <h3>H: + track + group</h3>
178
+ <p class="note">Full React structure</p>
179
+ <div class="dt-root">
180
+ <div class="gooey-canvas" id="canvas-h"></div>
181
+ <div class="dt-track">
182
+ <div class="dt-indicator"></div>
183
+ <label class="dt-option"><span>Free</span></label>
184
+ <div class="dt-group">
185
+ <label><span>Solo</span></label>
186
+ <label><span>Team</span></label>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+
192
+ <div class="col">
193
+ <h3>I: Full, no container-type</h3>
194
+ <p class="note">Group: container-type:normal, overflow:visible</p>
195
+ <div class="dt-root">
196
+ <div class="gooey-canvas" id="canvas-i"></div>
197
+ <div class="dt-track">
198
+ <div class="dt-indicator"></div>
199
+ <label class="dt-option"><span>Free</span></label>
200
+ <div class="dt-group" style="container-type:normal;overflow:visible;">
201
+ <label><span>Solo</span></label>
202
+ <label><span>Team</span></label>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ <script>
210
+ // ════════════════════════════════════════════════════════
211
+ // CONSTANTS
212
+ // ════════════════════════════════════════════════════════
213
+
214
+ const WIDTH = 260;
215
+ const HEIGHT = 38;
216
+ const PAD = 2;
217
+ const BUBBLE_H = Math.round(HEIGHT * 0.4); // 15
218
+ const TOTAL_H = HEIGHT + BUBBLE_H;
219
+ const RADIUS = 9999;
220
+ const EFFECTIVE_R = Math.min(RADIUS, HEIGHT / 2);
221
+ const INSET = 0.2;
222
+ const INSET_PX = WIDTH * INSET;
223
+ const BUBBLE_W = WIDTH - 2 * INSET_PX;
224
+ const BUBBLE_R = Math.min(EFFECTIVE_R * 0.6, BUBBLE_H * 0.45, 12);
225
+ const FILL = '#1e293b';
226
+ const BLUR = Math.round(HEIGHT * 0.15);
227
+ const EXPAND_MS = 550;
228
+ const COLLAPSE_MS = 400;
229
+
230
+ // Spring easing — the one Chrome handles perfectly
231
+ const SPRING_GENTLE = `linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%, 1.017 19.4%, 1.067, 1.099 24.3%, 1.108 26%, 1.100, 1.078 30.1%, 1.049 32.5%, 0.994 37.3%, 0.981 40.2%, 0.974 43.4%, 0.975 50.2%, 0.997 62.5%, 1.001 74.7%, 1)`;
232
+ const EASE_OUT_CUBIC = 'cubic-bezier(0.33, 1, 0.68, 1)';
233
+
234
+ // Pre-computed spring curve (same points as SPRING_GENTLE)
235
+ const SPRING_CURVE = [
236
+ [0, 0], [0.0105, 0.009], [0.021, 0.035], [0.044, 0.141],
237
+ [0.129, 0.723], [0.167, 0.938], [0.194, 1.017], [0.2185, 1.067],
238
+ [0.243, 1.099], [0.26, 1.108], [0.2805, 1.1], [0.301, 1.078],
239
+ [0.325, 1.049], [0.373, 0.994], [0.402, 0.981], [0.434, 0.974],
240
+ [0.502, 0.975], [0.625, 0.997], [0.747, 1.001], [1, 1],
241
+ ];
242
+
243
+ const EASE_OUT_CURVE = [
244
+ [0, 0], [0.05, 0.185], [0.1, 0.345], [0.15, 0.48],
245
+ [0.2, 0.594], [0.25, 0.688], [0.3, 0.765], [0.35, 0.827],
246
+ [0.4, 0.876], [0.45, 0.913], [0.5, 0.941], [0.55, 0.961],
247
+ [0.6, 0.975], [0.65, 0.984], [0.7, 0.990], [0.75, 0.994],
248
+ [0.8, 0.997], [0.85, 0.998], [0.9, 0.999], [0.95, 1.0], [1, 1],
249
+ ];
250
+
251
+ // Sileo's spring easing for CSS transitions
252
+ const SILEO_SPRING_CSS = `linear(0, 0.002 0.6%, 0.007 1.2%, 0.015 1.8%, 0.027 2.5%, 0.044 3.3%, 0.065 4.1%, 0.091 5%, 0.55 12%, 0.726 14.5%, 0.864 16.9%, 0.924 18.1%, 0.976 19.4%, 1.001 20.2%, 1.021 21%, 1.036 21.9%, 1.046 22.8%, 1.052 23.8%, 1.054 25%, 1.052 26.2%, 1.046 27.6%, 1.037 29.2%, 1.024 31.1%, 1.009 33.6%, 0.998 36.3%, 0.991 39.7%, 0.988 44.1%, 0.99 50.1%, 0.996 58.4%, 0.999 68.8%, 1)`;
253
+
254
+ // ════════════════════════════════════════════════════════
255
+ // FEATURE DETECTION
256
+ // ════════════════════════════════════════════════════════
257
+
258
+ function detectLinearEasing() {
259
+ try {
260
+ const el = document.createElement('div');
261
+ el.style.cssText = 'position:absolute;visibility:hidden;pointer-events:none';
262
+ document.body.appendChild(el);
263
+
264
+ const anim = el.animate(
265
+ [{ opacity: 0 }, { opacity: 1 }],
266
+ { duration: 100, easing: 'linear(0, 1.5, 1)', fill: 'forwards' },
267
+ );
268
+ anim.currentTime = 50;
269
+ const mid = parseFloat(getComputedStyle(el).opacity);
270
+ anim.cancel();
271
+ el.remove();
272
+
273
+ return { supported: mid > 0.8, midValue: mid };
274
+ } catch (e) {
275
+ return { supported: false, midValue: -1, error: e.message };
276
+ }
277
+ }
278
+
279
+ function detectCSSLinear() {
280
+ try {
281
+ const el = document.createElement('div');
282
+ el.style.cssText = 'position:absolute;visibility:hidden;transition:opacity 0s linear(0,1.5,1)';
283
+ document.body.appendChild(el);
284
+ const computed = getComputedStyle(el).transitionTimingFunction;
285
+ el.remove();
286
+ return { parsed: computed !== 'linear' && computed.includes('linear'), raw: computed };
287
+ } catch (e) {
288
+ return { parsed: false, raw: '', error: e.message };
289
+ }
290
+ }
291
+
292
+ // ════════════════════════════════════════════════════════
293
+ // SVG CREATION HELPERS
294
+ // ════════════════════════════════════════════════════════
295
+
296
+ function createGooeyFilterDefs(id) {
297
+ return `<defs>
298
+ <filter id="${id}" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
299
+ <feGaussianBlur in="SourceGraphic" stdDeviation="${BLUR}" result="blur"/>
300
+ <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10" result="goo"/>
301
+ <feComposite in="SourceGraphic" in2="goo" operator="atop"/>
302
+ </filter>
303
+ </defs>`;
304
+ }
305
+
306
+ function createSVGBase(canvasEl, filterId) {
307
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
308
+ svg.setAttribute('width', WIDTH);
309
+ svg.setAttribute('height', TOTAL_H);
310
+ svg.setAttribute('viewBox', `0 0 ${WIDTH} ${TOTAL_H}`);
311
+ svg.style.cssText = `position:absolute;top:-${BUBBLE_H}px;left:0;overflow:visible`;
312
+
313
+ svg.innerHTML = createGooeyFilterDefs(filterId);
314
+
315
+ // Pill rect (static)
316
+ const pill = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
317
+ pill.setAttribute('x', PAD);
318
+ pill.setAttribute('y', BUBBLE_H);
319
+ pill.setAttribute('width', WIDTH - PAD * 2);
320
+ pill.setAttribute('height', HEIGHT - PAD * 2);
321
+ pill.setAttribute('rx', EFFECTIVE_R);
322
+ pill.setAttribute('ry', EFFECTIVE_R);
323
+ pill.setAttribute('fill', FILL);
324
+ svg.appendChild(pill);
325
+
326
+ // Bubble rect
327
+ const bubble = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
328
+ bubble.setAttribute('x', INSET_PX);
329
+ bubble.setAttribute('y', BUBBLE_H);
330
+ bubble.setAttribute('width', BUBBLE_W);
331
+ bubble.setAttribute('height', 0);
332
+ bubble.setAttribute('rx', BUBBLE_R);
333
+ bubble.setAttribute('ry', BUBBLE_R);
334
+ bubble.setAttribute('fill', FILL);
335
+ svg.appendChild(bubble);
336
+
337
+ canvasEl.style.filter = `url(#${filterId}) drop-shadow(0 0 0.5px #334155) drop-shadow(0 0 0.5px #334155)`;
338
+ canvasEl.appendChild(svg);
339
+
340
+ return { svg, pill, bubble };
341
+ }
342
+
343
+ // ════════════════════════════════════════════════════════
344
+ // ANIMATION METHODS
345
+ // ════════════════════════════════════════════════════════
346
+
347
+ let _waapiId = 0;
348
+ // --- A: WAAPI + linear() easing ---
349
+ function createMethodWaapi(canvasEl) {
350
+ const { bubble } = createSVGBase(canvasEl, 'goo-waapi-' + (++_waapiId));
351
+ let expanded = false;
352
+ let anim = null;
353
+ let last = { y: BUBBLE_H, h: 0 };
354
+
355
+ return {
356
+ toggle() {
357
+ expanded = !expanded;
358
+ const toY = expanded ? 0 : BUBBLE_H;
359
+ const toH = expanded ? BUBBLE_H : 0;
360
+ const dur = expanded ? EXPAND_MS : COLLAPSE_MS;
361
+ const easing = expanded ? SPRING_GENTLE : EASE_OUT_CUBIC;
362
+
363
+ if (anim) anim.cancel();
364
+ anim = bubble.animate(
365
+ [
366
+ { y: `${last.y}px`, height: `${last.h}px` },
367
+ { y: `${toY}px`, height: `${toH}px` },
368
+ ],
369
+ { duration: dur, easing, fill: 'forwards' },
370
+ );
371
+ last = { y: toY, h: toH };
372
+ },
373
+ get expanded() { return expanded; },
374
+ };
375
+ }
376
+
377
+ // --- B: WAAPI + multi-keyframe spring (baked into values) ---
378
+ function createMethodWaapiBaked(canvasEl) {
379
+ const { bubble } = createSVGBase(canvasEl, 'goo-waapi-baked');
380
+ let expanded = false;
381
+ let anim = null;
382
+ let last = { y: BUBBLE_H, h: 0 };
383
+
384
+ function springKeyframes(fromY, toY, fromH, toH, curve) {
385
+ return curve.map(([offset, t]) => ({
386
+ offset,
387
+ y: `${fromY + (toY - fromY) * t}px`,
388
+ height: `${fromH + (toH - fromH) * t}px`,
389
+ }));
390
+ }
391
+
392
+ return {
393
+ toggle() {
394
+ expanded = !expanded;
395
+ const toY = expanded ? 0 : BUBBLE_H;
396
+ const toH = expanded ? BUBBLE_H : 0;
397
+ const dur = expanded ? EXPAND_MS : COLLAPSE_MS;
398
+ const curve = expanded ? SPRING_CURVE : EASE_OUT_CURVE;
399
+
400
+ if (anim) anim.cancel();
401
+ anim = bubble.animate(
402
+ springKeyframes(last.y, toY, last.h, toH, curve),
403
+ { duration: dur, easing: 'linear', fill: 'forwards' },
404
+ );
405
+ last = { y: toY, h: toH };
406
+ },
407
+ get expanded() { return expanded; },
408
+ };
409
+ }
410
+
411
+ // --- C: rAF + setAttribute (Motion-style) ---
412
+ function createMethodRaf(canvasEl) {
413
+ const { bubble } = createSVGBase(canvasEl, 'goo-raf');
414
+ let expanded = false;
415
+ let running = null;
416
+ let last = { y: BUBBLE_H, h: 0 };
417
+
418
+ function sampleCurve(curve, progress) {
419
+ if (progress <= 0) return curve[0][1];
420
+ if (progress >= 1) return curve[curve.length - 1][1];
421
+ let lo = 0, hi = curve.length - 1;
422
+ while (lo < hi - 1) {
423
+ const mid = (lo + hi) >> 1;
424
+ if (curve[mid][0] <= progress) lo = mid; else hi = mid;
425
+ }
426
+ const [t0, v0] = curve[lo];
427
+ const [t1, v1] = curve[hi];
428
+ return v0 + (v1 - v0) * ((progress - t0) / (t1 - t0));
429
+ }
430
+
431
+ function animateRaf(fromY, toY, fromH, toH, duration, curve) {
432
+ if (running) { running.cancelled = true; cancelAnimationFrame(running.raf); }
433
+ const ctx = { cancelled: false, raf: 0 };
434
+ running = ctx;
435
+ const start = performance.now();
436
+
437
+ function step(now) {
438
+ if (ctx.cancelled) return;
439
+ const p = Math.min((now - start) / duration, 1);
440
+ const t = sampleCurve(curve, p);
441
+ const curY = fromY + (toY - fromY) * t;
442
+ const curH = fromH + (toH - fromH) * t;
443
+ bubble.setAttribute('y', curY);
444
+ bubble.setAttribute('height', curH);
445
+ if (p < 1) ctx.raf = requestAnimationFrame(step);
446
+ }
447
+ ctx.raf = requestAnimationFrame(step);
448
+ }
449
+
450
+ return {
451
+ toggle() {
452
+ expanded = !expanded;
453
+ const toY = expanded ? 0 : BUBBLE_H;
454
+ const toH = expanded ? BUBBLE_H : 0;
455
+ const dur = expanded ? EXPAND_MS : COLLAPSE_MS;
456
+ const curve = expanded ? SPRING_CURVE : EASE_OUT_CURVE;
457
+ animateRaf(last.y, toY, last.h, toH, dur, curve);
458
+ last = { y: toY, h: toH };
459
+ },
460
+ get expanded() { return expanded; },
461
+ };
462
+ }
463
+
464
+ // --- D: CSS transition on SVG rect (Sileo vanilla approach) ---
465
+ function createMethodCSS(canvasEl) {
466
+ const { bubble } = createSVGBase(canvasEl, 'goo-css');
467
+ let expanded = false;
468
+
469
+ // Add CSS transition to the bubble rect
470
+ // Sileo vanilla uses transform: scaleY + CSS custom properties
471
+ // But let's test direct CSS transition on y/height (SVG2 CSS properties)
472
+ bubble.style.transition = `y ${EXPAND_MS}ms ${SILEO_SPRING_CSS}, height ${EXPAND_MS}ms ${SILEO_SPRING_CSS}`;
473
+
474
+ return {
475
+ toggle() {
476
+ expanded = !expanded;
477
+ bubble.style.y = expanded ? '0px' : `${BUBBLE_H}px`;
478
+ bubble.style.height = expanded ? `${BUBBLE_H}px` : '0px';
479
+ },
480
+ get expanded() { return expanded; },
481
+ };
482
+ }
483
+
484
+ // --- E: WAAPI on CSS transform (scaleY — guaranteed to work everywhere) ---
485
+ function createMethodScale(canvasEl) {
486
+ const { bubble } = createSVGBase(canvasEl, 'goo-scale');
487
+ let expanded = false;
488
+ let anim = null;
489
+
490
+ // Set bubble to full size but scaleY(0)
491
+ bubble.setAttribute('y', 0);
492
+ bubble.setAttribute('height', BUBBLE_H);
493
+ bubble.style.transformOrigin = '50% 100%';
494
+ bubble.style.transformBox = 'fill-box';
495
+ bubble.style.transform = 'scaleY(0)';
496
+
497
+ return {
498
+ toggle() {
499
+ expanded = !expanded;
500
+ if (anim) anim.cancel();
501
+ anim = bubble.animate(
502
+ [
503
+ { transform: expanded ? 'scaleY(0)' : 'scaleY(1)' },
504
+ { transform: expanded ? 'scaleY(1)' : 'scaleY(0)' },
505
+ ],
506
+ {
507
+ duration: expanded ? EXPAND_MS : COLLAPSE_MS,
508
+ easing: expanded ? SPRING_GENTLE : EASE_OUT_CUBIC,
509
+ fill: 'forwards',
510
+ },
511
+ );
512
+ },
513
+ get expanded() { return expanded; },
514
+ };
515
+ }
516
+
517
+ // ════════════════════════════════════════════════════════
518
+ // INIT
519
+ // ════════════════════════════════════════════════════════
520
+
521
+ const methods = [];
522
+
523
+ function init() {
524
+ const detection = detectLinearEasing();
525
+ const cssDetection = detectCSSLinear();
526
+ const ua = navigator.userAgent;
527
+ const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua);
528
+
529
+ document.getElementById('log').textContent =
530
+ `Browser: ${isSafari ? 'Safari' : 'Chrome/Other'}\n` +
531
+ `WAAPI linear() easing: ${detection.supported} (midpoint opacity: ${detection.midValue.toFixed(3)})\n` +
532
+ `CSS linear() parsing: ${cssDetection.parsed} (raw: ${cssDetection.raw.slice(0, 60)}...)\n` +
533
+ `\nExpected:\n` +
534
+ ` A (WAAPI+linear): ${detection.supported ? 'SPRING' : 'NO spring — plain linear fallback'}\n` +
535
+ ` B (WAAPI+baked): SPRING always (values in keyframes)\n` +
536
+ ` C (rAF+setAttr): SPRING always (Motion-style)\n` +
537
+ ` D (CSS trans): ${cssDetection.parsed ? 'SPRING' : 'NO spring — CSS linear() not parsed'}\n` +
538
+ ` E (WAAPI scaleY): ${detection.supported ? 'SPRING' : 'NO spring — transform, not SVG attr'}`;
539
+
540
+ methods.push(createMethodWaapi(document.getElementById('canvas-waapi')));
541
+ methods.push(createMethodWaapiBaked(document.getElementById('canvas-waapi-baked')));
542
+ methods.push(createMethodRaf(document.getElementById('canvas-raf')));
543
+ methods.push(createMethodCSS(document.getElementById('canvas-css')));
544
+ methods.push(createMethodScale(document.getElementById('canvas-scale')));
545
+
546
+ // Wrapped variants — all use WAAPI method A
547
+ methods.push(createMethodWaapi(document.getElementById('canvas-f')));
548
+ methods.push(createMethodWaapi(document.getElementById('canvas-g')));
549
+ methods.push(createMethodWaapi(document.getElementById('canvas-h')));
550
+ methods.push(createMethodWaapi(document.getElementById('canvas-i')));
551
+ }
552
+
553
+ function toggleAll() {
554
+ methods.forEach(m => m.toggle());
555
+ }
556
+
557
+ document.addEventListener('DOMContentLoaded', init);
558
+ </script>
559
+ </body>
560
+ </html>