关于 specimen
specimen 是面向编码 Agent 的技能,通过编写单个 piece 文件制作主题化循环 3D 图标。它使用固定的渲染运行时,并支持生成视频或实时栅格内容。
主要功能
- 01单文件图标作品
- 02深色与浅色主题
- 03循环 3D 动画
- 04视频或实时栅格输出
- 05校验与联系表脚本
适用场景
- 01为项目制作产品图标
- 02生成深色与浅色主题图标
- 03将图标导出为视频或实时栅格内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lucasmarkes/ skills --skill specimen安装后在 Claude Code 中输入/specimen确认已加载。
详细介绍
Specimen:制作图标
你正在制作一个 Specimen 图标:一个置于柔和圆角底板上的单体光泽物件,如同陈列柜中的展品般受光。色彩只以光的形式呈现。动画循环衔接自然。从一个作品文件即可生成深色和浅色主题,既可以是路径追踪视频,也可以是实时栅格元素。
examples/ 中的作品是标准。动笔前,先阅读最接近需求的作品。如果你制作的图标放在它们旁边显得格格不入,那就还没完成。
你只编写一项内容:作品文件 <name>.specimen.js。底板、摄影棚、灯光、后期处理、主题、路径追踪器、降噪器和编码器均已固定,存放在 runtime/ 中。绝不要编辑 runtime/,绝不要粘贴一份修改过的副本,也绝不要重新实现它已有的功能。如果你认为运行时缺少某项功能,请告知对方,不要另辟蹊径绕过它。
下文提到的所有文件都位于此技能的文件夹中。作品写入对方的工作目录(或对方指定的位置);媒体文件放在对方指定的位置(public/icons/,……),或放在作品文件旁边,并以图标名称作为前缀。绝不要在对方的项目中自行创建文件夹。请从对方的工作目录中,通过文件在此文件夹中的路径运行脚本。
Specimen 仅指此技能。它没有 npm 包,也不需要安装步骤。绝不要运行 npm install,绝不要向对方的项目添加依赖,也绝不要在作品中导入 three:所有内容都通过 ctx 提供。
技能文件夹
specimen/
SKILL.md this file
concepts.md objects that read well, metaphors by product area, what failed
package.json { "type": "module" }, so Node reads this folder's scripts the same under any project
render.mjs piece → webm + mp4 + poster per theme (video), or runtime + piece + posters (--live)
sheet.mjs contact sheet: raster frames, a dark row and a light row per piece; --pt, --guide
validate.mjs the measurable rules (section 5)
piece.mjs reads a piece in Node: its name, its syntax errors with the line, the contract
session.mjs opens a piece on the runtime page in headless Chrome
cdp.mjs Chrome finder, a minimal DevTools Protocol client and a static server
runtime/ fixed — never edited
specimen.js the bundle: three (WebGPU), the path tracer, the denoiser, the stage, <specimen-icon>
weights.tza the denoiser's weights
THIRD_PARTY_NOTICES.md the bundled packages' and the weights' licences; --live ships it as specimen.js.LICENSE.txt
page.html the page render, sheet and validate load a piece into
examples/ the reference pieces (section 7)
脚本使用纯 node 运行(版本 22 或更高),且只使用 Node 内置模块。它们会驱动机器上已有的 Chrome(或 SPECIMEN_CHROME);生成视频需要 ffmpeg。如果缺少其中任一项,脚本会指出具体缺少什么:请明确告知对方缺少的项目以及获取方式(brew install ffmpeg);不要自行安装任何内容。
0. 交付内容
在讨论任何概念前,先用一条消息询问对方:
Video(默认)——路径追踪,生成
<name>-dark.webm/.mp4和<name>-light.webm/.mp4,每个主题各带一张海报图,并提供<video>代码片段。效果最佳,每种主题占用数百 KB,可在各处播放。 Live ——生成specimen.js(含许可证文件),并将作品复制到对方的静态文件夹中;每种主题各带一张海报图,并提供<specimen-icon>代码片段。它是栅格图,而非路径追踪:反射效果较柔和,且不使用降噪器。请直说这一点。它需要 WebGPU;不支持 WebGPU 或启用了减少动态效果时,会回退到海报图。 Both ——默认生成视频;只有在对方启用的位置才额外使用实时版本。
除非对方只要求一种主题,否则应渲染两种主题。同时询问文件的保存位置;如果对方没有说明,就将文件放在作品文件旁边。如果没有人可以询问,就选择视频、两种主题和作品文件旁边,并说明这一点。
尺寸
请按图标实际显示的尺寸渲染:视频尺寸一旦被拉伸到超过其像素尺寸,边缘就会变模糊,没有任何编码设置能恢复清晰度。先在项目中找到图标所在 CSS 或组件里的宽度,并查看其最宽断点。只有无法判断时,才在询问交付方式的同一条消息中询问:
| 显示尺寸(CSS px) | --size | 视频 | 每种主题 |
|---|---|---|---|
| 不超过 160 | 384 | 320 px | 约 4 分钟 |
| 不超过 320 | 768 | 640 px | 约 17 分钟 |
| 不超过 480 | 1152 | 960 px | 约 36 分钟 |
视频尺寸必须至少是 CSS 宽度的两倍,才能适配 Retina 屏幕;最终视频尺寸为 --size 的 5/6:选择能覆盖该宽度的第一行。宽度超过 480 时,--size 为宽度的 2.4 倍,并向上取整到 12 的倍数,耗时则随尺寸的平方增长。对于实时版本,尺寸只决定海报图的尺寸。如果没有人可以询问,也没有内容可供查阅,就选择 768 并说明这一点。
1. 概念
阅读 concepts.md。提出两到三个概念,每个概念占一行:
名称。 物体 · 动起来的部分 · 强调色的位置 · 表面效果。
示例:
闪电。 一个厚实、边缘柔和的闪电 · 一道光每轮循环两次自上而下流过它,并汇聚在尖端 · tile 下方的电荷与光池 · 光泽主体。
然后为强调色提出两到种调色板:这是图标唯一使用的颜色,让对方选择或指定自己的颜色:
调色板。 青色
#22d3ee(冷静、科技感) · 琥珀色#f59e0b(温暖、警示) · 中性色#a1a1aa(完全不带颜色:物体仅凭表面效果来呈现)。
颜色由对方决定:他们的品牌、版块和网格。根据产品领域和页面上相邻的图标提出建议(安全主题适合冷色,警示适合暖色,单色网格适合中性色);绝不能默默替对方选择。调色板会设置 scene.accent;如果他们希望发光颜色不同,还会设置 scene.glow。
等待对方选择。只有对方已经选好了物体和动画时才跳过这一步。如果没有人可以询问,就选择轮廓最鲜明的概念,并说明你选了哪个。
优秀概念的特点:
- 一个物体,一个想法。 需要配上说明文字才能理解的概念,还不算合格。
- 先看轮廓。 眯起眼睛看:如果仅凭轮廓无法辨认物体,就换一个。
- 物体在产品中有意义。 身份验证用锁,警示用铃。参见
concepts.md。 - **用一个动作讲述故事,**幅度小、速度慢:旋转、摆动、充能、脉动。不要同时做三件事。
2. 阅读最接近的示例
打开 examples/ 中与该概念最接近的示例,并通读全文。沿用其结构:scene 块、如何使用 geo.* 构建几何体,以及 update(phase) 如何确保每个项都是周期性的。
3. 编写作品
编写一个包含两个导出且不含导入的 ES 模块:
export const scene = {
accent: '#38bdf8', // the piece's only colour, always as light (a Tailwind 500 reads well)
glow: '#7dd3fc', // optional: the colour of its emission; default is the accent a step lighter
duration: 8, // seconds, 4–12; the loop must not jump
fit: { scale: 0.9, x: 0, y: 0 },// optical fit into the 58% safe area (section 4)
lights: { tint: 0.45 }, // optional: accent, rim, under, underFill, floor, tint, strips
};
export function object( ctx ) {
// build with ctx.mat.*, ctx.geo.*, ctx.texture()
return { mesh, update( phase ) { /* phase runs 0 → 1 once per loop */ } };
}
ctx
| 键 | 含义 |
|---|---|
THREE | 套件中的 three 实例;作品绝不导入 three |
mat | 下方的材质角色,根据当前主题解析 |
geo.roundedRect(shape, w, h, r) | 在 Shape 或 Path(孔洞)上创建圆角矩形;w、h 为半尺寸 |
geo.bevelExtrude(shape, { depth, bevel, segments, curveSegments }) | 挤出形状并形成柔和的圆角边缘,居中于 z = 0 |
geo.lathe(profile, segments) | 将由 [x, y] 对组成的轮廓绕 +Y 旋转 |
geo.planarUV(geometry, w, h) | 在 [-w, w] × [-h, h] 范围内生成 0..1 的 UV 坐标,用于动态纹理 |
texture(w, h) | 唯一的动态纹理:{ tex, draw(fn(u, v) → [r, g, b]) }。将 tex 作为角色的 emissiveMap,并在 update 中调用 draw。再次调用会抛出错误:路径追踪器为每个对象持有一张动态纹理。详见下文 |
gradient(stops, { srgb }) | 静态垂直渐变,渐变点为 [t, css colour],从顶部(0)到底部(1);用于火焰和轨迹 |
accent, glow | 两种颜色,类型为 THREE.Color,供纹理的 draw 使用 |
theme | 'dark' 或 'light',只读;仅在万不得已时使用 |
TAU, bump(x, c, w), smoothstep(x, a, b) | 动画辅助函数:bump 是高斯函数 exp(-((x − c) / w)²),在 c 处为 1,超过 2w 后接近 0(但不会恰好为 0);smoothstep 在 a 到 b 之间从 0 平滑过渡到 1 |
空间和尺寸
图块为 2 × 2 个单位,以原点为中心,朝向摄像机(+x 向右,+y 向上,+z 朝向摄像机)。安全框为 1.16 个单位见方(图块的 58%),中心位于 y = +0.04。将物体做成约 1.0–1.3 个单位宽、最高约 1.6 个单位,并以自身原点为中心,位于 z ≈ 0.45:示例在 fit.scale 为 0.8–0.9 时就是这个尺寸。
scene.fit 会将物体包在一个组中:scale 会以世界原点为中心缩放物体(不会重新居中),x、y 则以世界单位移动物体(y: -0.08 表示在图块上向下移动 4%)。系统不会替你居中:如果物体在画布上偏离中心,先将其几何体居中(geometry.center(),或在组内移动各个部件),再调整 fit。
在 64 px 尺寸下仍清晰可见的厚度:管和杆的半径至少为 0.12,倒角至少为 0.05。主光源是位于上方偏右的柔光箱,因此负值的 rotation.y(约为 −0.3,会让物体右侧转向摄像机)能让倒角更好地接住光线。
动态纹理
draw(fn) 会对每个纹素调用一次 fn(u, v),其中纹理范围内的 u 和 v 为 0..1(v = 0 是第一行),纹理会通过网格自身的 UV 读取。拉伸体和扁平形状需要使用 geo.planarUV(geometry, w, h),它会将 v = 0 映射到底部(y = −h),将 u = 0 映射到左侧。车削体、管和球体已有 UV(TubeGeometry 的 u 沿路径延伸)。返回 0..1 范围内的线性颜色:超出范围的值会被截断,emissiveIntensity(螺栓使用 3)会对其进行缩放。使用 glow.r * k, glow.g * k, glow.b * k,其中 k 为 0..1;在物体应保持黑色的区域,保留深色底色(k ≈ 0)。沿路径的色带使用 texture(256, 16) 即可,表面则使用 128 × 128:draw 每帧都会在 CPU 上运行。
材质用途
所有材质都来自 mat.*。切勿自行创建材质(new THREE.MeshStandardMaterial(…) 会导致验证失败):手动创建的材质只有一种颜色,而浅色主题需要另一种颜色。
| 用途 | 深色主题 | 浅色主题 | 用于 |
|---|---|---|---|
mat.body(o) | 亮面黑色 | 白色釉面陶瓷 | 物体主体 |
mat.chrome(o) | 铬色 | 铬色 | 圆环、带、铰链、卸扣 |
mat.glass(o) | 深色玻璃 | 白色内芯上的透明玻璃 | 骰子、镜片、屏幕 |
mat.inlay(o) | 比黑色略浅 | 珍珠灰 | 主体上的凸起细节 |
mat.glow(k, o) | 发光颜色的自发光 | 发光颜色作为表面颜色,并带微弱自发光 | 徽章、钥匙孔、尖端珠:仅用于一个小型发光部件,绝不可用于主体 |
mat.light(k, o) | 暖白色发光 | 温暖、微弱的光芒 | 窗户、舷窗、火焰 |
o 仅用于覆盖物理属性:finish、finishRepeat、roughness、metalness、clearcoat、normalMap、side、transparent、depthWrite、emissiveMap、alphaMap、emissiveIntensity。传入颜色(color、emissive 等)会抛出错误:颜色由主题决定。动态纹理在深色主题下通过 emissiveMap 发光,在浅色主题下则成为白色陶瓷上的颜料,因此同一个 draw 两种主题都适用。mat.glow 部件是浅色主题中显示强调色的唯一部位,因此它必须保持小巧:徽章、珠子、钥匙孔。
表面处理
每个角色都采用一种表面处理:mat.body({ finish: 'brushed', finishRepeat: 5 })。表面处理会设置物理属性(对于带纹理的处理,还会设置精细的静态贴图),同时保留角色的颜色,因此在两种主题下都适用。部件自身传入的值优先于表面处理。
| 表面处理 | 光线照射时的效果 | 适用对象 |
|---|---|---|
gloss | 清漆下的镜面效果:清晰映出摄影棚灯条 | 应呈现漆面效果的主体 |
matte | 无反射,仅有柔和的形体明暗 | 裙摆、核心、需要弱化存在感的部件 |
brushed | 带细纹的金属:高光沿纹理延展成一道光带 | 板件、框架、套环、锁体 |
perforated | 带圆孔网格的金属,每个孔都能捕捉到自己的高光 | 格栅、键帽板、扬声器面板 |
speckle | 粗糙表面上稀疏分布着明亮的闪光点,如石材或闪粉漆 | 盖帽、键帽、铃铛主体 |
finishRepeat 表示每个 UV 单位上的重复次数(默认值为 10)。挤出体使用场景单位的 UV,因此无论尺寸如何,纹理间距都相同;车削体和管体的 UV 范围为 0..1,因此适合使用较小的数值(3–5)。精细的表面处理看起来像材质,粗糙的则像印刷图案(过宽的拉丝纹路看起来像木纹)。
绘制清晰线条的两条细灯带可通过 scene.lights.strips 缩放(0–1,默认值为 1)。当物体旋转时,如果精细倒角扫过灯带,请调低此值:反射比一个像素还细,会在不同帧间闪烁。螺栓使用 strips: 0。
validate.mjs 会将源代码作为文本读取,因此即使注释中包含 …Material(,也会导致 materials 检查失败。
制作规则
- 一个物体,一个想法。 不要添加第二个物体、道具或文字。
- 先看轮廓。 在 64 px 下能辨认出来,否则就不合格。
- 每个图标都要有精致的光影。 摄影棚使用宽大的柔光箱和两条细长的硬光灯带:灯带会沿倒角和边缘勾勒出清晰的光线。图标至少要采用两种表面处理,让这些光线在图标不同区域呈现不同特征(例如,哑光裙摆上的亮面顶部,或斑点主体周围的拉丝框架)。图标应通过表面受光的方式来呈现,而不只是依靠轮廓。
- 点缀色只能以光的形式出现。 主体在深色主题下保持黑色,在浅色主题下保持白色。点缀色只能以发光效果的形式出现在瓷砖下方的光池和边缘上。绝不要将表面涂成点缀色。
- 适配 58% 的框内区域。 实体主体位于安全区域内,略微高于中心,并且与瓷砖边缘始终至少相距 4%。使用
scene.fit定位,不要调整物体自身的位置。 - 循环不能跳变。
update中的每一项都必须是phase · TAU的整数谐波函数,或是在循环两端均为 0 的smoothstep包络。Phase 1 的效果必须与 Phase 0 完全一致。 - 光效必须留在瓷砖内。 溢出瓷砖边缘的发光和光晕无法通过验证。
- 绝不要编辑
runtime/。
4. 生成图集 → 检查 → 修复
node <skill>/sheet.mjs <piece...> [--frames 0,60,120,180] [--guide] [--pt] [--out sheet.png]
它会将第 0、60、120 和 180 帧(或 --frames 指定的帧)渲染为 256 px 的单元格:每个部件各有一行深色主题和一行浅色主题;使用 --guide 时会以紫色绘制 58% 的安全框。--pt 会在每行末尾添加第五个单元格:对第 0 帧进行路径追踪并降噪。每个部件的光栅渲染需要几秒钟;使用 --pt 每个部件还会增加约半分钟。将一个示例放在你的部件后面,即可按行对比。
查看 PNG。检查以下内容:轮廓是否清晰;每一帧中的主体是否都位于框内;浅色主题行的表现是否不逊于深色主题行(白色背景上的发光效果清晰可见,没有灰暗浑浊的效果);各帧之间的动作是否明显。修复并重复检查,直到它能与示例并列展示。完整渲染前至少运行一次 --pt:光栅单元格比最终效果更亮,而在光栅渲染中清晰可见的黑色部件,在路径追踪后的深色瓷砖上可能会变得不明显。要在 256 px 下检查细节,可在临时目录中复制一份,并增大 fit.scale;绝不要在他人的项目中这样做。
5. 验证
node <skill>/validate.mjs <name>.specimen.js
它会打印每条未通过规则及其原因和编号;任何规则未通过时,都会以状态码 1 退出。重复操作,直到它打印 ok。
| 规则 | 未通过条件 |
|---|---|
contract | 未导出 scene 或 object;accent 不是十六进制颜色;duration 不在 4–12 范围内;object() 抛出异常或未返回 mesh |
materials | 源代码中出现 …Material( 构造函数 |
seam | 相位 0 与相位 1 的栅格图像平均差异超过 0.5(0–255) |
fit | 实体部分(对象轮廓中间的 90%,透明网格(如火焰和光环)除外;不透明的 mat.glow 部分计入)超出安全框超过图块的 3%,或者整个轮廓距图块边缘不足 4%;检查循环中第 0、¼、½ 和 ¾ 帧 |
light-in-tile | 图块外任何区域的亮度比页面高出 2/255 以上 |
one-texture | 调用了两次 texture() |
6. 渲染并交付
开始前,根据 §0 中的尺寸表告知预计耗时:在近期的 Mac 上,--size 768 每帧约需 4 秒,因此 8 秒的循环(240 帧)每种主题约需 17 分钟,两种主题约需 35 分钟。仅实时模式大约一分钟,只渲染两张海报。视频渲染耗时会超过任何前台命令的超时限制,因此请在后台运行并查看进度行(每 30 帧显示一次,如 30/240 · 666 s left),直到打印出文件;不要在前台运行,否则超时会导致渲染中途终止。
node <skill>/render.mjs <name>.specimen.js --out public/icons --size 768 # video, both themes
node <skill>/render.mjs <name>.specimen.js --out public/icons --live # live
node <skill>/render.mjs <name>.specimen.js --out public/icons --live --video # both
--themes dark 会渲染一种主题。它会打印文件及其大小,还有代码片段;代码片段中的路径假定文件夹通过 /<folder name> 提供服务:请将前缀设置为实际提供服务的位置。
交付文件和代码片段,然后询问网站是跟随 OS 主题,还是提供了切换按钮([data-theme])。打印的视频代码片段会跟随 OS 主题:
<video autoplay muted loop playsinline poster="/icons/bolt-dark-poster.jpg">
<source src="/icons/bolt-light.webm" type="video/webm" media="(prefers-color-scheme: light)">
<source src="/icons/bolt-light.mp4" type="video/mp4" media="(prefers-color-scheme: light)">
<source src="/icons/bolt-dark.webm" type="video/webm">
<source src="/icons/bolt-dark.mp4" type="video/mp4">
</video>
对于带有 [data-theme] 切换按钮的网站,提供两个视频和用于选择其中一个的 CSS:
<video class="specimen-dark" autoplay muted loop playsinline poster="/icons/bolt-dark-poster.jpg">
<source src="/icons/bolt-dark.webm" type="video/webm">
<source src="/icons/bolt-dark.mp4" type="video/mp4">
</video>
<video class="specimen-light" autoplay muted loop playsinline poster="/icons/bolt-light-poster.jpg">
<source src="/icons/bolt-light.webm" type="video/webm">
<source src="/icons/bolt-light.mp4" type="video/mp4">
</video>
<style>
.specimen-light { display: none; }
[data-theme="light"] .specimen-light { display: block; }
[data-theme="light"] .specimen-dark { display: none; }
</style>
实时模式:
<specimen-icon src="/icons/bolt.specimen.js" poster="/icons/bolt-dark-poster.jpg" poster-light="/icons/bolt-light-poster.jpg"></specimen-icon>
<script type="module" src="/icons/specimen.js"></script>
除非设置了 theme="dark|light",否则 <specimen-icon> 会跟随 prefers-color-scheme(在带有 [data-theme] 的网站上,请通过切换按钮设置该属性);它会在离开屏幕时暂停,并在缺少 WebGPU 或设置了减少动态效果时保留海报:浅色主题使用 poster-light,其他情况使用 poster。通过 CSS 的 width 调整其大小(它会保持正方形;默认是 160 px)。
每一帧中,图块外的区域都与页面颜色完全一致(黑色或白色)。若图标位于其他颜色的版块上,请使用混合模式融入背景:深色主题使用 mix-blend-mode: screen,浅色主题使用 multiply。
7. 标准
examples/rocket.specimen.js— 一枚向右上方飞行的火箭,每次循环翻滚一周;火焰来自gradient,会照亮瓷砖;配有镀铬环带和喷嘴;舷窗采用mat.light。examples/bolt.specimen.js— 一枚厚实的倒角螺栓;唯一的动态纹理会让能量沿螺栓流动,并汇聚到尖端。examples/bell.specimen.js— 一口车削成形的铃铛,每次循环鸣响两次,镀铬铃锤滞后摆动;铃铛鸣响时,mat.glow徽章会弹出。examples/lock.specimen.js— 一把挂锁,每次循环中镀铬锁梁抬起并摆动打开一次,同时钥匙孔亮起;fit.y可确保抬起的锁梁不会超出顶部。examples/chip.specimen.js— 一枚四周都有镀铬引脚的芯片;mat.glass晶粒中,方形光脉冲通过动态纹理绘制,向外荡漾,每次循环两次。examples/cube.specimen.js— 一个无彩色的魔方:26 个小方块,每个贴纸都是同一种黑色的不同表面质感(在浅色模式下为白色);各层每次循环转动四次四分之一圈,并合拢还原。中性色调:仅凭表面质感就能看出区别。examples/key.specimen.js— 一个键盘键帽,置于穿孔镀铬板上;键帽顶面带有斑点纹理,裙边为哑光,回车箭头图例会发光;每次循环按下两次,带有回弹效果,同时下方的灯光会增强。examples/gears.specimen.js— 两个啮合的镀铬齿轮,大齿轮带有减重孔;每次循环逐齿啮合四次,同时轮毂会增强光亮。几何形状对称的旋转部件应保持普通镀铬表面:纹理表面会随其旋转,从而破坏循环。
8. 禁止事项
- 绝不要编辑
runtime/,也不要提交修改过的specimen.js副本。 - 绝不要在作品中构建材质或挑选颜色;每个表面都应来自
mat.*。 - 绝不要调用两次
texture(),也不要在作品中导入任何内容。 - 绝不要运行
npm install、添加依赖,或在对方的项目中自行创建文件夹。 - 绝不要把实时渲染称为“路径追踪”;它是光栅渲染。
- 在
validate.mjs输出ok之前,绝不要渲染。