yuanlang12/openpipal/interactive-prototype

Claude Code and agent skill by yuanlang12/openpipal

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

可交互原型 + 手机设备外框的说明书——DCLogic 状态驱动的交互清单,以及 ios-frame / android-frame 两件外框共 13 个预制件的全部 props 与默认值、明暗方案在两个平台上完全不同的下发方式、内容区自带的内边距与层序、"看着成了其实没生效"的一批静默失败(裸属性、驼峰属性名、IOSList 的下传规则)。判据:taskType=原型,或用户要"能点的 / 手机 App 原型 / phone mockup / 多屏流程"时必读;写第一行之前先读 dc-authoring(DC 格式、模板语法、x-import 协议、逻辑块都归它),再读这份。

Install

npx vskill@latest install yuanlang12/openpipal/interactive-prototype

SKILL.md

可交互原型(Interactive prototype)

这份技能管两件事:怎么让一份 DC 摸起来像真的在运行的 app, 以及怎么把它装进两件设备外框预制件(./ios-frame.jsx 7 个件、./android-frame.jsx 6 个件,共 13 个)。

DC 文件格式本身(骨架、{{ }} 空穴、sc-for/sc-if、伪态样式、x-import 协议、 DCLogic、data-props、画板模式、告警表)全在 dc-authoring,这里只引用不重复。


1. 骨架(可直接抄,每一行都跑得通)

两屏 iOS 原型:列表 → 详情,滑动切换,浮一枚毛玻璃迷你播放条。

html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<style>
*{ box-sizing:border-box }
html,body{ margin:0; height:100%; background:#e8e6e2 }
</style>
</helmet>
<div style="min-height:100%; display:flex; align-items:center; justify-content:center; padding:32px 0">
<x-import component-from-global-scope="IOSDevice" from="./ios-frame.jsx"
title="Library" dark="{{ true }}" hint-size="402px,874px">
<div style="position:relative; height:100%; overflow:hidden">
<!-- 轨道:全篇唯一一处让空穴决定布局的地方——它是唯一会动的东西,其余几何一律内联 -->
<div style="{{ track }}">
<!-- 屏 1 · 列表 -->
<div style="width:50%; height:100%; overflow:auto; padding-bottom:100px">
<x-import component-from-global-scope="IOSList" from="./ios-frame.jsx" header="最近播放" dark="{{ true }}">
<sc-for list="{{ songs }}" as="s" hint-placeholder-count="5">
<div onClick="{{ s.open }}" style="cursor:pointer" style-active="opacity:.55">
<x-import component-from-global-scope="IOSListRow" from="./ios-frame.jsx"
title="{{ s.name }}" detail="{{ s.len }}" icon="{{ s.tint }}" dark="{{ true }}"></x-import>
</div>
</sc-for>
</x-import>
</div>
<!-- 屏 2 · 详情。不写 font-family:外框根节点已经铺了平台字体栈,这里继承 -->
<div style="width:50%; height:100%; overflow:auto; padding:0 24px 100px">
<div onClick="{{ back }}" style="display:inline-block; padding:14px 0; font-size:16px; color:#0a84ff; cursor:pointer">‹ 返回</div>
<div style="height:220px; border-radius:22px; background:linear-gradient(160deg,#3a3a3c,#1c1c1e)"></div>
<div style="margin-top:20px; font-size:26px; font-weight:600; color:#f5f5f7">{{ picked.name }}</div>
<div style="margin-top:6px; font-size:15px; color:rgba(235,235,245,0.62)">{{ picked.len }}</div>
</div>
</div>
<!-- 迷你播放条:轨道的兄弟,所以不跟着屏幕滚动 -->
<x-import component-from-global-scope="IOSGlassPill" from="./ios-frame.jsx" dark="{{ true }}"
style="position:absolute; left:16px; bottom:18px; width:370px; height:58px; padding:0 14px">
<div style="display:flex; align-items:center; gap:12px; width:342px">
<div style="width:34px; height:34px; border-radius:8px; background:{{ picked.tint }}"></div>
<div style="flex:1; min-width:0; font-size:14px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap">{{ picked.name }}</div>
<div onClick="{{ toggle }}" style="font-size:16px; cursor:pointer" style-active="opacity:.5">{{ playIcon }}</div>
</div>
</x-import>
</div>
</x-import>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script>
class Component extends DCLogic {
state = { screen: 'list', picked: null, playing: false }
renderVals() {
const songs = [
{ name: '夜航', len: '3:24', tint: '#5b8def' },
{ name: '晚风', len: '4:02', tint: '#c2603f' },
{ name: '钝角', len: '2:47', tint: '#4f9e7a' }
]
const shifted = this.state.screen === 'detail'
return {
songs: songs.map((s) => ({ ...s, open: () => this.setState({ screen: 'detail', picked: s }) })),
picked: this.state.picked || songs[0],
track: 'display:flex; width:200%; height:100%; transition:transform .38s cubic-bezier(.32,.72,0,1);' +
' transform:translateX(' + (shifted ? '-50%' : '0') + ')',
back: () => this.setState({ screen: 'list' }),
playIcon: this.state.playing ? '❚❚' : '▶',
toggle: () => this.setState((s) => ({ playing: !s.playing }))
}
}
}
</script>
</body>
</html>

逻辑块的三条硬条件(dc-authoring §4 的约定,这里只重申,写错就整块失效): 标签必须是 <script type="text/x-dc" data-dc-script>(不写 type 浏览器会先执行一遍并抛 ReferenceError); 类名必须恰好叫 `Component`;必须 extends DCLogic,且必须有 renderVals()。


2. 两件设备外框

绝不自己手绘手机壳。状态栏、灵动岛/挖孔、导航区、Home Indicator / 手势条、圆角、描边、 落地阴影、内容裁切全部自带,自己画既是像素级精细活又白烧 token。

iOSAndroid
文件./ios-frame.jsx./android-frame.jsx
外框组件IOSDeviceAndroidDevice
默认尺寸 / hint-size402px,874px412px,892px
机身边框无(整块就是屏)10px 实体边框,box-sizing:border-box
屏幕可用区402 × 874392 × 872(各边被边框吃掉 10px)
顶部系统区状态栏 54px,浮在内容之上状态栏 36px,占位在流内
底部系统区Home Indicator 浮层(内容区自带 28px 余量)手势导航条 26px,占位在流内
系统区能否关掉导航区能(不传 title),状态栏与 Indicator 不能都不能,状态栏与手势条恒在
2.1 怎么挂

13 个件全部挂在 `window` 上,都能用 x-import 的 component-from-global-scope 直接挂进模板, 属性与 children 原样透传,可以嵌套(外框里套列表,列表里套行)。 不要为了用它们去写 React.createElement——那条路做 UI 布局宿主的就地编辑摸不到。

html
<x-import component-from-global-scope="IOSDevice" from="./ios-frame.jsx"
title="Settings" dark="{{ true }}" keyboard="{{ true }}" hint-size="402px,874px">
…屏幕内容…
</x-import>
  • 顶层那个 `x-import` 必须写 `hint-size`(= 外框尺寸),否则流式期只有一个撑不出版面的小占位。
  • 套在外框里的子件不用写 `hint-size`:它们和外框同一个文件,外框就绪的那一刻它们已经在 window 上,

永远不会进占位态。

  • 所有 x-import 都写成成对标签(<x-import …></x-import>)。HTML 解析器不认自闭合的未知元素,

写 /> 会把后面的兄弟节点全吞进去。

2.2 title 的三态语义(两件同款判定:title !== undefined)
你写的结果
不传 `title`没有导航栏 / 应用栏。做全屏 app、自定义头部、通栏封面的唯一正确写法
title=""出一条空标题的导航栏(合法用法:只要那条横杆占位)
title="Library"正常标题

自带导航栏上的按钮全是装饰——IOSNavBar 的返回尖角与三点、AndroidAppBar 的两枚图标位 都没有事件接口,点不动。所以:原型需要真的能点的返回键 / 操作按钮 = 不要传 `title`,自己画头部。 iOS 侧不传 title 时内容区自动让出 54px 顶部内边距,你的自定义头部从那里往下画就压不到时间。

2.3 iOS 全表(./ios-frame.jsx)
组件props(= 后是默认值)
IOSDevicechildren、width=402、height=874、dark=false、title(不传=无导航区)、keyboard=false
IOSStatusBardark=false、time='9:41'
IOSNavBartitle='Title'、dark=false、trailingIcon=true
IOSGlassPillchildren、dark=false、style={}
IOSListheader、children、dark=false
IOSListRowtitle、detail、icon、chevron=true、isLast=false、dark=false
IOSKeyboarddark=false
  • IOSListRow 的 `icon` 传的是一个颜色值(画左侧 30×30 的圆角色块),不是图标名。
  • IOSList 自带 18px 上外边距与左右各 16px 内缩,别再包一层带 padding 的容器。
  • IOSGlassPill 的 style 是唯一暴露出来的毛玻璃配方(半透明底 + backdrop-filter +

高光/暗边/描边三层 inset):浮动按钮、Toast、迷你播放条、分段控件都拿它当底座,别自己配一套。 它的 children 装在一个内层 inline-flex 里,要横向铺开就给自己的那层写死宽度。

  • IOSStatusBar / IOSNavBar / IOSKeyboard / IOSGlassPill 也能脱离外框单独挂

(做尺寸放大图、系统件对照页),此时 dark 得自己传。

2.4 Android 全表(./android-frame.jsx)
组件props(= 后是默认值)
AndroidDevicechildren、width=412、height=892、dark=false、title(不传=无应用栏)、large=false、keyboard=false
AndroidStatusBardark=false、time='9:30'
AndroidAppBartitle='Title'、large=false
AndroidListItemheadline、supporting、leading
AndroidNavBardark=false
AndroidKeyboard无 props
  • large="{{ true }}" 的应用栏把标题下沉成独立一行(30px 字号),首行只剩两枚图标位;

高度约 132px,不传 large 时 64px。

  • AndroidListItem 的 leading 是放进圆形头像位的字符(primary 底 + 反色字),不传就没有头像位;

supporting 不传则是单行项(56px 高),传了变双行(72px 高)。

2.5 明暗方案:两件的下发方式不同,这是最大的用法分野

Android —— `AndroidDevice` 在根节点上下发 15 个 M3 角色变量,屏幕内容可以直接写 var(--m3-…),跟着 dark 一起翻,而且这是静态内联样式(不走空穴,流式期就有观感):

html
<div style="padding:14px 16px; background:var(--m3-secondaryContainer); color:var(--m3-onSecondaryContainer)">
这块配色跟着外框的 dark 翻
</div>

变量名大小写敏感,照抄这 15 个(角色名保持驼峰):

--m3-surface --m3-surfaceVariant --m3-inverseOnSurface --m3-secondaryContainer
--m3-onSurface --m3-onSurfaceVariant --m3-onSecondaryContainer --m3-onPrimaryContainer
--m3-primary --m3-onPrimary --m3-primaryFixedDim
--m3-outline --m3-outlineVariant --m3-scrim --m3-shadow

变量只在 AndroidDevice 子树里有定义(AndroidStatusBar / AndroidNavBar 自己也带一份); AndroidAppBar / AndroidListItem / AndroidKeyboard 没有 `dark` 形参,靠继承这些变量, 单独拿出去用会落回浅色兜底。想换主色,在设备内的任意一层写 style="--m3-primary:#7c4dff" 覆盖即可,下面的系统件跟着变。

iOS —— 没有这套。色板全部内联在组件内部,屏幕内容拿不到任何变量,配色只能自己定并手动同步。 外框内部用的就是下面这两套,照抄它,你的内容才和系统件同一套:

角色light(dark=false)dark(dark=true)
屏幕底#f2f2f7#000000
卡片 / 分组底#ffffff#1c1c1e
主文本#101014#f5f5f7
次要文本rgba(60,60,67,0.60)rgba(235,235,245,0.62)
弱化文本 / 尖角rgba(60,60,67,0.34)rgba(235,235,245,0.32)
分隔线rgba(60,60,67,0.22)rgba(235,235,245,0.18)
强调色#007aff#0a84ff

推论:iOS 侧挑一套明暗写死,不要做运行时明暗切换。 要切换就得把每处配色都走空穴, 而空穴在流式全程是空的(逻辑块未上场),整份原型在生成期间会没有样式。要明暗开关就用 Android。

2.6 几何预算(够不够放得下,先按这张表算)
内容区高度备注
iOS 传 title≈ 697px导航区约 149px(含让出的 54px 状态栏)+ 底部 28px 余量
iOS 不传 title≈ 792px顶部自动 54px 内边距 + 底部 28px 余量
iOS keyboard="{{ true }}"再减 ≈ 270px同时底部 28px 余量归零
Android 不传 title810 × 392892 − 20 边框 − 36 状态栏 − 26 手势条
Android 传 title(小应用栏)746 × 392再减 64px 应用栏
Android 传 title + large="{{ true }}"678 × 392再减 132px 应用栏
Android keyboard="{{ true }}"再减 ≈ 250px

改尺寸就得同步改 `hint-size`,否则流式期占位框和终态尺寸对不上,用户会看到跳版。 且 width / height 直接进 CSS,必须写成整值空穴:width="{{ 390 }}"(写 width="390" 是没单位的 CSS 值,会被丢掉,外框塌成内容高)。

2.7 多屏叙事
  • 一台设备内部切屏:骨架里那套——一条轨道 + transform:translateX + transition,

由 state.screen 驱动。全屏页(Now Playing 这类)从底部升起就换成 translateY。

  • 多台设备并排看流程:画板模式(dc-authoring §6)。每台设备各自装进一个绝对定位的

根直接子元素,打上 data-screen-label,宿主的逐屏截图与文本摘要按它分组。

  • 状态栏时间只在单独挂 `IOSStatusBar` / `AndroidStatusBar` 时能改(time,默认 9:41 / 9:30):

设备外框内部是写死 dark 一个参数挂上去的,time 传给 IOSDevice 不会转下去。 想让多屏叙事的时间推进,就得不用设备外框、自己拼一台。多数情况下不值得——保持默认时间即可。


3. 什么才算"可交互"

逐项自检,缺一项就还是静态 mockup:

  • 状态驱动:列表增删、选中、展开折叠、步骤指示全部走 state + setState,不摆一次性 DOM。
  • hover / active 态:可点元素给 style-hover / style-active(伪态样式,dc-authoring §2.3)。

这两个不必进 state——纯 CSS 就够,进 state 反而多一次重渲染。

  • 表单校验:<input> / <textarea> 是普通 DOM 元素,照常用。必填与格式错误即时提示,

非法时提交按钮 disabled + 灰化。

  • 过渡动画:面板切换、元素出入场写 transition / transform,不做硬切。
  • 多步导航流:向导 / tab / 路由式切换要有前进后退,且状态在步骤间保留。
  • 真键盘体验自己做:两件的 IOSKeyboard / AndroidKeyboard 都是静态外观件,

不可交互、没有任何回调,按不下去。要"打字"就在内容区用真 <input> + state, 外框的 keyboard 只负责把那块视觉占位铺出来。


4. 运行时已经替你做了什么(别写这些)

  • 整台设备的外观:圆角、描边、落地阴影、内容裁切、灵动岛 / 挖孔、状态栏(含信号 Wi-Fi 电池)、

Home Indicator、手势导航条——全部自带。

  • 内容区已经是滚动容器(flex:1 + overflow:auto):不要自己写外层滚动壳,

不要给屏幕内容写 height:874px。

  • iOS 已经替系统区让好了位:不传 title 时顶部 54px 内边距,不开 keyboard 时底部 28px 余量。
  • 两套明暗色板已经算好:不要写 @media (prefers-color-scheme),明暗由 dark prop 一处切换。
  • 毛玻璃配方已经封在 IOSGlassPill 与 IOSKeyboard 里:不要自己调 backdrop-filter。
  • 导出降级已经想过:backdrop-filter 在 headless 截图 / PDF 里可能不合成,底色本身撑得住可读性——

不用为导出另写一套替代样式。

  • 平台字体栈铺在外框根节点上:屏幕内容不写 font-family 就继承(iOS 一套、Android 一套)。

字体口径照 dc-authoring §7,不拉网络字体。

  • 全部图标是内联 SVG 自绘,零外部资源:离线、导出、大陆网络下都在。
  • 外框文件由宿主内联(from="./ios-frame.jsx" 就够),不走网络、不用自己 fetch。
  • 自检卡按内容挑外框:产物里出现 ios-frame / IOSDevice 时,宿主的自检预览卡自动用

402×874 的手机比例展示——你不用为它做任何事。


5. 约束与静默失败(不知道就会踩,踩了不报错)

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 96 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/yuanlang12/openpipal/interactive-prototype/badge)](https://verified-skill.com/skills/yuanlang12/openpipal/interactive-prototype)

Something wrong with this skill or its page? Tell us.