@agent-native/core 0.80.0 → 0.80.2

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 (228) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
  5. package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
  7. package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
  9. package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
  11. package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  12. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
  13. package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  14. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
  15. package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  16. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
  17. package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  18. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
  19. package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  20. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
  21. package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  22. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
  23. package/corpus/core/docs/content/skills-guide.mdx +5 -0
  24. package/corpus/core/docs/content/template-design.mdx +14 -1
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/application-state/handlers.ts +1 -1
  27. package/corpus/core/src/brand-kit/index.ts +1 -1
  28. package/corpus/core/src/cli/skills.ts +18 -0
  29. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  30. package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
  31. package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
  32. package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
  33. package/corpus/core/src/extensions/path.ts +1 -4
  34. package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
  35. package/corpus/core/src/mcp/build-server.ts +2 -2
  36. package/corpus/core/src/mcp/embed-app.ts +35 -1
  37. package/corpus/core/src/org/handlers.ts +5 -5
  38. package/corpus/core/src/org/plugin.ts +2 -2
  39. package/corpus/core/src/scripts/call-agent.ts +1 -1
  40. package/corpus/core/src/scripts/db/safety.ts +1 -1
  41. package/corpus/core/src/server/auth.ts +3 -0
  42. package/corpus/core/src/server/builder-design-systems.ts +308 -0
  43. package/corpus/core/src/server/core-routes-plugin.ts +29 -3
  44. package/corpus/core/src/server/index.ts +8 -0
  45. package/corpus/core/src/styles/agent-native.css +14 -7
  46. package/corpus/core/src/usage/store.ts +2 -2
  47. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
  48. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
  49. package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
  50. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
  51. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
  52. package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
  53. package/corpus/templates/analytics/app/i18n-data.ts +404 -0
  54. package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
  57. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
  58. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
  59. package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
  66. package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
  67. package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
  68. package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
  69. package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
  70. package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
  71. package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
  73. package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
  74. package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
  75. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
  76. package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
  77. package/corpus/templates/content/shared/document-export.ts +1 -1
  78. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
  79. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
  80. package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
  81. package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
  82. package/corpus/templates/design/actions/update-file.ts +9 -2
  83. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
  84. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
  85. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
  86. package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
  87. package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
  88. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
  89. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
  90. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
  91. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
  92. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
  93. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
  94. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
  95. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
  96. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
  97. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
  98. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
  99. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
  100. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
  101. package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
  102. package/corpus/templates/design/app/global.css +29 -3
  103. package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
  104. package/corpus/templates/design/app/i18n-data.ts +239 -1
  105. package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
  106. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
  107. package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
  108. package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
  109. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
  110. package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
  111. package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
  112. package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
  113. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
  114. package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
  115. package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
  116. package/corpus/templates/design/shared/code-layer.ts +209 -9
  117. package/corpus/templates/design/shared/html-content.ts +21 -0
  118. package/corpus/templates/design/shared/pen-path.ts +207 -0
  119. package/corpus/templates/mail/app/lib/utils.ts +2 -2
  120. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
  121. package/corpus/templates/slides/AGENTS.md +3 -2
  122. package/corpus/templates/slides/actions/import-file.ts +26 -17
  123. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
  124. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
  125. package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
  126. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
  127. package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
  128. package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
  129. package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
  130. package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
  131. package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
  132. package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
  133. package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
  134. package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
  135. package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
  136. package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
  137. package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
  138. package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
  139. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  140. package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
  141. package/dist/application-state/handlers.js +1 -1
  142. package/dist/application-state/handlers.js.map +1 -1
  143. package/dist/brand-kit/index.d.ts +1 -1
  144. package/dist/brand-kit/index.js +1 -1
  145. package/dist/brand-kit/index.js.map +1 -1
  146. package/dist/cli/skills.d.ts.map +1 -1
  147. package/dist/cli/skills.js +18 -0
  148. package/dist/cli/skills.js.map +1 -1
  149. package/dist/client/AgentPanel.js +1 -1
  150. package/dist/client/AgentPanel.js.map +1 -1
  151. package/dist/client/MultiTabAssistantChat.js +1 -1
  152. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  153. package/dist/client/extensions/portable-extension.d.ts +1 -1
  154. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  155. package/dist/client/extensions/portable-extension.js.map +1 -1
  156. package/dist/code-agents/transcript-normalizer.js +1 -1
  157. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  158. package/dist/collab/awareness.d.ts +2 -2
  159. package/dist/collab/awareness.d.ts.map +1 -1
  160. package/dist/collab/routes.d.ts +1 -1
  161. package/dist/extensions/path.d.ts +1 -1
  162. package/dist/extensions/path.d.ts.map +1 -1
  163. package/dist/extensions/path.js.map +1 -1
  164. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  165. package/dist/integrations/a2a-continuation-processor.js +1 -1
  166. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  167. package/dist/mcp/build-server.d.ts +1 -1
  168. package/dist/mcp/build-server.d.ts.map +1 -1
  169. package/dist/mcp/build-server.js +1 -1
  170. package/dist/mcp/build-server.js.map +1 -1
  171. package/dist/mcp/embed-app.d.ts.map +1 -1
  172. package/dist/mcp/embed-app.js +35 -1
  173. package/dist/mcp/embed-app.js.map +1 -1
  174. package/dist/notifications/routes.d.ts +3 -3
  175. package/dist/observability/routes.d.ts +8 -8
  176. package/dist/org/handlers.js +5 -5
  177. package/dist/org/handlers.js.map +1 -1
  178. package/dist/org/plugin.js +2 -2
  179. package/dist/org/plugin.js.map +1 -1
  180. package/dist/resources/handlers.d.ts +1 -1
  181. package/dist/scripts/call-agent.js +1 -1
  182. package/dist/scripts/call-agent.js.map +1 -1
  183. package/dist/scripts/db/safety.js +1 -1
  184. package/dist/scripts/db/safety.js.map +1 -1
  185. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  186. package/dist/server/auth.d.ts.map +1 -1
  187. package/dist/server/auth.js +3 -0
  188. package/dist/server/auth.js.map +1 -1
  189. package/dist/server/builder-design-systems.d.ts +28 -0
  190. package/dist/server/builder-design-systems.d.ts.map +1 -0
  191. package/dist/server/builder-design-systems.js +217 -0
  192. package/dist/server/builder-design-systems.js.map +1 -0
  193. package/dist/server/core-routes-plugin.d.ts +1 -0
  194. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  195. package/dist/server/core-routes-plugin.js +20 -3
  196. package/dist/server/core-routes-plugin.js.map +1 -1
  197. package/dist/server/index.d.ts +1 -0
  198. package/dist/server/index.d.ts.map +1 -1
  199. package/dist/server/index.js +1 -0
  200. package/dist/server/index.js.map +1 -1
  201. package/dist/styles/agent-native.css +14 -7
  202. package/dist/usage/store.js +2 -2
  203. package/dist/usage/store.js.map +1 -1
  204. package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  205. package/docs/content/locales/ar-SA/template-design.mdx +14 -1
  206. package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  207. package/docs/content/locales/de-DE/template-design.mdx +15 -1
  208. package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  209. package/docs/content/locales/es-ES/template-design.mdx +15 -1
  210. package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  211. package/docs/content/locales/fr-FR/template-design.mdx +15 -1
  212. package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  213. package/docs/content/locales/hi-IN/template-design.mdx +15 -1
  214. package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  215. package/docs/content/locales/ja-JP/template-design.mdx +16 -1
  216. package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  217. package/docs/content/locales/ko-KR/template-design.mdx +14 -1
  218. package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  219. package/docs/content/locales/pt-BR/template-design.mdx +14 -1
  220. package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  221. package/docs/content/locales/zh-CN/template-design.mdx +14 -1
  222. package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  223. package/docs/content/locales/zh-TW/template-design.mdx +14 -1
  224. package/docs/content/skills-guide.mdx +5 -0
  225. package/docs/content/template-design.mdx +14 -1
  226. package/package.json +1 -1
  227. package/corpus/templates/design/actions/import-figma-file.ts +0 -166
  228. package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
@@ -16,9 +16,14 @@ import {
16
16
  type ShaderPresetDef,
17
17
  type ShaderPresetName,
18
18
  } from "@shared/shader-presets";
19
+ import {
20
+ buildFallbackGradient,
21
+ isWebGLAvailable,
22
+ prefersReducedMotion,
23
+ } from "@shared/shader-safety";
24
+ import { IconArrowLeft, IconX } from "@tabler/icons-react";
19
25
  import { useMemo, useState } from "react";
20
26
 
21
- import { Button } from "@/components/ui/button";
22
27
  import { Label } from "@/components/ui/label";
23
28
  import {
24
29
  Select,
@@ -35,7 +40,7 @@ import {
35
40
  } from "@/components/ui/tooltip";
36
41
  import { cn } from "@/lib/utils";
37
42
 
38
- import { ScrubInput } from "./ScrubInput";
43
+ import { type ScrubInputChangeMeta, ScrubInput } from "./ScrubInput";
39
44
 
40
45
  // ---------------------------------------------------------------------------
41
46
  // Dynamic shader component map
@@ -55,15 +60,6 @@ const SHADER_COMPONENTS: Record<ShaderPresetName, AnyShaderComponent> = {
55
60
  PaperTexture: PaperTexture as AnyShaderComponent,
56
61
  };
57
62
 
58
- // ---------------------------------------------------------------------------
59
- // Reduced-motion helper (inline, SSR-safe)
60
- // ---------------------------------------------------------------------------
61
-
62
- function getPreferreducedMotion(): boolean {
63
- if (typeof window === "undefined") return false;
64
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
65
- }
66
-
67
63
  // ---------------------------------------------------------------------------
68
64
  // ShaderPreview sub-component
69
65
  // ---------------------------------------------------------------------------
@@ -76,6 +72,7 @@ interface ShaderPreviewProps {
76
72
  function ShaderPreview({ descriptor, animated }: ShaderPreviewProps) {
77
73
  const preset = SHADER_PRESET_MAP[descriptor.preset];
78
74
  const ShaderComponent = SHADER_COMPONENTS[descriptor.preset];
75
+ const webglOk = isWebGLAvailable();
79
76
 
80
77
  // Build props — memoized to avoid identity churn on the WebGL layer
81
78
  const shaderProps = useMemo(() => {
@@ -92,20 +89,38 @@ function ShaderPreview({ descriptor, animated }: ShaderPreviewProps) {
92
89
  }, [descriptor, animated]);
93
90
 
94
91
  // Fallback gradient from the preset's default colors
95
- const fallbackColors =
96
- (preset?.defaultColors ?? preset?.defaultColorBack)
97
- ? [preset.defaultColorBack ?? "#555", preset.defaultColors?.[0] ?? "#888"]
98
- : ["#555555", "#888888"];
99
-
100
92
  const fallbackStyle = {
101
- background: `linear-gradient(135deg, ${fallbackColors.join(", ")})`,
93
+ background: buildFallbackGradient(
94
+ preset?.defaultColors ?? [],
95
+ preset?.defaultColorBack,
96
+ ),
102
97
  };
103
98
 
99
+ const fallbackEl = (
100
+ <Tooltip>
101
+ <TooltipTrigger asChild>
102
+ <div
103
+ className="w-full rounded-lg"
104
+ style={{ aspectRatio: "16 / 9", ...fallbackStyle }}
105
+ />
106
+ </TooltipTrigger>
107
+ <TooltipContent>
108
+ {
109
+ "WebGL unavailable – showing fallback" /* i18n-ignore shader tooltip */
110
+ }
111
+ </TooltipContent>
112
+ </Tooltip>
113
+ );
114
+
115
+ if (!webglOk) {
116
+ return fallbackEl;
117
+ }
118
+
104
119
  try {
105
120
  return (
106
121
  <div
107
- className="relative overflow-hidden rounded"
108
- style={{ width: 120, height: 80 }}
122
+ className="relative w-full overflow-hidden rounded-lg"
123
+ style={{ aspectRatio: "16 / 9" }}
109
124
  >
110
125
  <ShaderComponent
111
126
  {...shaderProps}
@@ -119,24 +134,62 @@ function ShaderPreview({ descriptor, animated }: ShaderPreviewProps) {
119
134
  </div>
120
135
  );
121
136
  } catch {
122
- return (
123
- <Tooltip>
124
- <TooltipTrigger asChild>
125
- <div
126
- className="rounded"
127
- style={{ width: 120, height: 80, ...fallbackStyle }}
128
- />
129
- </TooltipTrigger>
130
- <TooltipContent>
131
- {
132
- "WebGL unavailable - showing fallback" /* i18n-ignore shader tooltip */
133
- }
134
- </TooltipContent>
135
- </Tooltip>
136
- );
137
+ return fallbackEl;
137
138
  }
138
139
  }
139
140
 
141
+ // ---------------------------------------------------------------------------
142
+ // Shared row wrapper: label-left, control-right, h-6 density
143
+ // ---------------------------------------------------------------------------
144
+
145
+ function ParamLabel({ children }: { children: React.ReactNode }) {
146
+ return (
147
+ <span className="w-[5.5rem] shrink-0 truncate text-[11px] text-muted-foreground">
148
+ {children}
149
+ </span>
150
+ );
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Color swatch button — Figma style: rounded rect swatch + hex label inline
155
+ // ---------------------------------------------------------------------------
156
+
157
+ interface ColorSwatchProps {
158
+ color: string;
159
+ label: string;
160
+ onChange: (value: string) => void;
161
+ }
162
+
163
+ function ColorSwatch({ color, label, onChange }: ColorSwatchProps) {
164
+ return (
165
+ <Tooltip>
166
+ <TooltipTrigger asChild>
167
+ {/* The label wraps the native color input so clicking the swatch opens the picker */}
168
+ <label
169
+ className="flex h-6 min-w-0 cursor-pointer items-center gap-1.5 rounded px-1 text-[11px] text-muted-foreground transition-colors hover:bg-[var(--design-editor-control-bg)] hover:text-foreground focus-within:bg-[var(--design-editor-control-bg)]"
170
+ title={label}
171
+ >
172
+ <span
173
+ className="inline-block size-4 shrink-0 rounded-[3px] border border-black/20 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.15)]"
174
+ style={{ background: color }}
175
+ />
176
+ <span className="min-w-0 truncate font-mono uppercase">
177
+ {color.startsWith("#") ? color.slice(1).toUpperCase() : color}
178
+ </span>
179
+ <input
180
+ type="color"
181
+ value={color}
182
+ onChange={(e) => onChange(e.target.value)}
183
+ className="sr-only"
184
+ aria-label={label}
185
+ />
186
+ </label>
187
+ </TooltipTrigger>
188
+ <TooltipContent>{label}</TooltipContent>
189
+ </Tooltip>
190
+ );
191
+ }
192
+
140
193
  // ---------------------------------------------------------------------------
141
194
  // Param row renderers
142
195
  // ---------------------------------------------------------------------------
@@ -159,7 +212,7 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
159
212
  min={min}
160
213
  max={max}
161
214
  step={step ?? 1}
162
- onChange={(v) => onChange(key, v)}
215
+ onChange={(v: number, _meta: ScrubInputChangeMeta) => onChange(key, v)}
163
216
  className="w-full"
164
217
  />
165
218
  );
@@ -168,17 +221,15 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
168
221
  if (kind === "enum") {
169
222
  const strVal = typeof value === "string" ? value : String(paramDef.default);
170
223
  return (
171
- <div className="flex items-center gap-2">
172
- <span className="w-20 shrink-0 truncate text-xs text-muted-foreground">
173
- {label}
174
- </span>
224
+ <div className="flex h-6 items-center gap-1.5">
225
+ <ParamLabel>{label}</ParamLabel>
175
226
  <Select value={strVal} onValueChange={(v) => onChange(key, v)}>
176
- <SelectTrigger className="h-7 flex-1 text-xs">
227
+ <SelectTrigger className="h-6 flex-1 text-[11px]">
177
228
  <SelectValue />
178
229
  </SelectTrigger>
179
230
  <SelectContent>
180
231
  {(options ?? []).map((opt: string) => (
181
- <SelectItem key={opt} value={opt} className="text-xs">
232
+ <SelectItem key={opt} value={opt} className="text-[11px]">
182
233
  {opt}
183
234
  </SelectItem>
184
235
  ))}
@@ -193,15 +244,15 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
193
244
  typeof value === "boolean" ? value : Boolean(paramDef.default);
194
245
  const switchId = `shader-param-${key}`;
195
246
  return (
196
- <div className="flex items-center justify-between gap-2">
197
- <Label htmlFor={switchId} className="text-xs text-muted-foreground">
247
+ <div className="flex h-6 items-center justify-between gap-1.5">
248
+ <Label htmlFor={switchId} className="text-[11px] text-muted-foreground">
198
249
  {label}
199
250
  </Label>
200
251
  <Switch
201
252
  id={switchId}
202
253
  checked={boolVal}
203
254
  onCheckedChange={(checked) => onChange(key, checked)}
204
- className="scale-90"
255
+ className="origin-right scale-[0.8]"
205
256
  />
206
257
  </div>
207
258
  );
@@ -210,23 +261,13 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
210
261
  if (kind === "color") {
211
262
  const strVal = typeof value === "string" ? value : String(paramDef.default);
212
263
  return (
213
- <div className="flex items-center gap-2">
214
- <span className="w-20 shrink-0 truncate text-xs text-muted-foreground">
215
- {label}
216
- </span>
217
- <Tooltip>
218
- <TooltipTrigger asChild>
219
- <input
220
- type="color"
221
- value={strVal}
222
- onChange={(e) => onChange(key, e.target.value)}
223
- className="h-7 w-8 cursor-pointer rounded border border-border bg-transparent p-0.5"
224
- aria-label={label}
225
- />
226
- </TooltipTrigger>
227
- <TooltipContent>{label}</TooltipContent>
228
- </Tooltip>
229
- <span className="text-[10px] text-muted-foreground">{strVal}</span>
264
+ <div className="flex h-6 items-center gap-1.5">
265
+ <ParamLabel>{label}</ParamLabel>
266
+ <ColorSwatch
267
+ color={strVal}
268
+ label={label}
269
+ onChange={(v) => onChange(key, v)}
270
+ />
230
271
  </div>
231
272
  );
232
273
  }
@@ -238,28 +279,24 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
238
279
  const limit = maxCount ?? 10;
239
280
  return (
240
281
  <div className="flex flex-col gap-1">
241
- <span className="text-xs text-muted-foreground">{label}</span>
242
- <div className="flex flex-wrap gap-1">
282
+ {/* Section label sits at the top of the stop list */}
283
+ <span className="text-[11px] text-muted-foreground">{label}</span>
284
+ {/* Color stop rows — each a swatch + hex + remove button */}
285
+ <div className="flex flex-col gap-0.5">
243
286
  {arrVal.map((color, i) => {
244
287
  const colorLabel = `Color ${i + 1}`;
245
288
  return (
246
- <div key={i} className="flex items-center gap-0.5">
247
- <Tooltip>
248
- <TooltipTrigger asChild>
249
- <input
250
- type="color"
251
- value={color}
252
- onChange={(e) => {
253
- const next = [...arrVal];
254
- next[i] = e.target.value;
255
- onChange(key, next);
256
- }}
257
- className="h-6 w-6 cursor-pointer rounded border border-border bg-transparent p-0"
258
- aria-label={colorLabel}
259
- />
260
- </TooltipTrigger>
261
- <TooltipContent>{colorLabel}</TooltipContent>
262
- </Tooltip>
289
+ <div key={i} className="flex h-6 items-center gap-1">
290
+ <ColorSwatch
291
+ color={color}
292
+ label={colorLabel}
293
+ onChange={(v) => {
294
+ const next = [...arrVal];
295
+ next[i] = v;
296
+ onChange(key, next);
297
+ }}
298
+ />
299
+ <span className="flex-1" />
263
300
  {arrVal.length > 1 && (
264
301
  <Tooltip>
265
302
  <TooltipTrigger asChild>
@@ -269,12 +306,12 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
269
306
  const next = arrVal.filter((_, idx) => idx !== i);
270
307
  onChange(key, next);
271
308
  }}
272
- className="text-[10px] leading-none text-muted-foreground hover:text-destructive"
309
+ className="flex size-4 items-center justify-center rounded text-muted-foreground hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
273
310
  aria-label={
274
311
  "Remove color" /* i18n-ignore shader tooltip */
275
312
  }
276
313
  >
277
- x
314
+ <IconX className="size-2.5" />
278
315
  </button>
279
316
  </TooltipTrigger>
280
317
  <TooltipContent>
@@ -286,14 +323,18 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
286
323
  );
287
324
  })}
288
325
  {arrVal.length < limit && (
289
- <Button
290
- variant="ghost"
291
- size="sm"
292
- className="h-6 px-2 text-[10px]"
326
+ <button
327
+ type="button"
328
+ className="flex h-6 items-center gap-1.5 rounded px-1 text-[11px] text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
293
329
  onClick={() => onChange(key, [...arrVal, "#ffffff"])}
294
330
  >
295
- {"+ Add" /* i18n-ignore shader compact add button */}
296
- </Button>
331
+ <span className="flex size-4 shrink-0 items-center justify-center rounded-[3px] border border-dashed border-muted-foreground/50 text-[10px]">
332
+ +
333
+ </span>
334
+ <span>
335
+ {"Add color" /* i18n-ignore shader compact add button */}
336
+ </span>
337
+ </button>
297
338
  )}
298
339
  </div>
299
340
  </div>
@@ -310,18 +351,21 @@ function ParamRow({ paramDef, value, onChange }: ParamRowProps) {
310
351
  export interface ShaderControlsProps {
311
352
  descriptor: ShaderDescriptor;
312
353
  onChange: (descriptor: ShaderDescriptor) => void;
354
+ /** Optional callback to navigate back to the preset browser. */
355
+ onBack?: () => void;
313
356
  className?: string;
314
357
  }
315
358
 
316
359
  export function ShaderControls({
317
360
  descriptor,
318
361
  onChange,
362
+ onBack,
319
363
  className,
320
364
  }: ShaderControlsProps) {
321
- const prefersReducedMotion = getPreferreducedMotion();
365
+ const reducedMotion = prefersReducedMotion();
322
366
 
323
367
  const [animated, setAnimated] = useState(
324
- () => (descriptor.speed ?? 0) !== 0 && !prefersReducedMotion,
368
+ () => (descriptor.speed ?? 0) !== 0 && !reducedMotion,
325
369
  );
326
370
 
327
371
  const preset = SHADER_PRESET_MAP[descriptor.preset];
@@ -397,31 +441,43 @@ export function ShaderControls({
397
441
  }
398
442
  }
399
443
 
400
- function handleSpeedChange(v: number) {
444
+ function handleSpeedChange(v: number, _meta: ScrubInputChangeMeta) {
401
445
  onChange({ ...descriptor, speed: v });
402
446
  }
403
447
 
404
448
  const animateSwitchId = "shader-animate";
405
449
 
406
450
  return (
407
- <div
408
- className={cn(
409
- "flex flex-col gap-3 rounded-md bg-[#1a1a1a] p-3 text-xs",
410
- className,
451
+ <div className={cn("flex flex-col gap-0", className)}>
452
+ {/* ── Live preview ─ full-width, rounded, sits at top ─────────────── */}
453
+ <div className="px-3 pb-2 pt-1">
454
+ <ShaderPreview descriptor={descriptor} animated={animated} />
455
+ </div>
456
+
457
+ {/* ── "Back to presets" affordance ─────────────────────────────────── */}
458
+ {onBack && (
459
+ <div className="px-3 pb-1.5">
460
+ <button
461
+ type="button"
462
+ onClick={onBack}
463
+ className="flex h-6 items-center gap-1 rounded px-1 text-[11px] text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
464
+ >
465
+ <IconArrowLeft className="size-3" />
466
+ <span>{"Back to presets" /* i18n-ignore shader nav */}</span>
467
+ </button>
468
+ </div>
411
469
  )}
412
- >
413
- {/* Preset picker */}
414
- <div className="flex flex-col gap-1.5">
415
- <span className="text-[10px] uppercase tracking-wider text-muted-foreground">
416
- Preset
417
- </span>
470
+
471
+ {/* ── Preset picker ────────────────────────────────────────────────── */}
472
+ <div className="flex h-6 items-center gap-1.5 px-3">
473
+ <ParamLabel>{"Preset" /* i18n-ignore */}</ParamLabel>
418
474
  <Select value={descriptor.preset} onValueChange={handlePresetChange}>
419
- <SelectTrigger className="h-7 text-xs">
475
+ <SelectTrigger className="h-6 flex-1 text-[11px]">
420
476
  <SelectValue />
421
477
  </SelectTrigger>
422
478
  <SelectContent>
423
479
  {(SHADER_PRESETS as readonly ShaderPresetDef[]).map((p) => (
424
- <SelectItem key={p.name} value={p.name} className="text-xs">
480
+ <SelectItem key={p.name} value={p.name} className="text-[11px]">
425
481
  {p.label}
426
482
  </SelectItem>
427
483
  ))}
@@ -429,85 +485,86 @@ export function ShaderControls({
429
485
  </Select>
430
486
  </div>
431
487
 
432
- {/* Live preview */}
433
- <div className="flex justify-center">
434
- <ShaderPreview descriptor={descriptor} animated={animated} />
435
- </div>
436
-
437
- {/* Animate toggle */}
438
- <div className="flex items-center justify-between gap-2">
488
+ {/* ── Animate toggle ───────────────────────────────────────────────── */}
489
+ <div className="flex h-6 items-center justify-between gap-1.5 px-3 pt-1">
439
490
  <Label
440
491
  htmlFor={animateSwitchId}
441
492
  className={cn(
442
- "text-xs text-muted-foreground",
443
- prefersReducedMotion && "opacity-50",
493
+ "text-[11px] text-muted-foreground",
494
+ reducedMotion && "opacity-50",
444
495
  )}
445
496
  >
446
- Animate
447
- {prefersReducedMotion && (
448
- <span className="ml-1 text-[10px]">(reduced motion)</span>
497
+ {"Animate" /* i18n-ignore shader label */}
498
+ {reducedMotion && (
499
+ <span className="ml-1 text-[10px]">
500
+ {"(reduced motion)" /* i18n-ignore */}
501
+ </span>
449
502
  )}
450
503
  </Label>
451
504
  <Switch
452
505
  id={animateSwitchId}
453
506
  checked={animated}
454
507
  onCheckedChange={handleAnimatedChange}
455
- disabled={prefersReducedMotion}
456
- className="scale-90"
508
+ disabled={reducedMotion}
509
+ className="origin-right scale-[0.8]"
457
510
  />
458
511
  </div>
459
512
 
460
- {/* Speed scrub — only when animating */}
513
+ {/* ── Speed scrub — only when animating ───────────────────────────── */}
461
514
  {animated && (
462
- <ScrubInput
463
- label="Speed"
464
- value={descriptor.speed ?? 1}
465
- min={-5}
466
- max={5}
467
- step={0.1}
468
- onChange={handleSpeedChange}
469
- className="w-full"
470
- />
515
+ <div className="px-3 pt-1">
516
+ <ScrubInput
517
+ label="Speed"
518
+ value={descriptor.speed ?? 1}
519
+ min={-5}
520
+ max={5}
521
+ step={0.1}
522
+ onChange={handleSpeedChange}
523
+ className="w-full"
524
+ />
525
+ </div>
471
526
  )}
472
527
 
473
- {/* Shader-specific params */}
528
+ {/* ── Shader-specific params ───────────────────────────────────────── */}
474
529
  {preset && preset.params.length > 0 && (
475
- <div className="flex flex-col gap-2">
476
- <span className="text-[10px] uppercase tracking-wider text-muted-foreground">
477
- Parameters
478
- </span>
479
- {preset.params.map((paramDef: ParamDef) => {
480
- if (paramDef.kind === "colors") {
481
- // Resolve the current color array
482
- const val: string[] =
483
- descriptor.colors ?? preset.defaultColors ?? [];
530
+ <>
531
+ <div className="mx-3 mb-1 mt-2 border-t border-border/40" />
532
+ <div className="flex flex-col gap-1 px-3 pb-2">
533
+ {preset.params.map((paramDef: ParamDef) => {
534
+ if (paramDef.kind === "colors") {
535
+ // Resolve the current color array
536
+ const val: string[] =
537
+ descriptor.colors ?? preset.defaultColors ?? [];
538
+ return (
539
+ <ParamRow
540
+ key={paramDef.key}
541
+ paramDef={paramDef}
542
+ value={val}
543
+ onChange={(k, v) =>
544
+ handleColorsParamChange(k, v as string[])
545
+ }
546
+ />
547
+ );
548
+ }
549
+
550
+ const val = descriptor.params[paramDef.key] ?? paramDef.default;
551
+
484
552
  return (
485
553
  <ParamRow
486
554
  key={paramDef.key}
487
555
  paramDef={paramDef}
488
- value={val}
489
- onChange={(k, v) => handleColorsParamChange(k, v as string[])}
556
+ value={val as number | boolean | string}
557
+ onChange={handleParamChange}
490
558
  />
491
559
  );
492
- }
493
-
494
- const val = descriptor.params[paramDef.key] ?? paramDef.default;
495
-
496
- return (
497
- <ParamRow
498
- key={paramDef.key}
499
- paramDef={paramDef}
500
- value={val as number | boolean | string}
501
- onChange={handleParamChange}
502
- />
503
- );
504
- })}
505
- </div>
560
+ })}
561
+ </div>
562
+ </>
506
563
  )}
507
564
 
508
- {/* Expensive param performance warning */}
565
+ {/* ── Expensive param performance warning ─────────────────────────── */}
509
566
  {hasExpensiveParam && (
510
- <p className="rounded bg-yellow-950/50 px-2 py-1 text-[10px] text-yellow-400">
567
+ <p className="mx-3 mb-2 rounded bg-yellow-950/50 px-2 py-1 text-[10px] text-yellow-400">
511
568
  {
512
569
  "grainMixer / grainOverlay may impact performance on mobile" /* i18n-ignore shader performance warning */
513
570
  }