dsh-claude-design 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE.md CHANGED
@@ -24,6 +24,7 @@ baoyu-design brings claude.ai's Artifact types and Claude Design to local agents
24
24
  - **Reference docs.** `skills/*/artifact-type/reference/`, and the copies under `scripts/editor/artifact-type/`: the craft, format and layout guides, font lists and other references of the Artifact types.
25
25
  - **The `dataviz` skill.** `skills/dataviz/` is Anthropic's dataviz skill. Its palette validators include code from Color.js (MIT, Lea Verou and Chris Lilley), credited in the files.
26
26
  - **The editors.** `skills/claude-*/scripts/editor/` holds editors reconstructed from the claude.ai Artifact-type runtimes: their interface, wording and behaviour are Anthropic's. The built files mix that reconstruction with the maintainer's local additions (section 1) and with open-source packages (section 3).
27
+ - **Compiled engines.** Where an editor ships a WebAssembly engine as upstream built it (a `*.wasm` file in its `scripts/editor/artifact-type/`), the engine is Anthropic's too. The open-source code compiled into it is listed in the notices shipped beside it (`THIRD_PARTY_NOTICES.md`, `RUST_STANDARD_LIBRARY_NOTICES.html`).
27
28
  - **Icon fonts.** The Anthropicons icon fonts: the `anthropicons-*.woff2` files in the editors' `assets/` and `artifact-type/` folders, and the Anthropicons font faces embedded as data URIs in the editors' CSS.
28
29
  - **The archived `baoyu-design` skill.** In `archive/baoyu-design/`: `system-prompt.md`, `references/upstream-system-prompt.md` and `references/upstream-sync/` (the Claude Design system prompt, a snapshot of it and its sync records), `built-in-skills/` (Claude Design's built-in skills) and `starter-components/` (Claude Design's starter components; the local patches are recorded in the `*-patch.md` files there).
29
30
 
@@ -33,7 +34,7 @@ baoyu-design brings claude.ai's Artifact types and Claude Design to local agents
33
34
 
34
35
  ## 3. Third-party open-source code
35
36
 
36
- The editors bundle open-source packages, such as React, Base UI, d3 and pptxgenjs. Each keeps its own license, which this project's license does not replace. The licenses are in each editor's `scripts/editor/third-party-licenses.txt`, and for the slides compiler in `scripts/editor/node/third-party-licenses.txt`.
37
+ The editors bundle open-source packages, such as React, Base UI, d3 and pptxgenjs. Each keeps its own license, which this project's license does not replace. The licenses are in each editor's `scripts/editor/third-party-licenses.txt`, and for the slides compiler in `scripts/editor/node/third-party-licenses.txt`. The open-source crates and the Rust standard library compiled into a WebAssembly engine are listed in the notices beside it.
37
38
 
38
39
  The archived `baoyu-design` skill vendors React, ReactDOM, Babel and fflate (MIT) in `archive/baoyu-design/agents/vendor/`, and pptxgenjs (MIT) in `archive/baoyu-design/agents/gen-pptx/vendor/pptxgenjs/`, with its `LICENSE`.
39
40
 
@@ -51,6 +52,7 @@ If you hold rights in anything here and want it credited differently, changed or
51
52
  - `artifact-type/reference/` 里的参考文档;
52
53
  - `skills/dataviz/`(Anthropic 的 dataviz 技能);
53
54
  - `scripts/editor/` 里从 claude.ai 运行时还原的编辑器(界面、文案和行为);
55
+ - 编辑器原样带上的 WebAssembly 引擎(`scripts/editor/artifact-type/` 里的 `*.wasm`),编进去的开源代码列在它旁边的 `THIRD_PARTY_NOTICES.md`、`RUST_STANDARD_LIBRARY_NOTICES.html` 里;
54
56
  - Anthropicons 图标字体(`anthropicons-*.woff2` 文件,以及编辑器 CSS 里内嵌的字体);
55
57
  - `archive/baoyu-design/` 里的 Claude Design 系统提示词、内置技能和起步组件。
56
58
 
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // dsh-claude-design 2.13.0: built from baoyu-design.
1
+ // dsh-claude-design 2.14.0: built from baoyu-design.
2
2
  var define_PLUGIN_default = { name: "claude-design", provider: "dsh-claude-design", skills: [{ name: "claude-design", description: "Make and revise designs in the Design (canvas) artifact format (project/canvas.json plus one .dc.html Design Component file per artboard) as local files, then open them in a local canvas editor where the user can view the artboards side by side, retype copy and adjust tweaks. Use when the user asks for an editable canvas of artboards in this format, names claude-design or the Design (canvas) type, or wants to revise a folder that holds project/canvas.json. Not for a one-off HTML prototype or a design exploration outside this format.", comments: true }] };
3
3
 
4
4
  import { readFile } from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-claude-design",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "DeepSeek Harness plugin: the claude-design skill from baoyu-design. The agent writes canvases of artboards as files and opens them in a local canvas editor.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -24,6 +24,12 @@
24
24
  "icon": "./icon.svg",
25
25
  "baoyuDesign": {
26
26
  "releaseNotes": [
27
+ {
28
+ "version": "2.14.0",
29
+ "date": "2026-11-11",
30
+ "en": "### Features\n- **`claude-dashboard` is now `claude-data`.** claude.ai renamed the type to Data, and the skill, its DeepSeek Harness plugin (`dsh-claude-data`) and its ChatGPT and Codex plugin (**Baoyu Data**) follow. Call it as `/claude-data` (`$claude-data` in Codex). A dashboard made with `claude-dashboard` opens in `claude-data` as it is, with the same folder; its address is now `/data/<id>/` instead of `/dashboard/<id>/`. `dsh-claude-dashboard` and **Baoyu Dashboard** get no more updates: install `dsh-claude-data` (the **Artifacts** page offers it as a **Not installed** tile) or **Baoyu Data**, then remove the old one. On the Artifacts page, dashboards opened before the rename are listed with the new kind and still open, and comments sent from a dashboard reach the agent through `dsh-claude-data` (the copy of `dsh-claude-data` put on npm on its own before this release lacked both, because the Host plugin it came with did not know the new name).\n- **`claude-data` follows claude.ai's 2026-11-10 update.** A dashboard's toolbar now has a **Design** button. It shows the look in use and a new **Style** section: chart colors, page color, font, corners and text size. A change shows on the page at once and is kept with **Save**; **Reset all** goes back to the defaults. A dataset can be a text file (`format: \"text\"`), which the page turns into rows with a loader. `dash/meta` takes the page's language (`lang`). `check` also reports what the open page found when it checked itself (`pageCheck`: passed, failed with what went wrong, or still loading) beside the page's `runs`.\n- **`claude-design` follows claude.ai's 2026-11-10 update.** Pen, Line and Arrow have a **Drawing options** bar: four stroke weights, and solid, dashed or dotted lines. An artboard's export menu has **All artboards (.png)**, a zip with one PNG per artboard on the page. When the canvas's saved layout is damaged, the canvas turns read-only with a banner and an **Ask the agent to fix** button, and comes back on its own once the file is fixed. A new reference, `shaders.md`, is how the agent makes a shader artboard (WebGL, or anything that moves like light, water, smoke or liquid).\n\n### Fixes\n- `claude-design`: artboards ran React's development build (1.1 MB), which printed warnings such as `Invalid DOM property vector-effect` for drawn lines. They now run the production build (354 KB).\n- `claude-design`: when two tabs changed the same artboard and you chose **Keep both**, the copy was put 80 px right of the original, where it could cover another artboard. It now goes in the first free spot right of that row.\n- In every editor, Escape pressed while the keyboard is in the comments layer (on the comments button, say) now ends comment mode, as it does on the page.\n\n### Documentation\n- `claude-data`'s `SKILL.md` and reference follow the same update: what runs where (each reader's browser loads the datasets, and Sources shows where every number came from), what a page's code may write and when, and how `check` and `runs` report a page that went wrong.",
31
+ "zh": "### 新功能\n- **`claude-dashboard` 改名为 `claude-data`。** claude.ai 把这个类型改名为 Data,技能、它的 DeepSeek Harness 插件(`dsh-claude-data`)和 ChatGPT、Codex 插件(**Baoyu Data**)都跟着改名。调用名是 `/claude-data`(Codex 里是 `$claude-data`)。用 `claude-dashboard` 做的仪表盘,原样用 `claude-data` 打开,文件夹不变;地址从 `/dashboard/<id>/` 变成 `/data/<id>/`。`dsh-claude-dashboard` 和 **Baoyu Dashboard** 不再更新:装上 `dsh-claude-data`(**Artifacts** 页面上它是一个 **Not installed** 的卡片,点开就能装)或 **Baoyu Data**,再把旧的删掉。改名前打开过的仪表盘,在 Artifacts 页面上按新的类型列出,照样能打开;仪表盘上发出的评论经 `dsh-claude-data` 交给 Agent(这一版之前单独发到 npm 的 `dsh-claude-data` 这两样都不行,因为和它配套的 Host 插件还不认识新名字)。\n- **`claude-data` 跟上了 claude.ai 2026-11-10 的更新。** 仪表盘的工具栏多了一个 **Design** 按钮,里面是当前的外观和新的 **Style** 一段:图表配色、页面底色、字体、圆角和字号。改了页面马上显示,点 **Save** 保存;**Reset all** 回到默认。数据集可以是文本文件(`format: \"text\"`),由页面用 loader 拆成行。`dash/meta` 可以写页面的语言(`lang`)。`check` 还会报告打开着的页面自查的结果(`pageCheck`:通过、没通过和出了什么问题、还在加载),和页面的 `runs` 放在一起。\n- **`claude-design` 跟上了 claude.ai 2026-11-10 的更新。** 画笔、直线和箭头有了 **Drawing options** 条:四档粗细,实线、虚线或点线。画板的导出菜单里多了 **All artboards (.png)**,把这一页每块画板各导出一张 PNG,打成一个 zip。画布保存的布局文件坏了时,画布变成只读,顶上有横幅和 **Ask the agent to fix** 按钮,文件修好后自己恢复。新增 reference `shaders.md`,Agent 照它做着色器画板(WebGL,或者像光、水、烟、液体那样流动的效果)。\n\n### 修复\n- `claude-design`:画板里跑的是 React 的开发版(1.1 MB),画的线会报 `Invalid DOM property vector-effect` 这类警告。现在是生产版(354 KB)。\n- `claude-design`:两个标签页改了同一块画板、选 **Keep both** 时,副本放在原画板右边 80 px,可能压住那里的另一块画板。现在放在这一行右边第一个空位。\n- 所有编辑器:键盘在评论层里时(比如停在评论按钮上)按 Escape,现在会退出评论模式,和在页面上按一样。\n\n### 文档\n- `claude-data` 的 `SKILL.md` 和 reference 也跟着这次更新:什么在哪里运行(每个读者的浏览器加载数据集,Sources 说明每个数字从哪里来),页面的代码什么时候可以写入、能写什么,以及 `check` 和 `runs` 怎样报告出错的页面。"
32
+ },
27
33
  {
28
34
  "version": "2.13.0",
29
35
  "date": "2026-11-10",
@@ -65,12 +71,6 @@
65
71
  "date": "2026-11-05",
66
72
  "en": "### Features\n- **DeepSeek Harness: a new version no longer pops up over the app.** The **Artifacts** row in the left sidebar gets an **Update** label instead (a dot on the icon, where a collapsed sidebar keeps its icons), and a panel at the top of the Artifacts page shows the versions, each plugin and what the release changes. **Update all** updates them together; **Update** on a plugin's line takes just that one. **Later** folds the panel to one line and removes the label until the next version.\n- While an update runs you can leave the Artifacts page: the label says **Updating**, **Failed** or **Restart**. **Try again** picks up where it stopped: the rest after **Update all**, that plugin after its own **Update**.\n- The **Update** button on a plugin's page now updates only that plugin, and a page that was open when its plugin was replaced comes back by itself.",
67
73
  "zh": "### 新功能\n- **DeepSeek Harness:新版本不再弹出来打断你。** 左侧栏的 **Artifacts**(「作品」)这一行会多一个 **更新** 标签(侧栏收起后还留着图标的话,是图标上的小圆点),「作品」页顶部的面板写着版本、每个插件和这次改了什么。**全部更新** 一起更新;每个插件那一行的 **更新** 只更新它自己。**稍后** 把面板收成一行,标签也消失,等下一个版本再提示。\n- 更新时可以离开「作品」页:标签会显示 **更新中**、**更新失败** 或 **待重启**。**重试** 从停下的地方接着来:**全部更新** 失败就接着更新剩下的,单独更新失败就再试这一个。\n- 插件详情页的 **更新** 按钮现在只更新这一个插件;更新时开着的页面被替换后,会自己回来。"
68
- },
69
- {
70
- "version": "2.10.1",
71
- "date": "2026-11-05",
72
- "en": "### Fixes\n- DeepSeek Harness 0.2.2 has its own plugin updates, so a plugin's page showed two update buttons and two tags, the app's and the plugin's. It now shows one: the app's for a plugin the card hasn't updated, the plugin's own for one it has.\n- The one-click update looks again at what there is to update before it starts, so a plugin the app's button just updated isn't tried again. When the reply to an update is lost, the card no longer says the plugin stayed on its version: it asks you to check on the Plugins page.\n- The [how-to](dsh-plugins/README.md#if-something-doesnt-work) says what to do when the app's own **Update to x** fails on the second of two plugins and switches it off, and that 2.9.2 and earlier can use that button on DeepSeek Harness 0.2.2 instead of updating by hand.",
73
- "zh": "### 修复\n- DeepSeek Harness 0.2.2 自带了插件更新,插件详情页因此出现两个更新按钮和两个标签,应用的和插件的各一套。现在只有一个:没用卡片更新过的插件显示应用的按钮,更新过的显示插件自己的。\n- 一键更新开始前会重新确认要更新什么,刚用应用自带按钮更新过的插件不会再更新一次。更新的回复丢失时,卡片不再说插件还是原来的版本,而是请你到插件页确认。\n- [使用说明](dsh-plugins/README.zh-CN.md#遇到问题)写明了应用自带的 **更新到 x** 更新第二个插件失败、插件被关掉时怎么办;也说明了在 DeepSeek Harness 0.2.2 上,2.9.2 和更早的版本可以直接用这个按钮更新,不用手动。"
74
74
  }
75
75
  ]
76
76
  },
@@ -96,7 +96,7 @@
96
96
  "dsh": ">=0.2.0-0"
97
97
  },
98
98
  "dependencies": {
99
- "dsh-claude-artifacts": "^2.13.0"
99
+ "dsh-claude-artifacts": "^2.14.0"
100
100
  },
101
101
  "dsh": {
102
102
  "bundle": {
@@ -210,6 +210,6 @@ node "<this skill's folder>/scripts/artifact.mjs" check "<root>" > /dev/null ||
210
210
 
211
211
  ## The references
212
212
 
213
- Under `artifact-type/reference/` in this skill's folder: `format.md` (before a first artboard), `craft.md`, `diagrams.md` (FIRST for diagrams), `print.md` (FIRST for print), `questions.md`, `view-state.md` (before
213
+ Under `artifact-type/reference/` in this skill's folder: `format.md` (before a first artboard), `craft.md`, `diagrams.md` (FIRST for diagrams), `print.md` (FIRST for print), `shaders.md` (FIRST for a shader, WebGL, or anything that moves like light, water, smoke or liquid; a plain gradient stays CSS unless they ask for a shader or a pointer reaction), `questions.md`, `view-state.md` (before
214
214
  acting on "this artboard" or a selection), `design-system-components.md`, `brand-colors.md`, `brand-typography.md`. An artifact (deck, animation, doc) made FROM this
215
215
  canvas: read `make-from-this.md` FIRST.
@@ -155,14 +155,15 @@ its real viewing distance.
155
155
 
156
156
  ### Accessible as drawn
157
157
 
158
- Draw controls with the real elements even in a static mockup: `<button>`,
159
- `<a href>`, and `<input>` paired with a `<label>`. A div or span with
160
- `role` or `onClick` looks the same, but Tab skips it and a screen reader
161
- has nothing to announce. Give an icon-only button an `aria-label`. Keep
162
- text at a contrast of at least 4.5:1 against what is behind it (3:1 from
163
- 24px). What fails most often is caption grey, and coloured fills under
164
- white text: darken both. Colors that must be told apart also differ in
165
- lightness, not hue alone; prefer blue/orange to red/green.
158
+ Draw controls with real elements even in a static mockup: `<button>`,
159
+ `<a href>`, and `<input>` with a `<label>`. A div or span with `role` or
160
+ `onClick` looks the same, but Tab skips it. Give an icon-only button an
161
+ `aria-label`. SVG: decor `aria-hidden="true"`, else
162
+ `role="img" aria-label`. Keep text at a contrast of at least 4.5:1
163
+ against what is behind it (3:1 from 24px). Caption grey and coloured
164
+ fills under white text fail most often: darken both. Colors that must be
165
+ told apart also differ in lightness, not hue alone; prefer blue/orange to
166
+ red/green.
166
167
 
167
168
  ### Landing pages and marketing artboards
168
169
 
@@ -0,0 +1,302 @@
1
+ # Shaders
2
+
3
+ One shader is one artboard. Its levers are ordinary tweaks. Words or buttons over it are ordinary markup after the `<canvas>`, absolutely placed, with `pointer-events: none`, and `auto` on buttons.
4
+
5
+ You don't run your work, so keep to this page's sample, kit and building blocks.
6
+
7
+ ## The artboard
8
+
9
+ The whole file. Change the title, size, background, aria-label, `data-props`, `FRAG` and `uniforms()`, and add any markup. Keep the rest as is in every artboard. `function shaderStage` is the kit: it compiles `FRAG`, feeds it time, the pointer and `uniforms()`, handles reduced motion, boards off screen, slow machines and lost WebGL, and falls back to the root's background. When a shader fails, `renderVals()` throws the kit's message, so the artboard shows it and the editor passes it to you.
10
+
11
+ ```html
12
+ <!doctype html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <title>Tide pool</title>
17
+ <script src="./support.js"></script>
18
+ </head>
19
+ <body>
20
+ <x-dc>
21
+ <helmet>
22
+ <style>
23
+ body{margin:0}
24
+ </style>
25
+ </helmet>
26
+ <div style="position: relative; width: 960px; height: 600px; overflow: hidden; background: #0B3A4A">
27
+ <canvas ref="{{view}}" role="img" aria-label="Light over a shallow pool" style="display: block; width: 100%; height: 100%; touch-action: none"></canvas>
28
+ </div>
29
+ </x-dc>
30
+ <script type="text/x-dc" data-dc-script data-props='{"speed":{"editor":"range","min":0,"max":3,"step":0.01,"default":1,"section":"Motion"},"water":{"editor":"color","default":"#0B3A4A","section":"Color"},"light":{"editor":"color","default":"#E8FFF4","section":"Color"},"ripple":{"editor":"range","min":0,"max":1,"step":0.01,"default":0.6,"section":"Pointer"},"$preview":{"width":960,"height":600}}'>
31
+ /* Shader kit */
32
+ function shaderStage(logic, frag) {
33
+ var calm = matchMedia('(prefers-reduced-motion: reduce)');
34
+ var view, paint, buf, gl, uni, raf = 0, away = 0, shown = true, broken = false, fresh = true, before = '', tints = {};
35
+ var time = 12, clock = 0, last = 0, down = false, inside = false, press = 0;
36
+ var level = 0, ticks = 0, due = 0, next = 0, wait = 2, sharp = 1.5;
37
+ var aim = [0.5, 0.5], mouse = [0.5, 0.5], trail = [], oops = 'This shader has an error. Ask Claude to fix it. ';
38
+ function fail(text) { broken = true; halt(); sleep(); logic.error = text; logic.setState({}); }
39
+ function part(kind, code) {
40
+ var s = gl.createShader(kind);
41
+ if (!s) return;
42
+ gl.shaderSource(s, code); gl.compileShader(s);
43
+ if (gl.getShaderParameter(s, gl.COMPILE_STATUS) || gl.isContextLost()) return s;
44
+ var log = gl.getShaderInfoLog(s) || '', m = /ERROR: \d+:(\d+): (.*)/.exec(log);
45
+ fail(oops + (m ? 'Shader line ' + m[1] + ': ' + m[2] : log));
46
+ }
47
+ function wake() {
48
+ if (gl && gl.isContextLost()) gl = null;
49
+ if (gl || broken) return !!gl;
50
+ buf = document.createElement('canvas');
51
+ gl = buf.getContext('webgl2', { alpha: false, antialias: false });
52
+ if (!gl) {
53
+ broken = !uni;
54
+ if (broken) view.title = 'This browser can\u2019t draw shaders. You can turn on hardware acceleration or try another browser.';
55
+ return false;
56
+ }
57
+ var v = part(gl.VERTEX_SHADER, '#version 300 es\nvoid main(){vec2 p=vec2(gl_VertexID<<1&2,gl_VertexID&2);gl_Position=vec4(p*2.-1.,0.,1.);}');
58
+ var f = v && part(gl.FRAGMENT_SHADER, '#version 300 es\nprecision highp float;\nout vec4 outColor;\n#line 1\n' + frag);
59
+ if (!f) return false;
60
+ var prog = gl.createProgram();
61
+ if (!prog) return false;
62
+ gl.attachShader(prog, v); gl.attachShader(prog, f); gl.linkProgram(prog);
63
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS) && !gl.isContextLost()) { fail(oops + gl.getProgramInfoLog(prog)); return false; }
64
+ gl.useProgram(prog);
65
+ uni = {};
66
+ for (var i = gl.getProgramParameter(prog, gl.ACTIVE_UNIFORMS) - 1; i >= 0; i--) {
67
+ var a = gl.getActiveUniform(prog, i);
68
+ uni[a.name.replace('[0]', '')] = { at: gl.getUniformLocation(prog, a.name), type: a.type };
69
+ }
70
+ fresh = true;
71
+ return true;
72
+ }
73
+ function sleep() {
74
+ var x = gl && gl.getExtension('WEBGL_lose_context');
75
+ gl = null; if (x) x.loseContext();
76
+ }
77
+ function tint(color) {
78
+ if (!tints[color]) {
79
+ var g = document.createElement('canvas').getContext('2d', { willReadFrequently: true });
80
+ g.fillStyle = color; g.fillRect(0, 0, 1, 1);
81
+ tints[color] = Array.from(g.getImageData(0, 0, 1, 1).data.slice(0, 3), (n) => n / 255);
82
+ }
83
+ return tints[color];
84
+ }
85
+ function put(name, v) {
86
+ var u = uni[name];
87
+ if (!u) return;
88
+ if (typeof v === 'string') v = tint(v);
89
+ if (u.type === gl.FLOAT) gl.uniform1f(u.at, v);
90
+ else if (u.type === gl.INT || u.type === gl.BOOL) gl.uniform1i(u.at, +v);
91
+ else if (u.type === gl.FLOAT_VEC2) gl.uniform2fv(u.at, v);
92
+ else if (u.type === gl.FLOAT_VEC3) gl.uniform3fv(u.at, v);
93
+ }
94
+ function draw(now) {
95
+ var dt = Math.min(0.2, (now - last) / 1000), vals = logic.uniforms(), still = calm.matches, text = JSON.stringify(vals), rate = vals.speed == null ? 1 : vals.speed;
96
+ last = now; clock += dt * rate;
97
+ if (!still) time += dt * rate;
98
+ if (!inside && !still) {
99
+ aim = [0.5 + 0.3 * Math.sin(time * 0.37), 0.5 + 0.24 * Math.sin(time * 0.53 + 1.1)];
100
+ drop(false);
101
+ }
102
+ var x = mouse[0], y = mouse[1], k = 1 - Math.pow(0.86, dt * 60);
103
+ mouse = [x + (aim[0] - x) * k, y + (aim[1] - y) * k];
104
+ press += ((down ? 1 : 0) - press) * k;
105
+ var cw = view.clientWidth, ch = view.clientHeight, s = Math.min(devicePixelRatio || 1, 1.5);
106
+ if (!view.__omCapGl) s = Math.pow(0.7, level) * Math.min(s, sharp, Math.sqrt(2.0736e6 / (cw * ch)));
107
+ var w = Math.max(2, Math.round(cw * s)), h = Math.max(2, Math.round(ch * s));
108
+ if (view.width !== w || view.height !== h) { view.width = w; view.height = h; fresh = true; }
109
+ var moved = Math.abs(mouse[0] - x) + Math.abs(mouse[1] - y) > 0.0005 || (press > 0.01 && press < 0.99);
110
+ if (still && !fresh && !moved && text === before) return;
111
+ before = text; fresh = false;
112
+ if (buf.width !== w || buf.height !== h) { buf.width = w; buf.height = h; }
113
+ gl.viewport(0, 0, w, h);
114
+ for (var name in vals) put(name, vals[name]);
115
+ put('uTime', time); put('uResolution', [w, h]); put('uMouse', mouse); put('uPress', press);
116
+ if (uni.uTrail) {
117
+ var t = new Float32Array(36);
118
+ for (var i = 0; i < 12; i++) {
119
+ var p = trail[i] || [0, 0, clock + 1];
120
+ t[i * 3] = p[0]; t[i * 3 + 1] = p[1]; t[i * 3 + 2] = clock - p[2];
121
+ }
122
+ gl.uniform3fv(uni.uTrail.at, t);
123
+ }
124
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
125
+ paint.drawImage(buf, 0, 0);
126
+ }
127
+ function frame(now) {
128
+ try { draw(now); } catch (e) { fail(oops + e.message); }
129
+ }
130
+ function count(now) { ticks = 0; due = now + 2e3; }
131
+ function tune(now) {
132
+ var f = frameElement;
133
+ sharp = f && f.getBoundingClientRect().width < f.offsetWidth / 2 ? 1 : 1.5;
134
+ if (ticks < 50 && !calm.matches) { level += level < 5; next = now + wait * 1e3; }
135
+ else if (level && now > next) { level--; wait = Math.min(wait * 2, 64); next = now + wait * 1e3; }
136
+ count(now);
137
+ }
138
+ function tick(now) {
139
+ raf = gl && !gl.isContextLost() ? requestAnimationFrame(tick) : 0;
140
+ if (!raf) return;
141
+ ticks++;
142
+ if (now > due) tune(now);
143
+ if (view.__omCapGl || now - last > (inside ? 12 : Math.max(30, level * level * 4))) frame(now);
144
+ }
145
+ function run() { clearTimeout(away); if (view && !raf && wake()) { count(last = performance.now()); raf = requestAnimationFrame(tick); } }
146
+ function halt() { cancelAnimationFrame(raf); raf = 0; }
147
+ function drop(force) {
148
+ var l = trail[0];
149
+ if (!force && l && Math.hypot(l[0] - aim[0], l[1] - aim[1]) < 0.04) return;
150
+ trail.unshift([aim[0], aim[1], clock]);
151
+ trail.length = Math.min(trail.length, 12);
152
+ }
153
+ function point(e) {
154
+ aim = [e.offsetX / (view.clientWidth || 1), 1 - e.offsetY / (view.clientHeight || 1)];
155
+ inside = true; run();
156
+ }
157
+ var on = {
158
+ pointermove: (e) => { point(e); drop(false); },
159
+ pointerdown: (e) => { point(e); down = true; drop(true); },
160
+ pointerup: () => { down = false; },
161
+ pointerleave: () => { inside = false; down = false; }
162
+ };
163
+ var seen = new IntersectionObserver((es) => {
164
+ clearTimeout(away);
165
+ shown = es[es.length - 1].isIntersecting;
166
+ if (shown) run();
167
+ else { halt(); away = setTimeout(sleep, 1500); }
168
+ });
169
+ function hold(el) {
170
+ for (var ev in on) {
171
+ if (view) view.removeEventListener(ev, on[ev]);
172
+ if (el) el.addEventListener(ev, on[ev]);
173
+ }
174
+ seen.disconnect();
175
+ view = el;
176
+ if (el) { paint = el.getContext('2d'); seen.observe(el); } else halt();
177
+ }
178
+ document.addEventListener('visibilitychange', () => count(performance.now()));
179
+ hold(logic.view.current);
180
+ if (wake()) frame(last = performance.now());
181
+ return {
182
+ update() { if (logic.view.current !== view) hold(logic.view.current); if (shown) run(); },
183
+ stop() { halt(); clearTimeout(away); hold(null); sleep(); }
184
+ };
185
+ }
186
+ var FRAG = `
187
+ uniform float uTime;
188
+ uniform vec2 uResolution;
189
+ uniform vec2 uMouse;
190
+ uniform vec3 uWater;
191
+ uniform vec3 uLight;
192
+ uniform float uRipple;
193
+ void main() {
194
+ vec2 uv = gl_FragCoord.xy / uResolution;
195
+ float glow = exp(-length(uv - uMouse) * 6.0) * uRipple;
196
+ outColor = vec4(mix(uWater, uLight, glow + 0.1 * sin(uTime)), 1.0);
197
+ }
198
+ `;
199
+ class Component extends DCLogic {
200
+ constructor(props) { super(props); this.view = React.createRef(); }
201
+ componentDidMount() { this.stage = shaderStage(this, FRAG); }
202
+ componentDidUpdate() { this.stage.update(); }
203
+ componentWillUnmount() { this.stage.stop(); }
204
+ uniforms() {
205
+ const p = this.props;
206
+ return { speed: p.speed ?? 1, uWater: p.water ?? '#0B3A4A', uLight: p.light ?? '#E8FFF4', uRipple: p.ripple ?? 0.6 };
207
+ }
208
+ renderVals() {
209
+ if (this.error) throw new Error(this.error);
210
+ return { view: this.view };
211
+ }
212
+ }
213
+ </script>
214
+ </body>
215
+ </html>
216
+ ```
217
+
218
+ In `project/canvas.json`, every shader's `boards` entry has `"is_interactive": true`, or it gets no Play button.
219
+
220
+ ## Uniforms the kit sets, if declared
221
+
222
+ - `uniform float uTime;` seconds, from 12. Frozen under reduced motion.
223
+ - `uniform vec2 uResolution;` pixels; it can change, so size things by it.
224
+ - `uniform vec2 uMouse;` the pointer, 0 to 1 with y up, smoothed. With no pointer over the artboard it drifts slowly (unless motion is reduced), so a pointer effect still shows.
225
+ - `uniform float uPress;` 0 to 1, eased, while pressed.
226
+ - `uniform vec3 uTrail[12];` points the pointer left behind, newest first: x, y as in `uMouse`, z the age in seconds at `speed`'s rate, below 0 when empty. Presses add a point.
227
+
228
+ ## Tweaks
229
+
230
+ - `uniforms()` returns one entry per uniform of yours, named as in `FRAG`, with the same default as `data-props`. A number sets a float or int, true/false a bool, a color string a vec3, an array a vec2 or vec3. An enum: `['Soft', 'Sharp'].indexOf(p.edge ?? 'Soft')`.
231
+ - `speed` is special: return it and the kit runs `uTime` at that rate. Never multiply `uTime` by a tweak in the shader.
232
+ - Give 4 to 8 levers, each visibly changing the picture across its whole range: every color, even one used once (nothing else can edit it), two or three shape sliders, `speed` if it moves, one or two pointer strengths. Sections: Color, Look, Motion, Pointer. The panel shows prop names as written, so use plain words, like `glow`.
233
+
234
+ ## GLSL that compiles first time
235
+
236
+ WebGL2, GLSL ES 3.00. The kit adds `#version`, precision and `out vec4 outColor;`, so `FRAG` starts at your uniforms. `main()` writes `outColor` with alpha 1.0.
237
+
238
+ - No `gl_FragColor`, `varying`, `texture2D`, `#version`, backticks or `${`.
239
+ - Floats have a point: `2.0`, not `2`, outside constructors. No mixing float and int: `float(i)`.
240
+ - A function is defined before it's used. No recursion.
241
+ - Never use as names: `input output filter sample half fixed common active partition smooth flat texture patch resource class long short double union cast`.
242
+ - Nothing that gives NaN: `pow(max(x, 0.0), y)`, `sqrt(max(x, 0.0))`, `normalize(v + 1e-5)`, divide by `max(d, 1e-4)`, `smoothstep` with its first edge below its second.
243
+ - Every pixel runs every frame: at most about 30 noise calls and 64 march steps a pixel. Never call `fbm` inside a loop over `uTrail`.
244
+
245
+ ## Making it good
246
+
247
+ - Paint a subject, not a screensaver: where the light comes from, what's near and far. 2 to 4 colors from the brief; never a rainbow.
248
+ - Aspect: `vec2 p = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;` keeps circles round. `(uMouse - 0.5) * uResolution / uResolution.y` puts the pointer in the same units, and `uTrail` the same way.
249
+ - Use the whole value range, from the ground color to near white.
250
+ - Slow: big shapes at 0.05 to 0.3 times `uTime`, small detail a little faster.
251
+ - Depth: 2 to 4 layers at different scales; soft falloffs (`exp(-d * k)`); highlights from a normal by finite differences.
252
+ - Light adds up: sum it, then `col = 1.0 - exp(-col * 1.3);` so it glows instead of clipping.
253
+ - Grain of about 0.02 hides banding.
254
+ - The pointer should read in the first second: something near it bends, glows or parts.
255
+
256
+ ```glsl
257
+ float hash(vec2 p) { p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
258
+ float noise(vec2 p) {
259
+ vec2 i = floor(p), f = fract(p), u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
260
+ return mix(mix(hash(i), hash(i + vec2(1, 0)), u.x), mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), u.x), u.y);
261
+ }
262
+ float fbm(vec2 p) {
263
+ float v = 0.0, a = 0.5; mat2 r = mat2(0.8, 0.6, -0.6, 0.8);
264
+ for (int i = 0; i < 5; i++) { v += a * noise(p); p = r * p * 2.02; a *= 0.5; }
265
+ return v;
266
+ }
267
+ vec2 grad(vec2 i) { float a = hash(i) * 6.28318; return vec2(cos(a), sin(a)); }
268
+ float gnoise(vec2 p) {
269
+ vec2 i = floor(p), f = fract(p), u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
270
+ return mix(mix(dot(grad(i), f), dot(grad(i + vec2(1, 0)), f - vec2(1, 0)), u.x),
271
+ mix(dot(grad(i + vec2(0, 1)), f - vec2(0, 1)), dot(grad(i + vec2(1, 1)), f - vec2(1, 1)), u.x), u.y);
272
+ }
273
+ float caustic(vec2 p, float t) {
274
+ mat2 r = mat2(0.8, 0.6, -0.6, 0.8);
275
+ vec2 w = vec2(gnoise(p * 0.9 + t * 0.23), gnoise(r * p * 0.9 + 7.1 - t * 0.2));
276
+ float a = gnoise(r * p * 1.7 + w * 2.2 + t * 0.17), b = gnoise(r * r * p * 2.6 - w * 1.6 - t * 0.14 + 3.3);
277
+ float r1 = exp(-abs(a) * 26.0), r2 = exp(-abs(b) * 34.0);
278
+ return r1 * 0.55 + r2 * 0.28 + r1 * r2 * 1.8;
279
+ }
280
+ ```
281
+
282
+ - Liquid, marble, smoke: `fbm(q + 1.6 * vec2(fbm(q + t), fbm(q + 5.2 - t)))`, mapped through your colors with `smoothstep`.
283
+ - Water light: `caustic(p * 2.0, uTime * 0.9)` at two scales, added over a clear blue or green floor. Keep the water bright.
284
+ - Ripples: for each `uTrail` point with z >= 0 at distance `d`, with `r = d - z * 0.34`, a ring `sin(r * 58.0) * exp(-r * r * 81.0) * exp(-z * 1.1)`. Sum into a height, take its gradient, and shift what you draw by it.
285
+ - Goo: `f`, the sum of `r * r / max(dot(p - c, p - c), 1e-4)` over moving centers `c`. `smoothstep(0.95, 1.05, f)` is the edge; `f`'s gradient gives a normal.
286
+ - Sunlight through leaves: looking up, leaves filling most of the frame. Cells of small rotated leaf shapes (check the 3x3 neighbors), 4 layers: far ones pale, near ones dark, each lit from behind (mixed toward yellow-green by `exp(-d * 3.0)` from the sun at `uMouse`), sky in the gaps, a soft flare on top.
287
+ - Soft shadows: a shape mask blurred by a wide `smoothstep`, drifting over a lit surface.
288
+ - Aurora: 3 curtains: a base height from `fbm`, `smoothstep(-0.06, 0.12, y) * exp(-y / h)`, `y` the height above it, vertical rays from `fbm`, added up.
289
+
290
+ ## Rules
291
+
292
+ - Flashing stays inside the accessibility limit at any tweak setting: nothing brightens and darkens over much of the artboard more than 3 times a second. Asked for a strobe, pulse as fast as that allows and say so.
293
+ - A shader someone asked for is exempt from "no gradient washes".
294
+ - Comments in `FRAG` are short labels. Nothing on the canvas holds instructions to Claude or a later reader: those live on this page.
295
+
296
+ ## Limits
297
+
298
+ One pass, no memory between frames, no pictures: no true fluid or particle simulation, no photo, video, camera, microphone or network input. Asked for one, make the effect itself over an abstract ground in the brief's colors, never a painted stand-in, and say in your reply what it can't do.
299
+
300
+ A desktop browser runs about 16 shaders at once; the rest hold still.
301
+
302
+ In your reply, say to press Play to use the pointer, and that Tweaks holds the levers.
@@ -59,7 +59,7 @@ source files, or state read back from the artifact).
59
59
  <!-- /shared:view-context -->
60
60
 
61
61
  Design publishes `{ mode, page, pageName, visibleArtboards, selectedArtboards,
62
- erroredArtboards, firstError, dirty, selected, selection }` (plus `edits`, above). Artboards are named by their .dc.html file with the
62
+ erroredArtboards, firstError, dirty, shaders, selected, selection }` (plus `edits`, above). Artboards are named by their .dc.html file with the
63
63
  part before `.dc.html` percent-encoded (`encodeURIComponent`:
64
64
  `"Coffee & Deck.dc.html"` arrives as `"Coffee%20%26%20Deck.dc.html"`;
65
65
  plain names are unchanged). Treat these as opaque tokens: to match one,
@@ -100,6 +100,8 @@ the whole record, as above.
100
100
  "Ask Claude to fix", their next message carries up to 1800 characters
101
101
  of these errors.
102
102
  - `dirty` — as above.
103
+ - `shaders` — `true` when you may read `shaders.md` for this viewer, absent
104
+ otherwise. Shaders already on the canvas run either way.
103
105
  - `selected` — up to 20 selected ELEMENTS, most recent last (entries of
104
106
  the most recently touched artboard come last); empty in `focused`
105
107
  and when only whole artboards are selected. Each entry is
@@ -2536,7 +2536,7 @@ async function runningServer(root, kind, probe) {
2536
2536
  return null;
2537
2537
  }
2538
2538
  }
2539
- var DEFAULT_PORTS = { slides: 4710, design: 4720, "design-system": 4730, dashboard: 4740, docs: 4750, motion: 4760 };
2539
+ var DEFAULT_PORTS = { slides: 4710, design: 4720, "design-system": 4730, data: 4740, docs: 4750, motion: 4760 };
2540
2540
  var PORT_TRIES = 100;
2541
2541
  var NetworkBlocked = class extends Error {
2542
2542
  code = "NETWORK_BLOCKED";