dsh-claude-design 2.12.3 → 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 +3 -1
- package/lib/index.js +1 -1
- package/package.json +14 -14
- package/skills/claude-design/SKILL.md +1 -1
- package/skills/claude-design/artifact-type/reference/craft.md +9 -8
- package/skills/claude-design/artifact-type/reference/shaders.md +302 -0
- package/skills/claude-design/artifact-type/reference/view-state.md +3 -1
- package/skills/claude-design/scripts/artifact.mjs +1 -1
- package/skills/claude-design/scripts/editor/artifact-type/dc-runtime.js +7067 -23419
- package/skills/claude-design/scripts/editor/artifact-type/fast-edit-worker.js +115 -38
- package/skills/claude-design/scripts/editor/assets/index-B-f9ndUv.css +1 -0
- package/skills/claude-design/scripts/editor/assets/{index-B0Ur8XcL.js → index-Ci2kZlUe.js} +421 -452
- package/skills/claude-design/scripts/editor/index.html +3 -3
- package/skills/claude-design/scripts/server.mjs +2 -2
- package/skills/claude-design/scripts/editor/assets/index-B-Upphbb.css +0 -1
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.
|
|
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.
|
|
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,18 @@
|
|
|
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
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "2.13.0",
|
|
35
|
+
"date": "2026-11-10",
|
|
36
|
+
"en": "### Features\n- **Whiteboard is released.** `claude-whiteboard` lays out thinking on a board: a diagram, a flow, a map of ideas, a plan, a comparison or a drawn explanation. The agent writes it as elements (sections, blocks joined by arrows, text, tables, sticky notes, drawings and small working embeds) into the board's own store, and you watch them land in the local board editor. There you move and resize things, type, draw with the pen, and add sections, sticky notes, shapes and stickers from the toolbar, and the agent sees what you changed. Comment on an item, a spot or an area and send it to the agent, which changes the board and replies. **Share** opens the board to your local network with a link per role, everyone on a link sees the others' cursors and changes live, and its Export tab downloads the whole folder as a zip. **History** lists the versions. One server serves every board on the computer. Needs Node 22+. The board's own assistant on claude.ai (the Ask box, cursor chat, read-aloud and dictation) is not available locally; ask the agent in your conversation instead.\n- DeepSeek Harness: the new `dsh-claude-whiteboard` plugin. Comments sent from a board reach the agent, and boards are listed on the **Artifacts** page with a picture of the board, where they can be renamed like the other kinds.\n- ChatGPT and Codex: the new **Baoyu Whiteboard** plugin in the baoyu-design marketplace.",
|
|
37
|
+
"zh": "### 新功能\n- **白板正式发布。** `claude-whiteboard` 把想法摆在白板上:示意图、流程、想法地图、计划、对比或手绘的讲解。Agent 把它写成一个个元素(分区、用箭头连起来的方块、文字、表格、便签、手绘图和能操作的小嵌入),存进白板自己的存储,你在本地白板编辑器里看着它们一个个出现。你可以拖动、缩放、打字、用画笔画,从工具栏加分区、便签、形状和贴纸,你改了什么 Agent 也看得到。对一个元素、一个位置或一块区域留评论发给 Agent,它改好白板再回复你。**Share** 把白板开放到局域网,每种角色一个链接,拿到链接的人能实时看到彼此的光标和改动,它的 Export 页可以把整个文件夹下载成 zip。**History** 列出各个版本。这台电脑上的所有白板共用一个服务器。需要 Node 22+。claude.ai 上白板自带的助手(Ask 输入框、光标聊天、朗读和听写)在本地用不了,在对话里问 Agent 就好。\n- DeepSeek Harness:新增 `dsh-claude-whiteboard` 插件。白板上发出的评论会交给 Agent,白板也列在 **Artifacts** 页面上,带白板的缩略图,可以像其他类型一样改名。\n- ChatGPT 和 Codex:baoyu-design 插件市场新增 **Baoyu Whiteboard** 插件。"
|
|
38
|
+
},
|
|
27
39
|
{
|
|
28
40
|
"version": "2.12.3",
|
|
29
41
|
"date": "2026-11-10",
|
|
@@ -59,18 +71,6 @@
|
|
|
59
71
|
"date": "2026-11-05",
|
|
60
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.",
|
|
61
73
|
"zh": "### 新功能\n- **DeepSeek Harness:新版本不再弹出来打断你。** 左侧栏的 **Artifacts**(「作品」)这一行会多一个 **更新** 标签(侧栏收起后还留着图标的话,是图标上的小圆点),「作品」页顶部的面板写着版本、每个插件和这次改了什么。**全部更新** 一起更新;每个插件那一行的 **更新** 只更新它自己。**稍后** 把面板收成一行,标签也消失,等下一个版本再提示。\n- 更新时可以离开「作品」页:标签会显示 **更新中**、**更新失败** 或 **待重启**。**重试** 从停下的地方接着来:**全部更新** 失败就接着更新剩下的,单独更新失败就再试这一个。\n- 插件详情页的 **更新** 按钮现在只更新这一个插件;更新时开着的页面被替换后,会自己回来。"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
"version": "2.10.1",
|
|
65
|
-
"date": "2026-11-05",
|
|
66
|
-
"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.",
|
|
67
|
-
"zh": "### 修复\n- DeepSeek Harness 0.2.2 自带了插件更新,插件详情页因此出现两个更新按钮和两个标签,应用的和插件的各一套。现在只有一个:没用卡片更新过的插件显示应用的按钮,更新过的显示插件自己的。\n- 一键更新开始前会重新确认要更新什么,刚用应用自带按钮更新过的插件不会再更新一次。更新的回复丢失时,卡片不再说插件还是原来的版本,而是请你到插件页确认。\n- [使用说明](dsh-plugins/README.zh-CN.md#遇到问题)写明了应用自带的 **更新到 x** 更新第二个插件失败、插件被关掉时怎么办;也说明了在 DeepSeek Harness 0.2.2 上,2.9.2 和更早的版本可以直接用这个按钮更新,不用手动。"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"version": "2.10.0",
|
|
71
|
-
"date": "2026-11-05",
|
|
72
|
-
"en": "### Features\n- **The DeepSeek Harness plugins update in one click.** When a new version is out, the app offers it: a card at the bottom right, a line at the top of the **Artifacts** page, and an **Update** button on each plugin's page. One click updates the plugins together, with nothing to uninstall, and most updates need no restart. If an update fails, the plugin stays on the version it was. A plugin added from a folder isn't offered one. This starts with 2.10.0, so 2.9.2 and earlier need [one last update by hand](dsh-plugins/README.md#update).\n- **DeepSeek Harness: a skill can be kept to one agent preset.** Turn off the plugin's own row and declare the plugin in the preset, and `/` lists the skill only in that preset's sessions. The **Artifacts** page and comments keep working, through a new package, `dsh-claude-artifacts`, that each plugin installs with it. See [how to set it up](dsh-plugins/README.md#keep-a-skill-to-one-preset).\n\n### Fixes\n- `archive` and the **Whole folder** export zipped `.git`, including the remote's address and any token in it, and that zip is what gets sent to other people. `.git` now stays out, at any depth.\n- An artifact folder's default `.gitignore` took `exports/` (a film's MP4 too) and two of the three temp file shapes into git, and its `history/` rule also matched a project's own folder of that name, which git then dropped. `history/` and `exports/` now match only at the top of the folder.\n- `share` could give out a TUN proxy's address, such as sing-box's `172.19.0.1`, first when the system lists it first. It now skips an address that answers at every port, and lists tunnels after the local network.\n- Design's Standalone export: a `#id` link did nothing when the file was opened directly, and over `http://` it also scrolled the page around the frame. A click now scrolls inside the frame only, as Play does.",
|
|
73
|
-
"zh": "### 新功能\n- **DeepSeek Harness 插件可以一键更新。** 出了新版本,应用会提示你:右下角弹出卡片,**Artifacts** 页(中文界面是「作品」)顶部和每个插件的详情页也有 **Update**(更新)按钮。点一下,这些插件一起更新,不用卸载,多数情况也不用重启。更新失败时插件还是原来的版本。从文件夹安装的插件不会收到提示。这个功能从 2.10.0 开始,所以 2.9.2 和更早的版本要[最后手动更新一次](dsh-plugins/README.zh-CN.md#更新)。\n- **DeepSeek Harness:技能可以只留给某个 Agent 预设。** 关掉插件自己那一行,再在预设里声明这个插件,`/` 就只在这个预设的会话里列出这个技能。作品页和评论照常工作,它们现在由新包 `dsh-claude-artifacts` 负责,每个插件都会带上它。[设置方法](dsh-plugins/README.zh-CN.md#只让某个预设用某个技能)。\n\n### 修复\n- `archive` 和导出的 **Whole folder**(整个文件夹)会把 `.git` 打进 zip,里面有远程仓库地址和可能带着的 token,而这个 zip 正是要发给别人的。现在任何层级的 `.git` 都不会打进去。\n- 作品文件夹默认的 `.gitignore` 会把 `exports/`(包括影片的 MP4)和三种临时文件里的两种提交进 git;它的 `history/` 规则还会匹配项目自己的同名文件夹,导致 git 把那个文件夹漏掉。现在 `history/` 和 `exports/` 只匹配文件夹顶层。\n- 系统把 TUN 代理的网卡排在前面时,`share` 可能先给出代理的地址,比如 sing-box 的 `172.19.0.1`。现在会跳过在所有端口都有应答的地址,并把隧道网卡排在局域网后面。\n- 设计稿的 Standalone 导出:直接打开文件时,`#id` 链接点了没反应;通过 `http://` 打开时,还会连带滚动框架外的页面。现在点击只在框架内滚动,和 Play 一样。"
|
|
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.
|
|
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
|
|
159
|
-
`<a href>`, and `<input>`
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
text at a contrast of at least 4.5:1
|
|
163
|
-
|
|
164
|
-
white text: darken both. Colors that must be
|
|
165
|
-
lightness, not hue alone; prefer blue/orange to
|
|
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,
|
|
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";
|