claude-exode-mcp 7.5.7 → 7.5.8

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 (173) hide show
  1. package/MCP_DOCUMENTATION.md +1 -0
  2. package/SKILL.md +10 -0
  3. package/examples/aurum-product-page.exode +3208 -0
  4. package/examples/bell-ring-icon.exode +1 -0
  5. package/examples/drift-kinetic-type.exode +770 -0
  6. package/examples/earbuds-product.exode +214 -0
  7. package/examples/energy-flow.exode +1736 -0
  8. package/examples/flux-hero.exode +223 -0
  9. package/examples/gooey-toggle.exode +214 -0
  10. package/examples/gravity-well.exode +3113 -0
  11. package/examples/heart-icon.exode +1 -0
  12. package/examples/hologram-temple.exode +8100 -0
  13. package/examples/jarful-app.exode +1 -0
  14. package/examples/kip-character-rig.exode +1 -0
  15. package/examples/low-tide-tilt.exode +2216 -0
  16. package/examples/manifest.json +352 -0
  17. package/examples/swipe-stack-carousel.exode +4048 -0
  18. package/examples/tokyo-neon-weather.exode +470 -0
  19. package/examples/typographic-collapse.exode +1578 -0
  20. package/examples/void-product-ad.exode +1269 -0
  21. package/examples/wire-globe-card.exode +580 -0
  22. package/index.d.ts +41 -1
  23. package/index.js +2553 -2407
  24. package/package.json +4 -2
  25. package/render-runtime/exode-runtime.js +20 -20
  26. package/skills/creative-motion/SKILL.md +10 -0
  27. package/skills/creative-motion/recipes/character-rig.js +104 -0
  28. package/skills/creative-motion/recipes/dashboard.js +120 -0
  29. package/skills/creative-motion/recipes/game-arcade.js +203 -0
  30. package/skills/creative-motion/recipes/hero-physics.js +65 -0
  31. package/skills/creative-motion/recipes/pricing.js +88 -0
  32. package/skills/creative-motion/recipes/title-sequence.js +62 -0
  33. package/skills/creative-motion/references/apps.md +37 -0
  34. package/skills/creative-motion/references/gotchas.md +80 -0
  35. package/skills/creative-motion/references/native-layers.md +60 -0
  36. package/skills/creative-motion/references/quality.md +54 -0
  37. package/src/__tests__/buildKit.test.ts +127 -0
  38. package/src/__tests__/effectKnobs.test.ts +162 -0
  39. package/src/__tests__/nativeLayers.test.ts +178 -0
  40. package/src/buildKit.d.ts +301 -0
  41. package/src/buildKit.d.ts.map +1 -0
  42. package/src/buildKit.js +559 -0
  43. package/src/buildKit.js.map +1 -0
  44. package/src/buildKit.ts +520 -0
  45. package/src/compiler.d.ts +4 -0
  46. package/src/compiler.d.ts.map +1 -1
  47. package/src/compiler.js +21 -45
  48. package/src/compiler.js.map +1 -1
  49. package/src/compiler.ts +22 -51
  50. package/src/critique.d.ts +8 -0
  51. package/src/critique.d.ts.map +1 -0
  52. package/src/critique.js +348 -0
  53. package/src/critique.js.map +1 -0
  54. package/src/critique.ts +239 -0
  55. package/src/effectKnobs.d.ts +72 -0
  56. package/src/effectKnobs.d.ts.map +1 -0
  57. package/src/effectKnobs.js +541 -0
  58. package/src/effectKnobs.js.map +1 -0
  59. package/src/effectKnobs.ts +445 -0
  60. package/src/effectOptimizer.d.ts +18 -0
  61. package/src/effectOptimizer.d.ts.map +1 -0
  62. package/src/effectOptimizer.js +125 -0
  63. package/src/effectOptimizer.js.map +1 -0
  64. package/src/effectOptimizer.ts +104 -0
  65. package/src/engine/layout.js +1 -1
  66. package/src/engine/layout.js.map +1 -1
  67. package/src/engine/layout.ts +1 -1
  68. package/src/examples.d.ts +30 -0
  69. package/src/examples.d.ts.map +1 -0
  70. package/src/examples.js +168 -0
  71. package/src/examples.js.map +1 -0
  72. package/src/examples.ts +151 -0
  73. package/src/handlers/artboardHandlers.d.ts.map +1 -1
  74. package/src/handlers/artboardHandlers.js +45 -10
  75. package/src/handlers/artboardHandlers.js.map +1 -1
  76. package/src/handlers/artboardHandlers.ts +43 -12
  77. package/src/handlers/buildHandlers.d.ts +2 -0
  78. package/src/handlers/buildHandlers.d.ts.map +1 -0
  79. package/src/handlers/buildHandlers.js +86 -0
  80. package/src/handlers/buildHandlers.js.map +1 -0
  81. package/src/handlers/buildHandlers.ts +76 -0
  82. package/src/handlers/compilerHandlers.d.ts.map +1 -1
  83. package/src/handlers/compilerHandlers.js +32 -2
  84. package/src/handlers/compilerHandlers.js.map +1 -1
  85. package/src/handlers/compilerHandlers.ts +31 -2
  86. package/src/handlers/exampleHandlers.d.ts +2 -0
  87. package/src/handlers/exampleHandlers.d.ts.map +1 -0
  88. package/src/handlers/exampleHandlers.js +81 -0
  89. package/src/handlers/exampleHandlers.js.map +1 -0
  90. package/src/handlers/exampleHandlers.ts +76 -0
  91. package/src/handlers/renderHandlers.d.ts.map +1 -1
  92. package/src/handlers/renderHandlers.js +100 -2
  93. package/src/handlers/renderHandlers.js.map +1 -1
  94. package/src/handlers/renderHandlers.ts +90 -2
  95. package/src/handlers/scaffoldHandlers.d.ts.map +1 -1
  96. package/src/handlers/scaffoldHandlers.js +3 -0
  97. package/src/handlers/scaffoldHandlers.js.map +1 -1
  98. package/src/handlers/scaffoldHandlers.ts +2 -0
  99. package/src/handlers/widgetHandlers.js +1 -1
  100. package/src/handlers/widgetHandlers.js.map +1 -1
  101. package/src/handlers/widgetHandlers.ts +1 -1
  102. package/src/lottie/convert.d.ts +16 -0
  103. package/src/lottie/convert.d.ts.map +1 -0
  104. package/src/lottie/convert.js +1225 -0
  105. package/src/lottie/convert.js.map +1 -0
  106. package/src/lottie/convert.ts +1134 -0
  107. package/src/lottie/evaluate.d.ts +48 -0
  108. package/src/lottie/evaluate.d.ts.map +1 -0
  109. package/src/lottie/evaluate.js +174 -0
  110. package/src/lottie/evaluate.js.map +1 -0
  111. package/src/lottie/evaluate.ts +184 -0
  112. package/src/nativeLayers.d.ts +76 -0
  113. package/src/nativeLayers.d.ts.map +1 -0
  114. package/src/nativeLayers.js +404 -0
  115. package/src/nativeLayers.js.map +1 -0
  116. package/src/nativeLayers.ts +339 -0
  117. package/src/render/motionBrief.d.ts.map +1 -1
  118. package/src/render/motionBrief.js +4 -0
  119. package/src/render/motionBrief.js.map +1 -1
  120. package/src/render/motionBrief.ts +4 -0
  121. package/src/render/videoRenderer.d.ts.map +1 -1
  122. package/src/render/videoRenderer.js +5 -2
  123. package/src/render/videoRenderer.js.map +1 -1
  124. package/src/render/videoRenderer.ts +5 -2
  125. package/src/rive/convert.d.ts +17 -0
  126. package/src/rive/convert.d.ts.map +1 -0
  127. package/src/rive/convert.js +1779 -0
  128. package/src/rive/convert.js.map +1 -0
  129. package/src/rive/convert.ts +1492 -0
  130. package/src/rive/font.d.ts +13 -0
  131. package/src/rive/font.d.ts.map +1 -0
  132. package/src/rive/font.js +154 -0
  133. package/src/rive/font.js.map +1 -0
  134. package/src/rive/font.ts +132 -0
  135. package/src/rive/layout.d.ts +20 -0
  136. package/src/rive/layout.d.ts.map +1 -0
  137. package/src/rive/layout.js +145 -0
  138. package/src/rive/layout.js.map +1 -0
  139. package/src/rive/layout.ts +140 -0
  140. package/src/rive/reader.d.ts +32 -0
  141. package/src/rive/reader.d.ts.map +1 -0
  142. package/src/rive/reader.js +172 -0
  143. package/src/rive/reader.js.map +1 -0
  144. package/src/rive/reader.ts +160 -0
  145. package/src/rive/schema.generated.d.ts +9 -0
  146. package/src/rive/schema.generated.d.ts.map +1 -0
  147. package/src/rive/schema.generated.js +13 -0
  148. package/src/rive/schema.generated.js.map +1 -0
  149. package/src/rive/schema.generated.ts +13 -0
  150. package/src/schema.d.ts +5 -0
  151. package/src/schema.d.ts.map +1 -1
  152. package/src/schema.js +5 -0
  153. package/src/schema.js.map +1 -1
  154. package/src/schema.ts +6 -0
  155. package/src/serverInstructions.d.ts +2 -0
  156. package/src/serverInstructions.d.ts.map +1 -0
  157. package/src/serverInstructions.js +22 -0
  158. package/src/serverInstructions.js.map +1 -0
  159. package/src/serverInstructions.ts +21 -0
  160. package/src/sseServer.d.ts +2 -0
  161. package/src/sseServer.d.ts.map +1 -0
  162. package/src/sseServer.js +165 -0
  163. package/src/sseServer.js.map +1 -0
  164. package/src/sseServer.ts +189 -0
  165. package/src/utils.d.ts.map +1 -1
  166. package/src/utils.js +23 -1
  167. package/src/utils.js.map +1 -1
  168. package/src/utils.ts +13 -1
  169. package/src/widgets.d.ts +26 -0
  170. package/src/widgets.d.ts.map +1 -1
  171. package/src/widgets.js +89 -45
  172. package/src/widgets.js.map +1 -1
  173. package/src/widgets.ts +88 -45
@@ -0,0 +1,223 @@
1
+ {
2
+ "id": "mui_flux_hero",
3
+ "name": "Flux Hero",
4
+ "width": 1200,
5
+ "height": 760,
6
+ "cornerRadius": 0,
7
+ "fit": "Layout",
8
+ "backgroundColor": "#000000",
9
+ "opacity": 1,
10
+ "x": -600,
11
+ "y": -380,
12
+ "responsive": {
13
+ "enabled": true,
14
+ "mode": "constraint",
15
+ "width": {
16
+ "type": "fixed"
17
+ },
18
+ "height": {
19
+ "type": "fixed"
20
+ },
21
+ "maintainAspectRatio": false,
22
+ "scaleObjects": false,
23
+ "breakpoints": [
24
+ {
25
+ "id": "bp_mobile",
26
+ "name": "Mobile",
27
+ "minWidth": 0,
28
+ "maxWidth": 599
29
+ },
30
+ {
31
+ "id": "bp_tablet",
32
+ "name": "Tablet",
33
+ "minWidth": 600,
34
+ "maxWidth": 1023
35
+ },
36
+ {
37
+ "id": "bp_desktop",
38
+ "name": "Desktop",
39
+ "minWidth": 1024
40
+ }
41
+ ]
42
+ },
43
+ "layoutProps": {
44
+ "mode": "linear",
45
+ "direction": "vertical",
46
+ "gap": 0,
47
+ "padding": {
48
+ "top": 0,
49
+ "right": 0,
50
+ "bottom": 0,
51
+ "left": 0
52
+ },
53
+ "alignItems": "stretch",
54
+ "layoutSizingHorizontal": "fixed",
55
+ "layoutSizingVertical": "fixed"
56
+ },
57
+ "seo": {
58
+ "title": "Flux Hero — Exode Motion UI",
59
+ "description": "A responsive full hero block with living rotating fluid gradient mesh, glowing badge, dual action buttons, and a 3D perspective dashboard preview card that reacts to cursor movement."
60
+ },
61
+ "stateMachine": {
62
+ "inputs": [
63
+ {
64
+ "id": "in_badge",
65
+ "name": "badge",
66
+ "value": {
67
+ "type": "Text",
68
+ "value": "✦ EXODE 2.0 IS LIVE",
69
+ "defaultValue": "✦ EXODE 2.0 IS LIVE"
70
+ }
71
+ },
72
+ {
73
+ "id": "in_headline",
74
+ "name": "headline",
75
+ "value": {
76
+ "type": "Text",
77
+ "value": "Craft Living Interfaces at the Speed of Thought",
78
+ "defaultValue": "Craft Living Interfaces at the Speed of Thought"
79
+ }
80
+ },
81
+ {
82
+ "id": "in_sub",
83
+ "name": "sub",
84
+ "value": {
85
+ "type": "Text",
86
+ "value": "The next-generation motion design environment. Zero video bloat, sub-millisecond execution, complete state-machine control.",
87
+ "defaultValue": "The next-generation motion design environment. Zero video bloat, sub-millisecond execution, complete state-machine control."
88
+ }
89
+ },
90
+ {
91
+ "id": "in_primaryCta",
92
+ "name": "primaryCta",
93
+ "value": {
94
+ "type": "Text",
95
+ "value": "Start Creating Free",
96
+ "defaultValue": "Start Creating Free"
97
+ }
98
+ },
99
+ {
100
+ "id": "in_secondaryCta",
101
+ "name": "secondaryCta",
102
+ "value": {
103
+ "type": "Text",
104
+ "value": "Explore Showcase",
105
+ "defaultValue": "Explore Showcase"
106
+ }
107
+ },
108
+ {
109
+ "id": "in_palette",
110
+ "name": "palette",
111
+ "value": {
112
+ "type": "Number",
113
+ "value": 0,
114
+ "defaultValue": 0
115
+ }
116
+ },
117
+ {
118
+ "id": "in_speed",
119
+ "name": "speed",
120
+ "value": {
121
+ "type": "Number",
122
+ "value": 1,
123
+ "defaultValue": 1
124
+ }
125
+ },
126
+ {
127
+ "id": "in_glow",
128
+ "name": "glow",
129
+ "value": {
130
+ "type": "Number",
131
+ "value": 1,
132
+ "defaultValue": 1
133
+ }
134
+ },
135
+ {
136
+ "id": "in_accent",
137
+ "name": "accent",
138
+ "value": {
139
+ "type": "Text",
140
+ "value": "#2868ff",
141
+ "defaultValue": "#2868ff"
142
+ }
143
+ }
144
+ ],
145
+ "layers": [
146
+ {
147
+ "name": "Base",
148
+ "entryStateId": "s_idle",
149
+ "states": [
150
+ {
151
+ "id": "s_idle",
152
+ "name": "Idle",
153
+ "loop": true,
154
+ "transitions": []
155
+ }
156
+ ]
157
+ }
158
+ ],
159
+ "logicNodes": []
160
+ },
161
+ "animations": [],
162
+ "physics": {
163
+ "enabled": false,
164
+ "gravity": {
165
+ "x": 0,
166
+ "y": 9.8
167
+ }
168
+ },
169
+ "animationFlow": [],
170
+ "navigation": [],
171
+ "stateVariables": [],
172
+ "objects": [
173
+ {
174
+ "id": "stage",
175
+ "name": "Stage",
176
+ "type": "Group",
177
+ "transform": {
178
+ "x": 0,
179
+ "y": 0,
180
+ "rotation": 0,
181
+ "scale_x": 1,
182
+ "scale_y": 1,
183
+ "opacity": 1,
184
+ "skew_x": 0,
185
+ "skew_y": 0
186
+ },
187
+ "width": 1200,
188
+ "height": 760,
189
+ "geometry": {
190
+ "type": "Rectangle",
191
+ "width": 1200,
192
+ "height": 760,
193
+ "cornerRadius": 0
194
+ },
195
+ "cornerRadius": 0,
196
+ "children": [],
197
+ "clipContent": true,
198
+ "style": {
199
+ "fill": {
200
+ "type": "Solid",
201
+ "color": "#000000",
202
+ "opacity": 1
203
+ },
204
+ "effects": [
205
+ {
206
+ "id": "fx_flux-hero",
207
+ "type": "CustomEffect",
208
+ "name": "Flux Hero",
209
+ "isEnabled": true,
210
+ "params": {},
211
+ "code": "/* Shared by every motion-ui effect (prepended to each one).\n Rules for this file and the effects: the .bexode compiler minifies effect\n code, so use explicit semicolons, block comments only, and never write a\n minus followed by a negative value (it collapses to a decrement). */\n\n/* Read a live state-machine input, falling back to a default. */\nfunction inp(env, name, fallback) {\n const v = env.inputs ? env.inputs[name] : undefined;\n if (v === undefined || v === null || v === '') return fallback;\n if (typeof fallback === 'number') {\n const n = Number(v);\n return isFinite(n) ? n : fallback;\n }\n if (typeof fallback === 'boolean') return v === true || v === 1 || v === 'true';\n return v;\n}\n\n/* '#rgb' / '#rrggbb' to [r, g, b]; anything else falls back to white. */\nfunction rgbOf(hex) {\n let h = String(hex || '').trim().replace('#', '');\n if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];\n if (!/^[0-9a-fA-F]{6}$/.test(h)) return [255, 255, 255];\n const n = parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction rgba(hex, a) {\n const c = rgbOf(hex);\n return 'rgba(' + c[0] + ',' + c[1] + ',' + c[2] + ',' + Math.max(0, Math.min(1, a)) + ')';\n}\n\nfunction mix(a, b, t) {\n const x = rgbOf(a), y = rgbOf(b);\n const r = Math.round(x[0] + (y[0] - x[0]) * t);\n const g = Math.round(x[1] + (y[1] - x[1]) * t);\n const bl = Math.round(x[2] + (y[2] - x[2]) * t);\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + bl).toString(16).slice(1);\n}\n\n/* A soft round glow, rendered once per colour and blitted with drawImage\n (much cheaper than shadowBlur on every particle). */\nconst __glows = {};\nfunction glowSprite(hex) {\n if (__glows[hex]) return __glows[hex];\n const s = 64;\n const c = document.createElement('canvas');\n c.width = s; c.height = s;\n const g = c.getContext('2d');\n const gr = g.createRadialGradient(s / 2, s / 2, 0, s / 2, s / 2, s / 2);\n gr.addColorStop(0, rgba(hex, 1));\n gr.addColorStop(0.18, rgba(hex, 0.55));\n gr.addColorStop(0.45, rgba(hex, 0.14));\n gr.addColorStop(1, rgba(hex, 0));\n g.fillStyle = gr;\n g.fillRect(0, 0, s, s);\n __glows[hex] = c;\n return c;\n}\n\nfunction glow(ctx, hex, x, y, r, a) {\n if (r <= 0 || a <= 0) return;\n ctx.globalAlpha = Math.min(1, a);\n ctx.drawImage(glowSprite(hex), x - r, y - r, r * 2, r * 2);\n ctx.globalAlpha = 1;\n}\n\n/* Frame clock: seconds since start and a clamped delta. */\nfunction tick(S) {\n const now = performance.now();\n if (!S.t0) { S.t0 = now; S.last = now; }\n S.dt = Math.min(0.05, Math.max(0.001, (now - S.last) / 1000));\n S.last = now;\n S.age = (now - S.t0) / 1000;\n return S.dt;\n}\n\n/* Deterministic pseudo-random so layouts are stable across resizes. */\nfunction rng(seed) {\n let s = seed >>> 0 || 1;\n return function () {\n s = (s * 1664525 + 1013904223) >>> 0;\n return s / 4294967296;\n };\n}\n\nfunction ease(x) { return x < 0.5 ? 4 * x * x * x : 1 + (Math.pow(2 * x - 2, 3)) / 2; }\nfunction clamp(v, a, b) { return v < a ? a : v > b ? b : v; }\n\nconst SANS = 'Inter, Helvetica, Arial, sans-serif';\nconst MONO = 'ui-monospace, Menlo, monospace';\n\nfunction roundRect(ctx, x, y, w, h, r) {\n r = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + r, y);\n ctx.arcTo(x + w, y, x + w, y + h, r);\n ctx.arcTo(x + w, y + h, x, y + h, r);\n ctx.arcTo(x, y + h, x, y, r);\n ctx.arcTo(x, y, x + w, y, r);\n ctx.closePath();\n}\n\n/* Shared backdrop: flat colour, soft vignette and fine grain. */\nfunction backdrop(ctx, W, H, bg, tint, amt) {\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W, H);\n if (tint && amt > 0) {\n const gr = ctx.createRadialGradient(W / 2, H * 0.45, 0, W / 2, H * 0.45, Math.max(W, H) * 0.7);\n gr.addColorStop(0, rgba(tint, 0.10 * amt));\n gr.addColorStop(1, rgba(tint, 0));\n ctx.fillStyle = gr;\n ctx.fillRect(0, 0, W, H);\n }\n const vg = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.3, W / 2, H / 2, Math.max(W, H) * 0.8);\n vg.addColorStop(0, 'rgba(0,0,0,0)');\n vg.addColorStop(1, 'rgba(0,0,0,0.45)');\n ctx.fillStyle = vg;\n ctx.fillRect(0, 0, W, H);\n}\n\n/* Greedy word wrap with the current ctx.font. */\nfunction wrap(ctx, text, maxW) {\n const out = [];\n String(text).split('\\n').forEach(function (para) {\n const words = para.split(' ');\n let cur = '';\n for (let i = 0; i < words.length; i++) {\n const next = cur ? cur + ' ' + words[i] : words[i];\n if (cur && ctx.measureText(next).width > maxW) { out.push(cur); cur = words[i]; } else cur = next;\n }\n out.push(cur);\n });\n return out;\n}\n\n/* Largest font size (up to max) at which text wraps into maxLines within maxW. */\nfunction fitFont(ctx, text, weight, family, maxW, maxLines, max, min) {\n let fs = max;\n while (fs > min) {\n ctx.font = weight + ' ' + fs + 'px ' + family;\n if (wrap(ctx, text, maxW).length <= maxLines) break;\n fs = fs * 0.92;\n }\n ctx.font = weight + ' ' + fs + 'px ' + family;\n return fs;\n}\n\n/* Smoothed pointer that idles along a slow figure-eight when the cursor is away. */\nfunction follow(S, env, W, H, k) {\n const pt = env.pointer || {};\n const t = S.age || 0;\n const tx = pt.isHovered ? pt.localX : W / 2 + Math.sin(t * 0.37) * W * 0.3;\n const ty = pt.isHovered ? pt.localY : H / 2 + Math.sin(t * 0.74) * H * 0.22;\n if (S.px === undefined) { S.px = tx; S.py = ty; }\n S.px += (tx - S.px) * Math.min(1, S.dt * (k || 6));\n S.py += (ty - S.py) * Math.min(1, S.dt * (k || 6));\n S.inside = !!pt.isHovered;\n return pt;\n}\n\n/* Optional centred headline over a background effect. */\nfunction title(ctx, W, H, text, sub, ink, accent, bg) {\n if (!text) return;\n if (bg) {\n /* Soft scrim so the headline reads over busy motion. */\n const sc = ctx.createRadialGradient(W / 2, H / 2, 0, W / 2, H / 2, Math.min(W * 0.5, 560));\n sc.addColorStop(0, rgba(bg, 0.72));\n sc.addColorStop(1, rgba(bg, 0));\n ctx.fillStyle = sc;\n ctx.fillRect(0, 0, W, H);\n }\n ctx.textAlign = 'center';\n ctx.textBaseline = 'alphabetic';\n const maxW = Math.min(W * 0.86, 900);\n const fs = fitFont(ctx, text, 600, SANS, maxW, 2, clamp(W * 0.065, 26, 72), 16);\n const lines = wrap(ctx, text, maxW);\n const lh = fs * 1.08;\n const y0 = H / 2 - (lines.length * lh) / 2 + fs * 0.8 - (sub ? fs * 0.35 : 0);\n ctx.fillStyle = ink;\n lines.forEach(function (l, i) { ctx.fillText(l, W / 2, y0 + i * lh); });\n if (sub) {\n ctx.font = '500 ' + clamp(fs * 0.24, 11, 15) + 'px ' + MONO;\n ctx.fillStyle = rgba(accent, 0.85);\n ctx.fillText(String(sub).toUpperCase(), W / 2, y0 + (lines.length - 1) * lh + fs * 0.9);\n }\n}\n\n/* Flux Hero: a full interactive hero block inspired by reactvibe.com.\n Features a rotating fluid gradient mesh backdrop, floating ambient orbs,\n pill badge, large headline, subline, dual responsive buttons with hover/press\n physics, and a perspective-tilted floating dashboard preview card that\n reacts dynamically to cursor coordinates. */\nlet S = null;\n\nconst PALETTES = [\n ['#2868ff', '#4f80ff', '#779fff', '#1a3fa8'], /* Blue */\n ['#5e17eb', '#8b5cff', '#c1a6ff', '#360999'], /* Purple */\n ['#00796b', '#26a69a', '#80cbc4', '#004d40'], /* Teal */\n ['#ff6b00', '#ffa726', '#ffd180', '#b34700'], /* Orange */\n ['#007bff', '#00c6ff', '#80d8ff', '#0056b3'], /* Sky */\n];\n\nfunction render(ctx, env) {\n const W = env.width, H = env.height;\n if (!S) {\n S = {\n angle: 0,\n badgeHover: 0,\n btn1Hover: 0,\n btn1Press: 0,\n btn2Hover: 0,\n btn2Press: 0,\n cardTiltX: 0,\n cardTiltY: 0,\n cardFloat: 0,\n wasDown: false,\n ripples: [],\n };\n }\n const dt = tick(S);\n\n const badgeText = String(inp(env, 'badge', '✦ EXODE 2.0 IS LIVE'));\n const heading = String(inp(env, 'headline', 'Craft Living Interfaces at the Speed of Thought'));\n const sub = String(inp(env, 'sub', 'The next-generation motion design environment. Zero video bloat, sub-millisecond execution, complete state-machine control.'));\n const btn1Label = String(inp(env, 'primaryCta', 'Start Creating Free'));\n const btn2Label = String(inp(env, 'secondaryCta', 'Explore Showcase'));\n const palIdx = Math.floor(Math.abs(inp(env, 'palette', 0))) % PALETTES.length;\n const speed = clamp(inp(env, 'speed', 1), 0.1, 4);\n const glowIntensity = clamp(inp(env, 'glow', 1), 0, 2);\n const customAccent = inp(env, 'accent', '');\n\n S.angle = (S.angle + dt * 22 * speed) % 360;\n S.cardFloat = (S.cardFloat + dt * 1.4 * speed) % (Math.PI * 2);\n\n const curPal = PALETTES[palIdx];\n const c1 = customAccent || curPal[0];\n const c2 = curPal[1];\n const c3 = curPal[2];\n const cDark = curPal[3];\n\n ctx.save();\n ctx.translate(-W / 2, -H / 2);\n\n /* Deep Backdrop */\n ctx.fillStyle = '#06070a';\n ctx.fillRect(0, 0, W, H);\n\n /* Rotating Atmospheric Linear Gradient */\n const rad = (S.angle * Math.PI) / 180;\n const cx = W / 2, cy = H * 0.42;\n const diag = Math.sqrt(W * W + H * H);\n\n const x0 = cx - Math.cos(rad) * (diag * 0.45);\n const y0 = cy - Math.sin(rad) * (diag * 0.45);\n const x1 = cx + Math.cos(rad) * (diag * 0.45);\n const y1 = cy + Math.sin(rad) * (diag * 0.45);\n\n const bgGrad = ctx.createLinearGradient(x0, y0, x1, y1);\n bgGrad.addColorStop(0, rgba(cDark, 0.85));\n bgGrad.addColorStop(0.35, rgba(c1, 0.55));\n bgGrad.addColorStop(0.7, rgba(c2, 0.45));\n bgGrad.addColorStop(1, rgba(c3, 0.70));\n\n ctx.fillStyle = bgGrad;\n ctx.fillRect(0, 0, W, H);\n\n /* Fluid Ambient Floating Orbs */\n if (glowIntensity > 0) {\n const orb1X = cx + Math.sin(S.age * 0.65 * speed) * (W * 0.3);\n const orb1Y = cy + Math.cos(S.age * 0.55 * speed) * (H * 0.2);\n glow(ctx, c1, orb1X, orb1Y, Math.min(W, H) * 0.5 * glowIntensity, 0.55);\n\n const orb2X = cx - Math.cos(S.age * 0.5 * speed) * (W * 0.35);\n const orb2Y = cy - Math.sin(S.age * 0.75 * speed) * (H * 0.22);\n glow(ctx, c3, orb2X, orb2Y, Math.min(W, H) * 0.45 * glowIntensity, 0.45);\n }\n\n /* Vignette scrim for readability */\n const vig = ctx.createRadialGradient(cx, cy, Math.min(W, H) * 0.25, cx, cy, diag * 0.65);\n vig.addColorStop(0, 'rgba(0,0,0,0)');\n vig.addColorStop(0.7, 'rgba(6,7,10,0.6)');\n vig.addColorStop(1, 'rgba(6,7,10,0.92)');\n ctx.fillStyle = vig;\n ctx.fillRect(0, 0, W, H);\n\n /* Subtle background grid lines */\n ctx.save();\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.035)';\n ctx.lineWidth = 1;\n const gridStep = Math.max(36, Math.min(60, W / 24));\n for (let gx = 0; gx < W; gx += gridStep) {\n ctx.beginPath();\n ctx.moveTo(gx, 0);\n ctx.lineTo(gx, H);\n ctx.stroke();\n }\n for (let gy = 0; gy < H; gy += gridStep) {\n ctx.beginPath();\n ctx.moveTo(0, gy);\n ctx.lineTo(W, gy);\n ctx.stroke();\n }\n ctx.restore();\n\n /* Pointer tracking & Card tilt smoothing */\n const pt = env.pointer || {};\n const isMobile = W < 640;\n const isTablet = W >= 640 && W < 1024;\n const px = pt.isHovered ? pt.localX : cx;\n const py = pt.isHovered ? pt.localY : cy;\n\n const targetTiltX = pt.isHovered ? ((px - cx) / cx) * 12 : 0;\n const targetTiltY = pt.isHovered ? ((py - cy) / cy) * 10 : 0;\n S.cardTiltX += (targetTiltX - S.cardTiltX) * Math.min(1, dt * 6);\n S.cardTiltY += (targetTiltY - S.cardTiltY) * Math.min(1, dt * 6);\n\n /* ---------- Content Layout ---------- */\n let curY = Math.max(30, H * 0.07);\n\n /* 1. Pill Badge */\n if (badgeText) {\n ctx.font = '600 ' + (isMobile ? 11 : 12) + 'px ' + MONO;\n const badgeW = ctx.measureText(badgeText).width + 32;\n const badgeH = isMobile ? 26 : 30;\n const badgeX = cx - badgeW / 2;\n const badgeY = curY;\n\n const inBadge = pt.isHovered && px >= badgeX && px <= badgeX + badgeW && py >= badgeY && py <= badgeY + badgeH;\n S.badgeHover += ((inBadge ? 1 : 0) - S.badgeHover) * Math.min(1, dt * 10);\n\n /* Badge background with subtle border */\n ctx.fillStyle = rgba('#ffffff', 0.06 + S.badgeHover * 0.08);\n roundRect(ctx, badgeX, badgeY, badgeW, badgeH, badgeH / 2);\n ctx.fill();\n\n ctx.strokeStyle = rgba(c1, 0.4 + S.badgeHover * 0.4);\n ctx.lineWidth = 1;\n roundRect(ctx, badgeX, badgeY, badgeW, badgeH, badgeH / 2);\n ctx.stroke();\n\n /* Glowing indicator dot */\n const dotX = badgeX + 13;\n const dotY = badgeY + badgeH / 2;\n ctx.fillStyle = c2;\n ctx.beginPath();\n ctx.arc(dotX, dotY, 3, 0, Math.PI * 2);\n ctx.fill();\n glow(ctx, c2, dotX, dotY, 10, 0.8);\n\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillStyle = '#ffffff';\n ctx.fillText(badgeText, badgeX + 22, badgeY + badgeH / 2);\n\n curY += badgeH + (isMobile ? 16 : 22);\n }\n\n /* 2. Main Headline */\n const titleSize = clamp(W * 0.046, isMobile ? 24 : 34, 56);\n ctx.font = '800 ' + titleSize + 'px ' + SANS;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n\n const maxTextW = Math.min(W * 0.9, 960);\n const titleLines = wrap(ctx, heading, maxTextW);\n const lineH = titleSize * 1.14;\n\n titleLines.forEach(function (line, idx) {\n const ly = curY + idx * lineH;\n /* Gradient text fill */\n const textGrad = ctx.createLinearGradient(cx - maxTextW * 0.4, ly, cx + maxTextW * 0.4, ly);\n textGrad.addColorStop(0, '#ffffff');\n textGrad.addColorStop(0.55, '#f0f3fa');\n textGrad.addColorStop(1, mix('#ffffff', c2, 0.45));\n ctx.fillStyle = textGrad;\n ctx.fillText(line, cx, ly);\n });\n\n curY += titleLines.length * lineH + (isMobile ? 12 : 18);\n\n /* 3. Subtitle */\n const subSize = clamp(W * 0.018, isMobile ? 13 : 15, 18);\n ctx.font = '400 ' + subSize + 'px ' + SANS;\n const subLines = wrap(ctx, sub, Math.min(W * 0.85, 780));\n const subLineH = subSize * 1.45;\n\n ctx.fillStyle = 'rgba(235, 240, 255, 0.78)';\n subLines.forEach(function (line, idx) {\n ctx.fillText(line, cx, curY + idx * subLineH);\n });\n\n curY += subLines.length * subLineH + (isMobile ? 22 : 30);\n\n /* 4. Action Buttons (Primary & Secondary) */\n const btnH = isMobile ? 44 : 50;\n const btn1W = Math.max(isMobile ? 150 : 180, ctx.measureText(btn1Label).width + 50);\n const btn2W = Math.max(isMobile ? 140 : 170, ctx.measureText(btn2Label).width + 46);\n const btnGap = isMobile ? 12 : 16;\n const totalBtnsW = isMobile ? W * 0.88 : btn1W + btnGap + btn2W;\n\n let btn1X, btn1Y, btn2X, btn2Y;\n if (isMobile) {\n btn1X = cx - totalBtnsW / 2;\n btn1Y = curY;\n btn2X = cx - totalBtnsW / 2;\n btn2Y = curY + btnH + btnGap;\n curY += btnH * 2 + btnGap + 24;\n } else {\n btn1X = cx - (btn1W + btnGap + btn2W) / 2;\n btn1Y = curY;\n btn2X = btn1X + btn1W + btnGap;\n btn2Y = curY;\n curY += btnH + 34;\n }\n\n const inBtn1 = pt.isHovered && px >= btn1X && px <= (btn1X + (isMobile ? totalBtnsW : btn1W)) && py >= btn1Y && py <= btn1Y + btnH;\n const inBtn2 = pt.isHovered && px >= btn2X && px <= (btn2X + (isMobile ? totalBtnsW : btn2W)) && py >= btn2Y && py <= btn2Y + btnH;\n\n S.btn1Hover += ((inBtn1 ? 1 : 0) - S.btn1Hover) * Math.min(1, dt * 10);\n S.btn2Hover += ((inBtn2 ? 1 : 0) - S.btn2Hover) * Math.min(1, dt * 10);\n\n const isDown = !!(pt.isDown || pt.down);\n if (inBtn1 && isDown) S.btn1Press = Math.min(1, S.btn1Press + dt * 14);\n else S.btn1Press = Math.max(0, S.btn1Press - dt * 10);\n\n if (inBtn2 && isDown) S.btn2Press = Math.min(1, S.btn2Press + dt * 14);\n else S.btn2Press = Math.max(0, S.btn2Press - dt * 10);\n\n if (!S.wasDown && isDown) {\n if (inBtn1) {\n S.ripples.push({ x: px, y: py, r: 0, alpha: 0.8, color: '#ffffff' });\n if (typeof env.emit === 'function') env.emit('primaryClick');\n } else if (inBtn2) {\n S.ripples.push({ x: px, y: py, r: 0, alpha: 0.8, color: c2 });\n if (typeof env.emit === 'function') env.emit('secondaryClick');\n }\n }\n S.wasDown = isDown;\n\n /* Render Primary Button */\n const b1Scale = 1 + S.btn1Hover * 0.03 - S.btn1Press * 0.04;\n const b1ActualW = isMobile ? totalBtnsW : btn1W;\n ctx.save();\n ctx.translate(btn1X + b1ActualW / 2, btn1Y + btnH / 2);\n ctx.scale(b1Scale, b1Scale);\n\n /* Primary glow behind button */\n glow(ctx, c1, 0, 0, b1ActualW * 0.65, 0.45 * (0.8 + S.btn1Hover * 0.6));\n\n /* Fluid gradient fill for button */\n const btnGrad = ctx.createLinearGradient(-b1ActualW / 2, -btnH / 2, b1ActualW / 2, btnH / 2);\n btnGrad.addColorStop(0, mix(c1, '#ffffff', 0.15));\n btnGrad.addColorStop(1, mix(c2, cDark, 0.25));\n ctx.fillStyle = btnGrad;\n roundRect(ctx, -b1ActualW / 2, -btnH / 2, b1ActualW, btnH, 12);\n ctx.fill();\n\n /* Primary button border highlight */\n ctx.strokeStyle = rgba('#ffffff', 0.35 + S.btn1Hover * 0.35);\n ctx.lineWidth = 1.2;\n roundRect(ctx, -b1ActualW / 2, -btnH / 2, b1ActualW, btnH, 12);\n ctx.stroke();\n\n /* Label */\n ctx.font = '600 ' + (isMobile ? 14 : 15) + 'px ' + SANS;\n ctx.fillStyle = '#ffffff';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(btn1Label, 0, 0);\n ctx.restore();\n\n /* Render Secondary Button */\n const b2Scale = 1 + S.btn2Hover * 0.03 - S.btn2Press * 0.04;\n const b2ActualW = isMobile ? totalBtnsW : btn2W;\n ctx.save();\n ctx.translate(btn2X + b2ActualW / 2, btn2Y + btnH / 2);\n ctx.scale(b2Scale, b2Scale);\n\n /* Secondary Glass Fill */\n ctx.fillStyle = rgba('#ffffff', 0.05 + S.btn2Hover * 0.08);\n roundRect(ctx, -b2ActualW / 2, -btnH / 2, b2ActualW, btnH, 12);\n ctx.fill();\n\n /* Glass Border */\n ctx.strokeStyle = rgba('#ffffff', 0.18 + S.btn2Hover * 0.32);\n ctx.lineWidth = 1.2;\n roundRect(ctx, -b2ActualW / 2, -btnH / 2, b2ActualW, btnH, 12);\n ctx.stroke();\n\n /* Secondary Label */\n ctx.font = '600 ' + (isMobile ? 14 : 15) + 'px ' + SANS;\n ctx.fillStyle = '#f0f4ff';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(btn2Label, 0, 0);\n ctx.restore();\n\n /* 5. 3D Floating Mockup Card */\n const cardW = Math.min(W * 0.88, 920);\n const cardH = Math.max(160, Math.min(H - curY - 30, 340));\n\n if (cardH > 140) {\n const cardX = cx - cardW / 2;\n const cardY = curY + Math.sin(S.cardFloat) * 6;\n\n ctx.save();\n ctx.translate(cx, cardY + cardH / 2);\n /* Perspective tilt emulation */\n ctx.transform(1, S.cardTiltY * 0.003, S.cardTiltX * 0.003, 1, 0, 0);\n\n /* Card Glow */\n glow(ctx, c1, 0, -cardH * 0.3, cardW * 0.5, 0.35);\n\n /* Card Base */\n ctx.fillStyle = 'rgba(13, 15, 23, 0.75)';\n roundRect(ctx, -cardW / 2, -cardH / 2, cardW, cardH, 18);\n ctx.fill();\n\n /* Card Gradient Border */\n const cardBorderGrad = ctx.createLinearGradient(-cardW / 2, -cardH / 2, cardW / 2, cardH / 2);\n cardBorderGrad.addColorStop(0, rgba(c1, 0.6));\n cardBorderGrad.addColorStop(0.5, rgba('#ffffff', 0.15));\n cardBorderGrad.addColorStop(1, rgba(c3, 0.4));\n ctx.strokeStyle = cardBorderGrad;\n ctx.lineWidth = 1.5;\n roundRect(ctx, -cardW / 2, -cardH / 2, cardW, cardH, 18);\n ctx.stroke();\n\n /* Header Bar inside card */\n const barH = 38;\n ctx.fillStyle = 'rgba(255, 255, 255, 0.04)';\n roundRect(ctx, -cardW / 2, -cardH / 2, cardW, barH, 18);\n ctx.fill();\n\n /* Window dots */\n const dotColors = ['#ff5f56', '#ffbd2e', '#27c93f'];\n for (let i = 0; i < 3; i++) {\n ctx.fillStyle = dotColors[i];\n ctx.beginPath();\n ctx.arc(-cardW / 2 + 20 + i * 16, -cardH / 2 + barH / 2, 4.5, 0, Math.PI * 2);\n ctx.fill();\n }\n\n /* Window Title */\n ctx.font = '500 12px ' + MONO;\n ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText('exode-runtime-v2.0.wasm — 60 FPS', 0, -cardH / 2 + barH / 2);\n\n /* Live Telemetry / Graphic inside card */\n const innerY = -cardH / 2 + barH + 16;\n const innerH = cardH - barH - 32;\n\n /* Animated Sine Wave Spectrum */\n ctx.strokeStyle = rgba(c2, 0.7);\n ctx.lineWidth = 2.5;\n ctx.beginPath();\n const waveCount = 40;\n for (let wi = 0; wi <= waveCount; wi++) {\n const wx = -cardW / 2 + 24 + (wi / waveCount) * (cardW - 48);\n const phase = S.age * 3 * speed + wi * 0.25;\n const wy = innerY + innerH / 2 + Math.sin(phase) * (innerH * 0.28);\n if (wi === 0) ctx.moveTo(wx, wy);\n else ctx.lineTo(wx, wy);\n }\n ctx.stroke();\n\n /* Glow on wave crests */\n const crestWi = Math.floor((Math.sin(S.age * 2) * 0.5 + 0.5) * waveCount);\n const crestX = -cardW / 2 + 24 + (crestWi / waveCount) * (cardW - 48);\n const crestY = innerY + innerH / 2 + Math.sin(S.age * 3 * speed + crestWi * 0.25) * (innerH * 0.28);\n glow(ctx, '#ffffff', crestX, crestY, 24, 0.9);\n\n /* Status text at bottom of card */\n ctx.font = '600 11px ' + MONO;\n ctx.fillStyle = rgba(c1, 0.9);\n ctx.textAlign = 'left';\n ctx.fillText('STATE: ACTIVE // ZERO LATENCY', -cardW / 2 + 24, cardH / 2 - 14);\n\n ctx.textAlign = 'right';\n ctx.fillStyle = 'rgba(255,255,255,0.4)';\n ctx.fillText('PHYSICS ENGINE: ONLINE', cardW / 2 - 24, cardH / 2 - 14);\n\n ctx.restore();\n }\n\n /* Animate Ripples */\n for (let ri = S.ripples.length - 1; ri >= 0; ri--) {\n const rip = S.ripples[ri];\n rip.r += dt * 180;\n rip.alpha -= dt * 1.5;\n if (rip.alpha <= 0) {\n S.ripples.splice(ri, 1);\n continue;\n }\n ctx.strokeStyle = rgba(rip.color, rip.alpha);\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(rip.x, rip.y, rip.r, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n ctx.restore();\n}\n"
212
+ }
213
+ ]
214
+ },
215
+ "layoutProps": {
216
+ "layoutSizingHorizontal": "fill",
217
+ "layoutSizingVertical": "fill"
218
+ },
219
+ "zIndex": 1,
220
+ "interactions": []
221
+ }
222
+ ]
223
+ }
@@ -0,0 +1,214 @@
1
+ {
2
+ "id": "mui_gooey_toggle",
3
+ "name": "Gooey Toggle",
4
+ "width": 800,
5
+ "height": 400,
6
+ "cornerRadius": 0,
7
+ "fit": "Layout",
8
+ "backgroundColor": "#080a10",
9
+ "opacity": 1,
10
+ "x": -400,
11
+ "y": -200,
12
+ "responsive": {
13
+ "enabled": true,
14
+ "mode": "constraint",
15
+ "width": {
16
+ "type": "fixed"
17
+ },
18
+ "height": {
19
+ "type": "fixed"
20
+ },
21
+ "maintainAspectRatio": false,
22
+ "scaleObjects": false,
23
+ "breakpoints": [
24
+ {
25
+ "id": "bp_mobile",
26
+ "name": "Mobile",
27
+ "minWidth": 0,
28
+ "maxWidth": 599
29
+ },
30
+ {
31
+ "id": "bp_tablet",
32
+ "name": "Tablet",
33
+ "minWidth": 600,
34
+ "maxWidth": 1023
35
+ },
36
+ {
37
+ "id": "bp_desktop",
38
+ "name": "Desktop",
39
+ "minWidth": 1024
40
+ }
41
+ ]
42
+ },
43
+ "layoutProps": {
44
+ "mode": "linear",
45
+ "direction": "vertical",
46
+ "gap": 0,
47
+ "padding": {
48
+ "top": 0,
49
+ "right": 0,
50
+ "bottom": 0,
51
+ "left": 0
52
+ },
53
+ "alignItems": "stretch",
54
+ "layoutSizingHorizontal": "fixed",
55
+ "layoutSizingVertical": "fixed"
56
+ },
57
+ "seo": {
58
+ "title": "Gooey Toggle — Exode Motion UI",
59
+ "description": "A liquid mercury metaball toggle switch with a stretching fluid bridge during transit, break-away separation physics, liquid splashes, and on/off triggers."
60
+ },
61
+ "stateMachine": {
62
+ "inputs": [
63
+ {
64
+ "id": "in_checked",
65
+ "name": "checked",
66
+ "value": {
67
+ "type": "Boolean",
68
+ "value": false,
69
+ "defaultValue": false
70
+ }
71
+ },
72
+ {
73
+ "id": "in_label",
74
+ "name": "label",
75
+ "value": {
76
+ "type": "Text",
77
+ "value": "Quantum Superposition",
78
+ "defaultValue": "Quantum Superposition"
79
+ }
80
+ },
81
+ {
82
+ "id": "in_sublabel",
83
+ "name": "sublabel",
84
+ "value": {
85
+ "type": "Text",
86
+ "value": "Liquid metaball toggle with fluid bridge separation",
87
+ "defaultValue": "Liquid metaball toggle with fluid bridge separation"
88
+ }
89
+ },
90
+ {
91
+ "id": "in_speed",
92
+ "name": "speed",
93
+ "value": {
94
+ "type": "Number",
95
+ "value": 1,
96
+ "defaultValue": 1
97
+ }
98
+ },
99
+ {
100
+ "id": "in_accent",
101
+ "name": "accent",
102
+ "value": {
103
+ "type": "Text",
104
+ "value": "#06b6d4",
105
+ "defaultValue": "#06b6d4"
106
+ }
107
+ },
108
+ {
109
+ "id": "in_accent2",
110
+ "name": "accent2",
111
+ "value": {
112
+ "type": "Text",
113
+ "value": "#3b82f6",
114
+ "defaultValue": "#3b82f6"
115
+ }
116
+ },
117
+ {
118
+ "id": "in_offColor",
119
+ "name": "offColor",
120
+ "value": {
121
+ "type": "Text",
122
+ "value": "#1e2230",
123
+ "defaultValue": "#1e2230"
124
+ }
125
+ },
126
+ {
127
+ "id": "in_bg",
128
+ "name": "bg",
129
+ "value": {
130
+ "type": "Text",
131
+ "value": "#080a10",
132
+ "defaultValue": "#080a10"
133
+ }
134
+ }
135
+ ],
136
+ "layers": [
137
+ {
138
+ "name": "Base",
139
+ "entryStateId": "s_idle",
140
+ "states": [
141
+ {
142
+ "id": "s_idle",
143
+ "name": "Idle",
144
+ "loop": true,
145
+ "transitions": []
146
+ }
147
+ ]
148
+ }
149
+ ],
150
+ "logicNodes": []
151
+ },
152
+ "animations": [],
153
+ "physics": {
154
+ "enabled": false,
155
+ "gravity": {
156
+ "x": 0,
157
+ "y": 9.8
158
+ }
159
+ },
160
+ "animationFlow": [],
161
+ "navigation": [],
162
+ "stateVariables": [],
163
+ "objects": [
164
+ {
165
+ "id": "stage",
166
+ "name": "Stage",
167
+ "type": "Group",
168
+ "transform": {
169
+ "x": 0,
170
+ "y": 0,
171
+ "rotation": 0,
172
+ "scale_x": 1,
173
+ "scale_y": 1,
174
+ "opacity": 1,
175
+ "skew_x": 0,
176
+ "skew_y": 0
177
+ },
178
+ "width": 800,
179
+ "height": 400,
180
+ "geometry": {
181
+ "type": "Rectangle",
182
+ "width": 800,
183
+ "height": 400,
184
+ "cornerRadius": 0
185
+ },
186
+ "cornerRadius": 0,
187
+ "children": [],
188
+ "clipContent": true,
189
+ "style": {
190
+ "fill": {
191
+ "type": "Solid",
192
+ "color": "#080a10",
193
+ "opacity": 1
194
+ },
195
+ "effects": [
196
+ {
197
+ "id": "fx_gooey-toggle",
198
+ "type": "CustomEffect",
199
+ "name": "Gooey Toggle",
200
+ "isEnabled": true,
201
+ "params": {},
202
+ "code": "/* Shared by every motion-ui effect (prepended to each one).\n Rules for this file and the effects: the .bexode compiler minifies effect\n code, so use explicit semicolons, block comments only, and never write a\n minus followed by a negative value (it collapses to a decrement). */\n\n/* Read a live state-machine input, falling back to a default. */\nfunction inp(env, name, fallback) {\n const v = env.inputs ? env.inputs[name] : undefined;\n if (v === undefined || v === null || v === '') return fallback;\n if (typeof fallback === 'number') {\n const n = Number(v);\n return isFinite(n) ? n : fallback;\n }\n if (typeof fallback === 'boolean') return v === true || v === 1 || v === 'true';\n return v;\n}\n\n/* '#rgb' / '#rrggbb' to [r, g, b]; anything else falls back to white. */\nfunction rgbOf(hex) {\n let h = String(hex || '').trim().replace('#', '');\n if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];\n if (!/^[0-9a-fA-F]{6}$/.test(h)) return [255, 255, 255];\n const n = parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction rgba(hex, a) {\n const c = rgbOf(hex);\n return 'rgba(' + c[0] + ',' + c[1] + ',' + c[2] + ',' + Math.max(0, Math.min(1, a)) + ')';\n}\n\nfunction mix(a, b, t) {\n const x = rgbOf(a), y = rgbOf(b);\n const r = Math.round(x[0] + (y[0] - x[0]) * t);\n const g = Math.round(x[1] + (y[1] - x[1]) * t);\n const bl = Math.round(x[2] + (y[2] - x[2]) * t);\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + bl).toString(16).slice(1);\n}\n\n/* A soft round glow, rendered once per colour and blitted with drawImage\n (much cheaper than shadowBlur on every particle). */\nconst __glows = {};\nfunction glowSprite(hex) {\n if (__glows[hex]) return __glows[hex];\n const s = 64;\n const c = document.createElement('canvas');\n c.width = s; c.height = s;\n const g = c.getContext('2d');\n const gr = g.createRadialGradient(s / 2, s / 2, 0, s / 2, s / 2, s / 2);\n gr.addColorStop(0, rgba(hex, 1));\n gr.addColorStop(0.18, rgba(hex, 0.55));\n gr.addColorStop(0.45, rgba(hex, 0.14));\n gr.addColorStop(1, rgba(hex, 0));\n g.fillStyle = gr;\n g.fillRect(0, 0, s, s);\n __glows[hex] = c;\n return c;\n}\n\nfunction glow(ctx, hex, x, y, r, a) {\n if (r <= 0 || a <= 0) return;\n ctx.globalAlpha = Math.min(1, a);\n ctx.drawImage(glowSprite(hex), x - r, y - r, r * 2, r * 2);\n ctx.globalAlpha = 1;\n}\n\n/* Frame clock: seconds since start and a clamped delta. */\nfunction tick(S) {\n const now = performance.now();\n if (!S.t0) { S.t0 = now; S.last = now; }\n S.dt = Math.min(0.05, Math.max(0.001, (now - S.last) / 1000));\n S.last = now;\n S.age = (now - S.t0) / 1000;\n return S.dt;\n}\n\n/* Deterministic pseudo-random so layouts are stable across resizes. */\nfunction rng(seed) {\n let s = seed >>> 0 || 1;\n return function () {\n s = (s * 1664525 + 1013904223) >>> 0;\n return s / 4294967296;\n };\n}\n\nfunction ease(x) { return x < 0.5 ? 4 * x * x * x : 1 + (Math.pow(2 * x - 2, 3)) / 2; }\nfunction clamp(v, a, b) { return v < a ? a : v > b ? b : v; }\n\nconst SANS = 'Inter, Helvetica, Arial, sans-serif';\nconst MONO = 'ui-monospace, Menlo, monospace';\n\nfunction roundRect(ctx, x, y, w, h, r) {\n r = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + r, y);\n ctx.arcTo(x + w, y, x + w, y + h, r);\n ctx.arcTo(x + w, y + h, x, y + h, r);\n ctx.arcTo(x, y + h, x, y, r);\n ctx.arcTo(x, y, x + w, y, r);\n ctx.closePath();\n}\n\n/* Shared backdrop: flat colour, soft vignette and fine grain. */\nfunction backdrop(ctx, W, H, bg, tint, amt) {\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W, H);\n if (tint && amt > 0) {\n const gr = ctx.createRadialGradient(W / 2, H * 0.45, 0, W / 2, H * 0.45, Math.max(W, H) * 0.7);\n gr.addColorStop(0, rgba(tint, 0.10 * amt));\n gr.addColorStop(1, rgba(tint, 0));\n ctx.fillStyle = gr;\n ctx.fillRect(0, 0, W, H);\n }\n const vg = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.3, W / 2, H / 2, Math.max(W, H) * 0.8);\n vg.addColorStop(0, 'rgba(0,0,0,0)');\n vg.addColorStop(1, 'rgba(0,0,0,0.45)');\n ctx.fillStyle = vg;\n ctx.fillRect(0, 0, W, H);\n}\n\n/* Greedy word wrap with the current ctx.font. */\nfunction wrap(ctx, text, maxW) {\n const out = [];\n String(text).split('\\n').forEach(function (para) {\n const words = para.split(' ');\n let cur = '';\n for (let i = 0; i < words.length; i++) {\n const next = cur ? cur + ' ' + words[i] : words[i];\n if (cur && ctx.measureText(next).width > maxW) { out.push(cur); cur = words[i]; } else cur = next;\n }\n out.push(cur);\n });\n return out;\n}\n\n/* Largest font size (up to max) at which text wraps into maxLines within maxW. */\nfunction fitFont(ctx, text, weight, family, maxW, maxLines, max, min) {\n let fs = max;\n while (fs > min) {\n ctx.font = weight + ' ' + fs + 'px ' + family;\n if (wrap(ctx, text, maxW).length <= maxLines) break;\n fs = fs * 0.92;\n }\n ctx.font = weight + ' ' + fs + 'px ' + family;\n return fs;\n}\n\n/* Smoothed pointer that idles along a slow figure-eight when the cursor is away. */\nfunction follow(S, env, W, H, k) {\n const pt = env.pointer || {};\n const t = S.age || 0;\n const tx = pt.isHovered ? pt.localX : W / 2 + Math.sin(t * 0.37) * W * 0.3;\n const ty = pt.isHovered ? pt.localY : H / 2 + Math.sin(t * 0.74) * H * 0.22;\n if (S.px === undefined) { S.px = tx; S.py = ty; }\n S.px += (tx - S.px) * Math.min(1, S.dt * (k || 6));\n S.py += (ty - S.py) * Math.min(1, S.dt * (k || 6));\n S.inside = !!pt.isHovered;\n return pt;\n}\n\n/* Optional centred headline over a background effect. */\nfunction title(ctx, W, H, text, sub, ink, accent, bg) {\n if (!text) return;\n if (bg) {\n /* Soft scrim so the headline reads over busy motion. */\n const sc = ctx.createRadialGradient(W / 2, H / 2, 0, W / 2, H / 2, Math.min(W * 0.5, 560));\n sc.addColorStop(0, rgba(bg, 0.72));\n sc.addColorStop(1, rgba(bg, 0));\n ctx.fillStyle = sc;\n ctx.fillRect(0, 0, W, H);\n }\n ctx.textAlign = 'center';\n ctx.textBaseline = 'alphabetic';\n const maxW = Math.min(W * 0.86, 900);\n const fs = fitFont(ctx, text, 600, SANS, maxW, 2, clamp(W * 0.065, 26, 72), 16);\n const lines = wrap(ctx, text, maxW);\n const lh = fs * 1.08;\n const y0 = H / 2 - (lines.length * lh) / 2 + fs * 0.8 - (sub ? fs * 0.35 : 0);\n ctx.fillStyle = ink;\n lines.forEach(function (l, i) { ctx.fillText(l, W / 2, y0 + i * lh); });\n if (sub) {\n ctx.font = '500 ' + clamp(fs * 0.24, 11, 15) + 'px ' + MONO;\n ctx.fillStyle = rgba(accent, 0.85);\n ctx.fillText(String(sub).toUpperCase(), W / 2, y0 + (lines.length - 1) * lh + fs * 0.9);\n }\n}\n\n/* Gooey Toggle: a liquid mercury metaball toggle switch.\n Features a fluid liquid bridge connecting the thumb during transit,\n metaball separation physics as it breaks away, liquid droplet splashes,\n and dynamic on/off state triggers. */\nlet S = null;\n\nfunction render(ctx, env) {\n const W = env.width, H = env.height;\n if (!S) {\n S = {\n isOn: false,\n thumbPos: 0,\n liquidBridge: 0,\n hover: 0,\n press: 0,\n wasDown: false,\n droplets: [],\n ripples: [],\n };\n }\n const dt = tick(S);\n\n const initialOn = inp(env, 'checked', false);\n const label = String(inp(env, 'label', 'Quantum Superposition'));\n const sublabel = String(inp(env, 'sublabel', 'Liquid metaball toggle with fluid bridge separation'));\n const accent = inp(env, 'accent', '#06b6d4');\n const accent2 = inp(env, 'accent2', '#3b82f6');\n const offColor = inp(env, 'offColor', '#1e2230');\n const bg = inp(env, 'bg', '#080a10');\n const speed = clamp(inp(env, 'speed', 1), 0.1, 4);\n\n if (!S.initialized) {\n S.isOn = !!initialOn;\n S.thumbPos = S.isOn ? 1 : 0;\n S.initialized = true;\n }\n\n ctx.save();\n ctx.translate(-W / 2, -H / 2);\n\n /* Clean Dark Canvas Backdrop */\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W, H);\n\n const cx = W / 2, cy = H / 2;\n const isMobile = W < 600;\n const pt = env.pointer || {};\n const px = pt.isHovered ? pt.localX : -9999;\n const py = pt.isHovered ? pt.localY : -9999;\n const isDown = !!(pt.isDown || pt.down);\n\n /* Switch Track Dimensions */\n const trackW = isMobile ? 120 : 144;\n const trackH = isMobile ? 64 : 76;\n const thumbR = (trackH - 14) / 2;\n const travelDist = trackW - thumbR * 2 - 14;\n\n const cardW = Math.min(W * 0.9, 560);\n const cardH = isMobile ? 140 : 124;\n const cardX = cx - cardW / 2;\n const cardY = cy - cardH / 2;\n\n /* Card Base */\n ctx.fillStyle = 'rgba(15, 18, 27, 0.75)';\n roundRect(ctx, cardX, cardY, cardW, cardH, 22);\n ctx.fill();\n\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';\n ctx.lineWidth = 1.2;\n roundRect(ctx, cardX, cardY, cardW, cardH, 22);\n ctx.stroke();\n\n /* Switch Coordinates */\n const switchX = cardX + cardW - trackW - 24;\n const switchY = cy - trackH / 2;\n\n const inSwitch = pt.isHovered && px >= switchX && px <= switchX + trackW && py >= switchY && py <= switchY + trackH;\n S.hover += ((inSwitch ? 1 : 0) - S.hover) * Math.min(1, dt * 10);\n\n /* Click toggle trigger */\n if (!S.wasDown && isDown && inSwitch) {\n S.isOn = !S.isOn;\n\n /* Droplet Splash */\n const spawnColor = S.isOn ? accent : '#94a3b8';\n for (let i = 0; i < 12; i++) {\n const angle = (i / 12) * Math.PI * 2;\n const vel = 50 + Math.random() * 80;\n S.droplets.push({\n x: switchX + (S.isOn ? trackW - thumbR - 7 : thumbR + 7),\n y: switchY + trackH / 2,\n vx: Math.cos(angle) * vel,\n vy: Math.sin(angle) * vel,\n r: 2.5 + Math.random() * 2.5,\n alpha: 1,\n color: spawnColor,\n });\n }\n\n S.ripples.push({\n x: switchX + (S.isOn ? trackW - thumbR - 7 : thumbR + 7),\n y: switchY + trackH / 2,\n r: 0,\n alpha: 0.85,\n color: spawnColor,\n });\n\n if (typeof env.emit === 'function') {\n env.emit('toggle', S.isOn);\n env.emit(S.isOn ? 'stateOn' : 'stateOff');\n }\n }\n S.wasDown = isDown;\n\n /* Spring Interpolation for Thumb Position */\n const targetPos = S.isOn ? 1 : 0;\n const prevPos = S.thumbPos;\n S.thumbPos += (targetPos - S.thumbPos) * Math.min(1, dt * 11 * speed);\n\n /* Liquid Bridge Stretch Factor */\n const distTravelled = Math.abs(S.thumbPos - prevPos);\n const bridgeTarget = Math.sin(S.thumbPos * Math.PI) * 1.2;\n S.liquidBridge += (bridgeTarget - S.liquidBridge) * Math.min(1, dt * 14 * speed);\n\n /* ---------- Render Track ---------- */\n const trackColor = mix(offColor, 'rgba(6, 182, 212, 0.25)', S.thumbPos);\n ctx.fillStyle = trackColor;\n roundRect(ctx, switchX, switchY, trackW, trackH, trackH / 2);\n ctx.fill();\n\n ctx.strokeStyle = rgba('#ffffff', 0.12 + S.thumbPos * 0.2);\n ctx.lineWidth = 1.2;\n roundRect(ctx, switchX, switchY, trackW, trackH, trackH / 2);\n ctx.stroke();\n\n if (S.thumbPos > 0.05) {\n glow(ctx, accent, switchX + trackW / 2, switchY + trackH / 2, trackW * 0.7, 0.4 * S.thumbPos);\n }\n\n /* ---------- Render Liquid Gooey Thumb ---------- */\n const thumbBaseX = switchX + 7 + thumbR + S.thumbPos * travelDist;\n const thumbBaseY = switchY + trackH / 2;\n\n /* If in motion, draw liquid bridge connecting origin and destination */\n const thumbColor = S.thumbPos > 0.5 ? accent : '#f1f5f9';\n\n ctx.save();\n /* Draw Liquid Bridge if switching */\n if (S.liquidBridge > 0.08) {\n const originX = S.isOn ? switchX + 7 + thumbR : switchX + trackW - 7 - thumbR;\n const midX = (originX + thumbBaseX) / 2;\n const bridgeThickness = Math.max(3, thumbR * (1 - S.liquidBridge * 0.55));\n\n ctx.fillStyle = thumbColor;\n ctx.beginPath();\n ctx.moveTo(originX, thumbBaseY - thumbR * 0.8);\n ctx.quadraticCurveTo(midX, thumbBaseY - bridgeThickness, thumbBaseX, thumbBaseY - thumbR * 0.8);\n ctx.lineTo(thumbBaseX, thumbBaseY + thumbR * 0.8);\n ctx.quadraticCurveTo(midX, thumbBaseY + bridgeThickness, originX, thumbBaseY + thumbR * 0.8);\n ctx.closePath();\n ctx.fill();\n }\n\n /* Draw Main Liquid Thumb Ball */\n glow(ctx, S.thumbPos > 0.5 ? accent : '#ffffff', thumbBaseX, thumbBaseY, thumbR * 1.6, 0.6);\n\n ctx.fillStyle = thumbColor;\n ctx.beginPath();\n ctx.arc(thumbBaseX, thumbBaseY, thumbR, 0, Math.PI * 2);\n ctx.fill();\n\n /* Surface Specular Highlight */\n ctx.fillStyle = 'rgba(255, 255, 255, 0.65)';\n ctx.beginPath();\n ctx.arc(thumbBaseX - thumbR * 0.3, thumbBaseY - thumbR * 0.3, thumbR * 0.32, 0, Math.PI * 2);\n ctx.fill();\n\n ctx.restore();\n\n /* ---------- Left Labels ---------- */\n const textLeftX = cardX + 24;\n const maxLabelW = switchX - textLeftX - 16;\n\n ctx.font = '700 ' + (isMobile ? 16 : 18) + 'px ' + SANS;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillStyle = '#ffffff';\n ctx.fillText(label, textLeftX, cardY + (isMobile ? 24 : 30));\n\n ctx.font = '400 ' + (isMobile ? 12 : 13) + 'px ' + SANS;\n ctx.fillStyle = 'rgba(226, 232, 240, 0.7)';\n const subWrap = wrap(ctx, sublabel, maxLabelW);\n subWrap.slice(0, 2).forEach(function (sw, si) {\n ctx.fillText(sw, textLeftX, cardY + (isMobile ? 52 : 60) + si * 18);\n });\n\n /* Status Tag */\n const statusText = S.isOn ? 'ACTIVE // COHERENT' : 'IDLE // DECOHERENT';\n ctx.font = '700 10px ' + MONO;\n ctx.fillStyle = S.isOn ? accent : '#64748b';\n ctx.fillText('STATE: ' + statusText, textLeftX, cardY + cardH - (isMobile ? 18 : 24));\n\n /* Droplets */\n for (let di = S.droplets.length - 1; di >= 0; di--) {\n const d = S.droplets[di];\n d.x += d.vx * dt;\n d.y += d.vy * dt;\n d.alpha -= dt * 2.2;\n if (d.alpha <= 0) {\n S.droplets.splice(di, 1);\n continue;\n }\n ctx.fillStyle = rgba(d.color, d.alpha);\n ctx.beginPath();\n ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2);\n ctx.fill();\n }\n\n /* Ripples */\n for (let ri = S.ripples.length - 1; ri >= 0; ri--) {\n const rip = S.ripples[ri];\n rip.r += dt * 140;\n rip.alpha -= dt * 2.2;\n if (rip.alpha <= 0) {\n S.ripples.splice(ri, 1);\n continue;\n }\n ctx.strokeStyle = rgba(rip.color, rip.alpha);\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(rip.x, rip.y, rip.r, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n ctx.restore();\n}\n"
203
+ }
204
+ ]
205
+ },
206
+ "layoutProps": {
207
+ "layoutSizingHorizontal": "fill",
208
+ "layoutSizingVertical": "fill"
209
+ },
210
+ "zIndex": 1,
211
+ "interactions": []
212
+ }
213
+ ]
214
+ }