mirror of
https://github.com/tt-a1i/archify.git
synced 2026-09-28 23:16:23 +08:00
523 lines
48 KiB
HTML
Generated
523 lines
48 KiB
HTML
Generated
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Archify Start — Describe It, Then See It</title>
|
||
<meta name="description" content="Create a verifiable technical diagram directly from a plain-language description, pasted Mermaid, or a repository. No repository is required.">
|
||
<meta name="theme-color" content="#eff2f1">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:title" content="Archify Start — Describe it, then see it">
|
||
<meta property="og:description" content="Start with a plain-language description, pasted Mermaid, or real code. No repository is required.">
|
||
<meta property="og:url" content="https://tt-a1i.github.io/archify/start.html">
|
||
<meta property="og:image" content="https://tt-a1i.github.io/archify/assets/archify-social-preview.png">
|
||
<link rel="icon" type="image/svg+xml" href="assets/archify-mark.svg">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,500;1,9..144,400&family=Inter:wght@400;450;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/site-navigation.css">
|
||
<style>
|
||
:root {
|
||
/* Drafted-sheet palette — shared with the project page */
|
||
--paper:#eff2f1; --paper-raised:#f8faf9;
|
||
--ink:#15181b; --ink-soft:#363c41; --muted:#60686d; --dim:#929a9e;
|
||
--line:rgba(20,23,26,0.14); --line-hi:rgba(20,23,26,0.3);
|
||
--accent:#e14a0e; --accent-soft:rgba(225,74,14,0.08);
|
||
--plate:#0f1216;
|
||
--emerald:#047857;
|
||
--font-display:'Fraunces','Songti SC','STSong',Georgia,serif;
|
||
--font-body:'Inter',-apple-system,BlinkMacSystemFont,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;
|
||
--font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
|
||
--ease-out:cubic-bezier(.22,1,.36,1);
|
||
}
|
||
|
||
*,*::before,*::after { margin:0; padding:0; box-sizing:border-box; }
|
||
html { scroll-behavior:smooth; }
|
||
body {
|
||
font-family:var(--font-body);
|
||
-webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
|
||
background:var(--paper); color:var(--ink); min-height:100vh; overflow-x:hidden;
|
||
}
|
||
body::after {
|
||
content:''; position:fixed; inset:0; z-index:1000; pointer-events:none;
|
||
opacity:.05; mix-blend-mode:multiply;
|
||
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
|
||
}
|
||
::selection { background:rgba(225,74,14,.18); color:var(--ink); }
|
||
a { color:inherit; }
|
||
button { font:inherit; }
|
||
:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:4px; }
|
||
|
||
.grid-bg {
|
||
position:fixed; inset:0; z-index:0; pointer-events:none;
|
||
background-image:linear-gradient(rgba(20,23,26,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(20,23,26,.045) 1px,transparent 1px);
|
||
background-size:56px 56px;
|
||
-webkit-mask-image:radial-gradient(ellipse 95% 62% at 50% -6%, black 12%, transparent 75%);
|
||
mask-image:radial-gradient(ellipse 95% 62% at 50% -6%, black 12%, transparent 75%);
|
||
}
|
||
|
||
main { position:relative; z-index:1; width:min(1120px,calc(100% - 3rem)); margin:0 auto; padding:10rem 0 4rem; }
|
||
|
||
/* LEAD */
|
||
.lead { display:grid; grid-template-columns:minmax(0,1.5fr) minmax(260px,.65fr); gap:4rem; align-items:end; margin-bottom:3.5rem; }
|
||
.lead-label { display:flex; align-items:center; gap:.875rem; margin-bottom:1.5rem; font-family:var(--font-mono); font-size:.625rem; font-weight:500; letter-spacing:.2em; color:var(--muted); text-transform:uppercase; }
|
||
.lead-label::before { content:''; width:2.75rem; height:1px; background:var(--line-hi); }
|
||
h1 { max-width:780px; font-family:var(--font-display); font-optical-sizing:auto; font-size:clamp(2.5rem,5vw,4.25rem); font-weight:400; line-height:1.06; letter-spacing:-.028em; color:var(--ink); text-wrap:balance; }
|
||
h1 em { font-style:italic; color:var(--accent); }
|
||
html[lang^="zh"] .title-gap { display:none; }
|
||
.lead-copy { color:var(--muted); font-size:.9375rem; line-height:1.72; text-wrap:pretty; }
|
||
.trust-line { display:flex; gap:.6rem; align-items:flex-start; margin-top:1.125rem; color:var(--emerald); font-family:var(--font-mono); font-size:.6875rem; line-height:1.65; }
|
||
.trust-line::before { content:'✓'; flex:0 0 auto; }
|
||
|
||
/* INPUT PATH — make the no-repository path explicit before diagram choices */
|
||
.input-paths { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.625rem; margin-bottom:1.25rem; }
|
||
.input-path {
|
||
min-height:72px; padding:.85rem 1rem; text-align:left;
|
||
border:1px solid var(--line); border-radius:10px; background:var(--paper);
|
||
color:var(--muted); cursor:pointer; transition:all .18s var(--ease-out);
|
||
}
|
||
.input-path:hover { color:var(--ink); border-color:var(--line-hi); }
|
||
.input-path strong { display:block; margin-bottom:.3rem; color:inherit; font-size:.8125rem; font-weight:600; }
|
||
.input-path span { display:block; font-size:.6875rem; line-height:1.5; }
|
||
.input-path[aria-selected="true"] { border-color:var(--accent); background:var(--accent-soft); color:var(--ink); box-shadow:inset 3px 0 0 var(--accent); }
|
||
|
||
/* TYPE TABS */
|
||
.type-shell { margin-bottom:var(--bento-gap,14px); padding:1.25rem 1.5rem 1.5rem; border:1px solid var(--line-hi); border-radius:14px; background:var(--paper-raised); }
|
||
.type-label { display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:1rem; color:var(--dim); font-family:var(--font-mono); font-size:.625rem; letter-spacing:.16em; text-transform:uppercase; }
|
||
.type-label strong { color:var(--accent); font-weight:600; }
|
||
.type-tabs { display:grid; grid-template-columns:repeat(5,1fr); gap:1px; border:1px solid var(--line); border-radius:10px; overflow:hidden; background:var(--line); }
|
||
.type-tab {
|
||
min-height:52px;
|
||
border:0;
|
||
background:var(--paper-raised);
|
||
color:var(--muted);
|
||
cursor:pointer;
|
||
font-family:var(--font-mono); font-size:.6875rem; font-weight:500; letter-spacing:.06em;
|
||
transition:background .18s var(--ease-out),color .18s var(--ease-out);
|
||
}
|
||
.type-tab:hover { background:var(--paper); color:var(--ink); }
|
||
.type-tab[aria-selected="true"] { background:var(--ink); color:var(--paper); }
|
||
|
||
/* WORKBENCH — two paper cards */
|
||
.workbench { display:grid; grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr); gap:14px; align-items:stretch; }
|
||
.panel { min-width:0; padding:2.25rem 2.5rem; border:1px solid var(--line-hi); border-radius:14px; background:var(--paper-raised); }
|
||
.panel-kicker { display:flex; align-items:center; gap:.875rem; margin-bottom:1.5rem; color:var(--dim); font-family:var(--font-mono); font-size:.625rem; letter-spacing:.16em; text-transform:uppercase; }
|
||
.panel-kicker b { font-family:var(--font-display); font-style:italic; font-optical-sizing:auto; font-weight:400; font-size:1.375rem; color:var(--accent); }
|
||
.panel h2 { margin-bottom:.625rem; font-family:var(--font-display); font-optical-sizing:auto; font-size:1.5rem; font-weight:500; letter-spacing:-.02em; color:var(--ink); }
|
||
.panel-intro { min-height:3.9rem; margin-bottom:1.5rem; color:var(--muted); font-size:.8125rem; line-height:1.7; text-wrap:pretty; }
|
||
|
||
.agent-picker { margin-bottom:1.25rem; }
|
||
.agent-picker-head { display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:.75rem; color:var(--muted); font-family:var(--font-mono); font-size:.625rem; letter-spacing:.1em; text-transform:uppercase; line-height:1.45; }
|
||
.agent-picker-head strong { color:var(--emerald); font-weight:600; }
|
||
.agent-tabs { display:flex; flex-wrap:wrap; gap:.45rem; }
|
||
.agent-tab {
|
||
min-height:44px;
|
||
padding:.55rem .85rem;
|
||
border:1px solid var(--line-hi);
|
||
border-radius:8px;
|
||
background:transparent;
|
||
color:var(--muted);
|
||
cursor:pointer;
|
||
font-family:var(--font-mono); font-size:.625rem; font-weight:500; letter-spacing:.06em;
|
||
transition:all .18s var(--ease-out);
|
||
}
|
||
.agent-tab:hover { color:var(--ink); border-color:var(--ink); }
|
||
.agent-tab[aria-selected="true"] { color:var(--paper); border-color:var(--ink); background:var(--ink); }
|
||
|
||
/* COMMAND BLOCKS — dark plate on paper, like the project page install strip */
|
||
.command-block { margin-top:.875rem; border:1px solid var(--ink); border-radius:10px; overflow:hidden; background:var(--plate); }
|
||
.command-bar { display:flex; align-items:center; justify-content:space-between; gap:1rem; padding:.6rem .875rem; border-bottom:1px solid rgba(232,237,242,.1); color:#7d8894; font-family:var(--font-mono); font-size:.5625rem; text-transform:uppercase; letter-spacing:.14em; }
|
||
.copy-button { border:0; background:transparent; color:var(--accent); cursor:pointer; font-family:var(--font-mono); font-size:.5625rem; font-weight:600; text-transform:uppercase; letter-spacing:.12em; transition:color .18s var(--ease-out); }
|
||
.copy-button:hover { color:#ff7a3d; }
|
||
pre { margin:0; padding:.9rem 1rem; overflow:auto; color:#e8edf2; font:500 .6875rem/1.7 var(--font-mono); white-space:pre-wrap; overflow-wrap:anywhere; }
|
||
.secondary-note { margin:1rem 0 0; padding:.9rem 1rem; border:1px solid var(--line); border-radius:10px; background:var(--paper); color:var(--muted); font-size:.75rem; line-height:1.7; }
|
||
|
||
.question { margin-bottom:1rem; color:var(--ink-soft); font-size:.9375rem; font-weight:500; line-height:1.6; }
|
||
.include-list { display:flex; flex-wrap:wrap; gap:.45rem; margin-bottom:1.25rem; list-style:none; }
|
||
.include-list li { padding:.35rem .65rem; border:1px solid var(--line); border-radius:999px; color:var(--muted); font-family:var(--font-mono); font-size:.5625rem; letter-spacing:.06em; background:var(--paper); }
|
||
.prompt-box { min-height:172px; padding:1.15rem 1.25rem; border:1px solid rgba(225,74,14,.3); border-radius:10px; background:var(--accent-soft); color:var(--ink-soft); font-family:var(--font-mono); font-size:.75rem; line-height:1.8; white-space:pre-wrap; }
|
||
.actions { display:flex; align-items:center; flex-wrap:wrap; gap:.625rem; margin-top:1.25rem; }
|
||
.action { min-height:44px; display:inline-flex; align-items:center; justify-content:center; padding:.65rem 1.05rem; border:1px solid var(--line-hi); border-radius:8px; background:transparent; color:var(--ink-soft); font:500 .8125rem var(--font-body); text-decoration:none; cursor:pointer; transition:all .2s var(--ease-out); }
|
||
.action:hover { color:var(--ink); border-color:var(--ink); }
|
||
.action-primary { border-color:var(--ink); background:var(--ink); color:var(--paper); }
|
||
.action-primary:hover { background:var(--accent); border-color:var(--accent); color:var(--paper); }
|
||
.proof-meta { margin-top:1.1rem; color:var(--dim); font-family:var(--font-mono); font-size:.5625rem; letter-spacing:.08em; line-height:1.65; text-transform:uppercase; }
|
||
|
||
.page-footer { display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-top:3.5rem; padding-top:1.5rem; border-top:1px solid var(--line); color:var(--dim); font-family:var(--font-mono); font-size:.625rem; letter-spacing:.04em; }
|
||
.page-footer a { color:var(--muted); text-decoration:none; transition:color .18s var(--ease-out); }
|
||
.page-footer a:hover { color:var(--ink); }
|
||
#copy-status { min-height:1.1rem; color:var(--emerald); }
|
||
|
||
/* ENTRANCE — one quiet rise on load */
|
||
@keyframes rise { from { opacity:0; transform:translateY(14px); } to { opacity:1; transform:none; } }
|
||
.lead, .type-shell, .workbench { animation:rise .7s var(--ease-out) both; }
|
||
.type-shell { animation-delay:.08s; }
|
||
.workbench { animation-delay:.16s; }
|
||
|
||
@media (max-width:840px) {
|
||
main { width:min(100% - 2rem,720px); padding-top:8rem; }
|
||
.lead { grid-template-columns:1fr; gap:1.5rem; margin-bottom:2.5rem; }
|
||
.workbench { grid-template-columns:1fr; }
|
||
.type-tabs { grid-template-columns:repeat(2,1fr); }
|
||
.input-paths { grid-template-columns:1fr; }
|
||
.type-tab:last-child { grid-column:1 / -1; }
|
||
}
|
||
@media (max-width:560px) {
|
||
main { width:calc(100% - 1.5rem); padding-top:7rem; }
|
||
h1 { font-size:2.2rem; }
|
||
.panel { padding:1.5rem 1.25rem; }
|
||
.type-shell { padding:1rem 1rem 1.25rem; }
|
||
.type-tabs { grid-template-columns:1fr; }
|
||
.type-tab:last-child { grid-column:auto; }
|
||
.page-footer { align-items:flex-start; flex-direction:column; }
|
||
}
|
||
@media (prefers-reduced-motion:reduce) {
|
||
html { scroll-behavior:auto; }
|
||
.lead, .type-shell, .workbench { animation:none; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="grid-bg" aria-hidden="true"></div>
|
||
|
||
<nav class="site-nav" aria-label="Primary navigation">
|
||
<a class="nav-logo" href="./" aria-label="Archify project page">
|
||
<img class="nav-logo-text brand-lockup" src="assets/archify-lockup-light.svg" alt="Archify" width="117" height="32">
|
||
<span class="nav-logo-path" data-en="/ start" data-zh="/ 快速上手">/ start</span>
|
||
</a>
|
||
<div class="nav-right">
|
||
<a class="nav-link" href="guide.html" data-en="Guide" data-zh="场景指南">Guide</a>
|
||
<a class="nav-link" href="gallery.html" data-en="Proof Lab" data-zh="验证作品集">Proof Lab</a>
|
||
<a class="nav-link" href="start.html" aria-current="page" data-en="Start" data-zh="快速上手">Start</a>
|
||
<a class="nav-link" href="https://github.com/tt-a1i/archify" target="_blank" rel="noopener">GitHub</a>
|
||
<button class="btn-lang" id="language" type="button" aria-label="Switch language">中文</button>
|
||
<a class="btn btn-primary nav-cta" href="#install" data-en="Install Skill" data-zh="安装技能">Install Skill</a>
|
||
</div>
|
||
</nav>
|
||
|
||
<main>
|
||
<header class="lead">
|
||
<div>
|
||
<p class="lead-label" data-en="Start with words, Mermaid, or code" data-zh="从描述、Mermaid 或代码开始">Start with words, Mermaid, or code</p>
|
||
<h1><span data-en="Describe it." data-zh="直接说,">Describe it.</span><span class="title-gap" aria-hidden="true"> </span><em data-en="Archify maps it." data-zh="Archify 就能画。">Archify maps it.</em></h1>
|
||
</div>
|
||
<div>
|
||
<p class="lead-copy" data-en="No repository is required. Describe a system or flow in a normal agent chat and Archify will turn it into a checked, interactive diagram. If code exists, the same skill can inspect it for evidence." data-zh="不需要绑定代码库。在任意 Agent 对话里用自然语言描述系统或流程,Archify 就会把它变成经过校验的交互图;如果有代码,也可以再让它读取仓库作为证据。">No repository is required. Describe a system or flow in a normal agent chat and Archify will turn it into a checked, interactive diagram. If code exists, the same skill can inspect it for evidence.</p>
|
||
<div class="trust-line" data-en="Your description is enough to make the first diagram." data-zh="只有一段描述,也足够画出第一张图。">Your description is enough to make the first diagram.</div>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="type-shell" aria-labelledby="type-label">
|
||
<p class="type-label"><span data-en="Choose how to start" data-zh="选择如何开始">Choose how to start</span><strong data-en="Repository optional" data-zh="代码库可选">Repository optional</strong></p>
|
||
<div class="input-paths" role="tablist" aria-label="Input source">
|
||
<button class="input-path" type="button" role="tab" data-input="description"><strong data-en="Just describe it" data-zh="直接描述">直接描述</strong><span data-en="Start in any chat. No files or repository needed." data-zh="在任意对话里开始,不需要文件或代码库。">Start in any chat. No files or repository needed.</span></button>
|
||
<button class="input-path" type="button" role="tab" data-input="repository"><strong data-en="Use a repository" data-zh="从代码库开始">Use a repository</strong><span data-en="Let the agent inspect code when source evidence matters." data-zh="需要源码证据时,再让 Agent 检查代码。">Let the agent inspect code when source evidence matters.</span></button>
|
||
</div>
|
||
<p class="type-label" id="type-label"><span data-en="Choose the question shape" data-zh="选择问题形态">Choose the question shape</span><strong data-en="5 typed renderers" data-zh="5 种类型化渲染器">5 typed renderers</strong></p>
|
||
<div class="type-tabs" role="tablist" aria-label="Diagram type">
|
||
<button class="type-tab" type="button" role="tab" data-type="architecture" data-en="Architecture" data-zh="架构图">Architecture</button>
|
||
<button class="type-tab" type="button" role="tab" data-type="workflow" data-en="Workflow" data-zh="工作流">Workflow</button>
|
||
<button class="type-tab" type="button" role="tab" data-type="sequence" data-en="Sequence" data-zh="时序图">Sequence</button>
|
||
<button class="type-tab" type="button" role="tab" data-type="dataflow" data-en="Data flow" data-zh="数据流">Data flow</button>
|
||
<button class="type-tab" type="button" role="tab" data-type="lifecycle" data-en="Lifecycle" data-zh="生命周期">Lifecycle</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="workbench" aria-label="Archify quick start">
|
||
<article class="panel" id="install">
|
||
<div class="panel-kicker"><b>01</b><span data-en="Install the skill" data-zh="安装 Skill">Install the skill</span></div>
|
||
<h2 data-en="Keep the tool local." data-zh="让工具留在本地。">Keep the tool local.</h2>
|
||
<p class="panel-intro" data-en="Choose Cursor, Codex, Claude Code, OpenCode, or GitHub Copilot. The switcher gives each one the same checked Skill and zero-dependency renderers—no vendor-specific fork." data-zh="选择 Cursor、Codex、Claude Code、OpenCode 或 GitHub Copilot。切换器为每个 Agent 提供同一份经过检查的 Skill 与零依赖渲染器,不维护厂商专属分叉。">Choose Cursor, Codex, Claude Code, OpenCode, or GitHub Copilot. The switcher gives each one the same checked Skill and zero-dependency renderers—no vendor-specific fork.</p>
|
||
|
||
<div class="agent-picker">
|
||
<p class="agent-picker-head"><span data-en="Choose your coding agent" data-zh="选择你的编码 Agent">Choose your coding agent</span><strong id="agent-state">Codex · same Skill</strong></p>
|
||
<div class="agent-tabs" role="tablist" aria-label="Coding agent">
|
||
<button class="agent-tab" type="button" role="tab" data-agent="cursor">Cursor</button>
|
||
<button class="agent-tab" type="button" role="tab" data-agent="codex">Codex</button>
|
||
<button class="agent-tab" type="button" role="tab" data-agent="claude-code">Claude Code</button>
|
||
<button class="agent-tab" type="button" role="tab" data-agent="opencode">OpenCode</button>
|
||
<button class="agent-tab" type="button" role="tab" data-agent="github-copilot">GitHub Copilot</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="command-block">
|
||
<div class="command-bar"><span data-en="Global install" data-zh="全局安装">Global install</span><button class="copy-button" type="button" data-copy-source="install-command" data-en="Copy" data-zh="复制">Copy</button></div>
|
||
<pre id="install-command">npx -y skills add tt-a1i/archify --skill archify --agent codex --global --copy --yes</pre>
|
||
</div>
|
||
<div class="command-block">
|
||
<div class="command-bar"><span data-en="This repository only" data-zh="仅安装到当前仓库">This repository only</span><button class="copy-button" type="button" data-copy-source="project-command" data-en="Copy" data-zh="复制">Copy</button></div>
|
||
<pre id="project-command">npx -y skills add tt-a1i/archify --skill archify --agent codex --copy --yes</pre>
|
||
</div>
|
||
<p class="secondary-note" data-en="The Start link carries only your agent, language, diagram type, input mode, and a coarse source label. Journey events stay in this browser session and are never sent by Archify. No repository path, title, node, relationship, or source JSON is recorded." data-zh="Start 链接只携带 Agent、语言、图表类型、输入模式和粗粒度来源标签。转化步骤只保存在当前浏览器会话中,Archify 不会发送这些数据,也不会记录仓库路径、标题、节点、关系或源 JSON。">The Start link carries only your agent, language, diagram type, input mode, and a coarse source label. Journey events stay in this browser session and are never sent by Archify. No repository path, title, node, relationship, or source JSON is recorded.</p>
|
||
</article>
|
||
|
||
<article class="panel" aria-live="polite">
|
||
<div class="panel-kicker"><b>02</b><span data-en="Ask one bounded question" data-zh="只问一个有边界的问题">Ask one bounded question</span></div>
|
||
<h2 id="recipe-title">System overview</h2>
|
||
<p class="question" id="recipe-question"></p>
|
||
<ul class="include-list" id="include-list" aria-label="Required evidence"></ul>
|
||
<p class="prompt-box" id="recipe-prompt"></p>
|
||
<div class="actions">
|
||
<button class="action action-primary" id="copy-starter" type="button" data-en="Copy install + prompt" data-zh="复制安装命令 + 提示词">Copy install + prompt</button>
|
||
<button class="action" id="copy-prompt" type="button" data-en="Prompt only" data-zh="仅复制提示词">Prompt only</button>
|
||
<a class="action" id="proof-link" href="gallery.html" data-en="Open verified proof ↗" data-zh="打开验证成品 ↗">Open verified proof ↗</a>
|
||
<a class="action" href="guide.html" data-en="Need another recipe?" data-zh="需要其他配方?">Need another recipe?</a>
|
||
</div>
|
||
<p class="proof-meta" id="proof-meta"></p>
|
||
</article>
|
||
</section>
|
||
|
||
<footer class="page-footer">
|
||
<span data-en="Archify v3.0.0 · stable · self-contained HTML · typed JSON source" data-zh="Archify v3.0.0 · 稳定版 · 独立 HTML · 类型化 JSON 源">Archify v3.0.0 · stable · self-contained HTML · typed JSON source</span>
|
||
<span id="copy-status" role="status" aria-live="polite"></span>
|
||
<a href="https://github.com/tt-a1i/archify" data-en="View source on GitHub ↗" data-zh="在 GitHub 查看源码 ↗">View source on GitHub ↗</a>
|
||
</footer>
|
||
</main>
|
||
|
||
<script id="start-data" type="application/json">{"architecture":{"id":"system-overview","type":"architecture","proof":"web-app","presentation":{"preset":"classic","motion":"static"},"en":{"title":"System overview","question":"What exists, who owns it, and how is it connected?","summary":"A bounded map of core components, external dependencies, primary paths, and trust boundaries.","useWhen":"Onboarding, design reviews, repository orientation, or explaining a service landscape.","avoidWhen":"The audience needs exact call order, state transitions, or row-level data lineage.","include":["8–12 core components","one primary path","external dependencies","trust boundaries"],"prompt":"Analyze this repository, then use Archify to create a high-level architecture diagram. Show 8–12 core runtime components, one primary request or data path, external dependencies, ownership or trust boundaries, and put supporting detail in cards instead of adding more edges.","descriptionPrompt":"Use Archify to turn this plain-language system description into a high-level architecture diagram: [describe the users, core components, primary path, external dependencies, and boundaries]. No repository is required. Ask only for missing facts that would materially change the diagram, mark any remaining unknowns instead of inventing them, and keep one obvious primary path across 8–12 core components.","repositoryPrompt":"Analyze this repository, then use Archify to create a high-level architecture diagram. Show 8–12 core runtime components, one primary request or data path, external dependencies, ownership or trust boundaries, and put supporting detail in cards instead of adding more edges."},"zh":{"title":"系统总览","question":"系统里有什么、归谁负责、彼此如何连接?","summary":"用一张有边界的图展示核心组件、外部依赖、主路径和信任边界。","useWhen":"适合新人上手、方案评审、仓库梳理和服务全景说明。","avoidWhen":"如果重点是精确调用顺序、状态流转或字段级血缘,请换其他配方。","include":["8–12 个核心组件","一条主路径","外部依赖","归属或信任边界"],"prompt":"分析这个仓库,然后用 Archify 生成高层系统架构图。展示 8–12 个核心运行时组件、一条主要请求或数据路径、外部依赖、归属或信任边界;支持性细节放进卡片,不要继续堆连线。","descriptionPrompt":"用 Archify 把下面这段自然语言系统描述画成高层架构图:[在这里描述用户、核心组件、主要路径、外部依赖和边界]。不需要代码库。只追问会实质影响图的缺失信息,其余不确定内容要标明而不是编造;保留 8–12 个核心组件和一条一眼可见的主路径。","repositoryPrompt":"分析这个仓库,然后用 Archify 生成高层系统架构图。展示 8–12 个核心运行时组件、一条主要请求或数据路径、外部依赖、归属或信任边界;支持性细节放进卡片,不要继续堆连线。"}},"workflow":{"id":"agent-tool-call","type":"workflow","proof":"agent-tool-call","presentation":{"preset":"signal-flow","motion":"trace"},"en":{"title":"Agent tool-call loop","question":"How does an agent plan, get permission, act, recover, and report?","summary":"A lane-based agent loop with policy gates, tool execution, exception recovery, evidence, and final response.","useWhen":"Explaining agent runtimes, MCP/tool orchestration, approvals, retries, or observability.","avoidWhen":"The goal is only to show static agent components or exact API message timing.","include":["request and planning","policy or approval gate","tool execution","exception and evidence paths"],"prompt":"Use Archify workflow mode to explain this agent tool-call loop. Separate user surface, agent runtime, policy boundary, exception handling, tool execution, and observability into lanes. Make the successful path primary and show approval, retry, blocked, and evidence paths explicitly.","descriptionPrompt":"Use Archify workflow mode to turn this description into a diagram: [paste the actors, main steps, decisions, approvals, and exception paths]. Use lanes for distinct owners, keep one unmistakable happy path, and mark missing ownership or unresolved branches instead of inventing them.","repositoryPrompt":"Inspect this repository for evidence, then use Archify workflow mode to explain this agent tool-call loop. Separate user surface, agent runtime, policy boundary, exception handling, tool execution, and observability into lanes. Make the successful path primary and show approval, retry, blocked, and evidence paths explicitly. Do not invent behavior that the code does not support."},"zh":{"title":"智能体工具调用","question":"智能体如何规划、获批、执行、恢复并汇报?","summary":"用泳道表达策略门、工具执行、异常恢复、证据和最终回复。","useWhen":"适合解释 Agent Runtime、MCP/工具编排、审批、重试和可观测性。","avoidWhen":"如果只想看静态组件,或重点是精确 API 消息时序,请换其他配方。","include":["请求与规划","策略或审批门","工具执行","异常与证据路径"],"prompt":"用 Archify 工作流模式解释这段智能体工具调用。把用户界面、Agent Runtime、策略边界、异常处理、工具执行和可观测性分成泳道;突出成功主路径,并明确展示审批、重试、阻塞和证据路径。","descriptionPrompt":"用 Archify 工作流模式把下面的描述画成图:[粘贴参与者、主要步骤、决策、审批和异常路径]。不同负责方使用独立泳道,保留一条明确的成功主路径,缺失的负责人或未定分支要标明而不是编造。","repositoryPrompt":"先检查这个仓库里的相关证据,然后用 Archify 工作流模式解释这段智能体工具调用。把用户界面、Agent Runtime、策略边界、异常处理、工具执行和可观测性分成泳道;突出成功主路径,并明确展示审批、重试、阻塞和证据路径。不要编造代码无法支持的行为。"}},"sequence":{"id":"api-request","type":"sequence","proof":"cache-miss","presentation":{"preset":"classic","motion":"trace"},"en":{"title":"API request chain","question":"Who calls whom, in what order, and what returns?","summary":"A time-ordered request path with authentication, cache fallback, persistence, return traffic, and async trace.","useWhen":"API documentation, debugging request latency, auth reviews, or explaining cache fallback.","avoidWhen":"Order is unimportant and the audience only needs the stable service topology.","include":["callers and callees","request and return messages","fallback or error path","async side effects"],"prompt":"Use Archify sequence mode to show this request from caller to final response. Include authentication, cache hit or miss, persistence fallback, return messages, and asynchronous trace or event emission. Keep message labels short and order unambiguous.","descriptionPrompt":"Use Archify sequence mode to draw this interaction: [paste the participants, calls, returns, fallback, and asynchronous side effects]. Keep message order unambiguous, labels short, and unknown behavior explicit. No repository is required.","repositoryPrompt":"Inspect this repository for evidence, then use Archify sequence mode to show this request from caller to final response. Include authentication, cache hit or miss, persistence fallback, return messages, and asynchronous trace or event emission. Keep message labels short and order unambiguous. Do not invent behavior that the code does not support."},"zh":{"title":"API 请求链","question":"谁调用谁、顺序如何、最终返回什么?","summary":"按时间展示鉴权、缓存回退、持久化、返回流量和异步追踪。","useWhen":"适合 API 文档、请求耗时排查、鉴权评审和缓存回退说明。","avoidWhen":"如果顺序不重要,受众只需要稳定的服务拓扑,请用架构图。","include":["调用方与被调用方","请求与返回消息","回退或错误路径","异步副作用"],"prompt":"用 Archify 时序模式展示从调用方到最终响应的完整请求。包含鉴权、缓存命中或未命中、持久化回退、返回消息,以及异步 Trace 或事件上报;消息标签保持简短,顺序必须明确。","descriptionPrompt":"用 Archify 时序模式绘制下面的交互:[粘贴参与者、调用、返回、回退和异步副作用]。确保消息顺序无歧义、标签简短,并明确标注未知行为。不需要代码库。","repositoryPrompt":"先检查这个仓库里的相关证据,然后用 Archify 时序模式展示从调用方到最终响应的完整请求。包含鉴权、缓存命中或未命中、持久化回退、返回消息,以及异步 Trace 或事件上报;消息标签保持简短,顺序必须明确。不要编造代码无法支持的行为。"}},"dataflow":{"id":"event-stream","type":"dataflow","proof":"event-stream","presentation":{"preset":"signal-flow","motion":"trace"},"en":{"title":"Event-stream topology","question":"Which events move through which topics, processors, groups, and failure paths?","summary":"A stream map of producers, topics, ordered processors, consumer groups, state, replay, and DLQ.","useWhen":"Kafka/event-platform design, stream processing reviews, ownership, replay, and failure handling.","avoidWhen":"Topic names, consumer groups, and delivery semantics are not known—use a generic workflow instead.","include":["producers and event names","topics and ordering","processors and consumer groups","state, replay, and DLQ"],"prompt":"Use Archify dataflow mode to draw this event-stream topology. Name producers, events, topics, ordered processors, consumer groups, state stores, replay paths, and the DLQ. Show ownership and delivery semantics only when supported by evidence.","descriptionPrompt":"Use Archify dataflow mode to map this data journey: [paste the sources, data assets, transforms, stores, boundaries, and consumers]. Label every flow, distinguish streaming from batch where relevant, and mark unknown classifications or ownership instead of inventing them.","repositoryPrompt":"Inspect this repository for evidence, then use Archify dataflow mode to draw this event-stream topology. Name producers, events, topics, ordered processors, consumer groups, state stores, replay paths, and the DLQ. Show ownership and delivery semantics only when supported by evidence. Do not invent behavior that the code does not support."},"zh":{"title":"事件流拓扑","question":"哪些事件经过哪些 Topic、处理器、消费者组和失败路径?","summary":"展示生产者、Topic、有序处理器、消费者组、状态、重放和 DLQ。","useWhen":"适合 Kafka/事件平台设计、流处理评审、归属、重放和失败处理。","avoidWhen":"如果 Topic、消费者组和投递语义都不清楚,请先用通用工作流,不要编造事件拓扑。","include":["生产者与事件名","Topic 与顺序","处理器与消费者组","状态、重放与 DLQ"],"prompt":"用 Archify 数据流模式绘制这段事件流拓扑。命名生产者、事件、Topic、有序处理器、消费者组、状态存储、重放路径和 DLQ;只有在证据充分时才标注归属和投递语义。","descriptionPrompt":"用 Archify 数据流模式梳理下面的数据路径:[粘贴来源、数据资产、转换、存储、边界和消费者]。为每条数据流标注名称,在有意义时区分流式与批处理,未知的分类或归属要标明而不是编造。","repositoryPrompt":"先检查这个仓库里的相关证据,然后用 Archify 数据流模式绘制这段事件流拓扑。命名生产者、事件、Topic、有序处理器、消费者组、状态存储、重放路径和 DLQ;只有在证据充分时才标注归属和投递语义。不要编造代码无法支持的行为。"}},"lifecycle":{"id":"object-lifecycle","type":"lifecycle","proof":"agent-run","presentation":{"preset":"classic","motion":"trace"},"en":{"title":"Object lifecycle","question":"Which states exist, what events move between them, and how does it end?","summary":"A state model with active work, waits, retries, cancellation, failure, and explicit terminal outcomes.","useWhen":"Tasks, orders, tickets, subscriptions, jobs, agent runs, or any durable object with status.","avoidWhen":"The object has no durable state and the real question is participant interaction over time.","include":["start and active states","event-labelled transitions","wait and retry states","all terminal outcomes"],"prompt":"Use Archify lifecycle mode to model this object. Separate main progress, waiting or interruption states, and terminal outcomes. Label transitions with events, include retry, cancellation, timeout, success, and failure where real, and never hide an ending.","descriptionPrompt":"Use Archify lifecycle mode to model this object: [paste its states, transition events, waits, retries, cancellation, and terminal outcomes]. Separate active, waiting, recoverable-failure, and terminal states, and never hide an ending. No repository is required.","repositoryPrompt":"Inspect this repository for evidence, then use Archify lifecycle mode to model this object. Separate main progress, waiting or interruption states, and terminal outcomes. Label transitions with events, include retry, cancellation, timeout, success, and failure where real, and never hide an ending. Do not invent behavior that the code does not support."},"zh":{"title":"对象生命周期","question":"有哪些状态、什么事件触发流转、最终如何结束?","summary":"展示执行、等待、重试、取消、失败以及明确终态的状态模型。","useWhen":"适合任务、订单、工单、订阅、作业、Agent Run 等带持久状态的对象。","avoidWhen":"对象没有持久状态,真正问题是参与者随时间的交互时,请使用时序图。","include":["开始与执行态","带事件的转换","等待与重试态","所有终态"],"prompt":"用 Archify 生命周期模式建模这个对象。分开主进度、等待或中断状态和终态;用事件标注转换,并在真实存在时展示重试、取消、超时、成功和失败,不能隐藏任何结束方式。","descriptionPrompt":"用 Archify 生命周期模式建模这个对象:[粘贴它的状态、转换事件、等待、重试、取消和终态]。分开执行、等待、可恢复失败和终态,不要隐藏任何结束方式。不需要代码库。","repositoryPrompt":"先检查这个仓库里的相关证据,然后用 Archify 生命周期模式建模这个对象。分开主进度、等待或中断状态和终态;用事件标注转换,并在真实存在时展示重试、取消、超时、成功和失败,不能隐藏任何结束方式。不要编造代码无法支持的行为。"}}}</script>
|
||
<script src="assets/site-language.js"></script>
|
||
<script>
|
||
(function () {
|
||
'use strict';
|
||
|
||
var DATA = JSON.parse(document.getElementById('start-data').textContent);
|
||
var KNOWN_TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);
|
||
var KNOWN_AGENTS = new Set(['cursor', 'codex', 'claude-code', 'opencode', 'github-copilot']);
|
||
var KNOWN_INPUTS = new Set(['description', 'repository']);
|
||
var KNOWN_SOURCES = new Set(['artifact', 'gallery', 'readme', 'direct']);
|
||
var EVENT_KEY = 'archify.start.events.v1';
|
||
var params = new URLSearchParams(window.location.search);
|
||
var requestedType = params.get('type');
|
||
var requestedAgent = params.get('agent');
|
||
var requestedSource = params.get('source');
|
||
var requestedInput = params.get('input');
|
||
var type = KNOWN_TYPES.has(requestedType) ? requestedType : 'architecture';
|
||
var agent = KNOWN_AGENTS.has(requestedAgent) ? requestedAgent : 'codex';
|
||
var source = KNOWN_SOURCES.has(requestedSource) ? requestedSource : 'direct';
|
||
var input = KNOWN_INPUTS.has(requestedInput) ? requestedInput : 'description';
|
||
var language = ArchifySiteLanguage.read();
|
||
|
||
var title = document.getElementById('recipe-title');
|
||
var question = document.getElementById('recipe-question');
|
||
var prompt = document.getElementById('recipe-prompt');
|
||
var includeList = document.getElementById('include-list');
|
||
var proofLink = document.getElementById('proof-link');
|
||
var proofMeta = document.getElementById('proof-meta');
|
||
var copyStatus = document.getElementById('copy-status');
|
||
var languageButton = document.getElementById('language');
|
||
var agentState = document.getElementById('agent-state');
|
||
var installCommand = document.getElementById('install-command');
|
||
var projectCommand = document.getElementById('project-command');
|
||
|
||
function copyForElement(element) {
|
||
return language === 'zh' ? element.dataset.zh : element.dataset.en;
|
||
}
|
||
|
||
function updateStaticCopy() {
|
||
document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
|
||
document.querySelectorAll('[data-en][data-zh]').forEach(function (element) {
|
||
element.textContent = copyForElement(element);
|
||
});
|
||
languageButton.textContent = language === 'zh' ? 'EN' : '中文';
|
||
languageButton.setAttribute('aria-label', language === 'zh' ? 'Switch to English' : '切换到中文');
|
||
}
|
||
|
||
function updateUrl() {
|
||
var next = new URL(window.location.href);
|
||
next.searchParams.set('type', type);
|
||
next.searchParams.set('agent', agent);
|
||
next.searchParams.delete('lang');
|
||
next.searchParams.set('source', source);
|
||
next.searchParams.set('input', input);
|
||
history.replaceState(null, '', next.pathname + next.search + next.hash);
|
||
}
|
||
|
||
function recordStep(step) {
|
||
var detail = {
|
||
schemaVersion: 1,
|
||
step: step,
|
||
source: source,
|
||
type: type,
|
||
agent: agent,
|
||
language: language
|
||
};
|
||
try {
|
||
var events = JSON.parse(sessionStorage.getItem(EVENT_KEY) || '[]');
|
||
if (!Array.isArray(events)) events = [];
|
||
events.push(detail);
|
||
sessionStorage.setItem(EVENT_KEY, JSON.stringify(events.slice(-24)));
|
||
} catch (_) {}
|
||
try { window.dispatchEvent(new CustomEvent('archify:start-funnel', { detail: detail })); } catch (_) {}
|
||
}
|
||
|
||
window.ArchifyStartMetrics = {
|
||
snapshot: function () {
|
||
try {
|
||
var events = JSON.parse(sessionStorage.getItem(EVENT_KEY) || '[]');
|
||
return Array.isArray(events) ? events.slice() : [];
|
||
} catch (_) { return []; }
|
||
}
|
||
};
|
||
|
||
function currentPrompt() {
|
||
return input === 'description'
|
||
? DATA[type][language].descriptionPrompt
|
||
: DATA[type][language].repositoryPrompt;
|
||
}
|
||
|
||
function render() {
|
||
language = ArchifySiteLanguage.write(language);
|
||
var recipe = DATA[type];
|
||
var copy = recipe[language];
|
||
updateStaticCopy();
|
||
document.querySelectorAll('[data-type]').forEach(function (button) {
|
||
var active = button.dataset.type === type;
|
||
button.setAttribute('aria-selected', String(active));
|
||
button.tabIndex = active ? 0 : -1;
|
||
});
|
||
document.querySelectorAll('[data-agent]').forEach(function (button) {
|
||
var active = button.dataset.agent === agent;
|
||
button.setAttribute('aria-selected', String(active));
|
||
button.tabIndex = active ? 0 : -1;
|
||
});
|
||
document.querySelectorAll('[data-input]').forEach(function (button) {
|
||
var active = button.dataset.input === input;
|
||
button.setAttribute('aria-selected', String(active));
|
||
button.tabIndex = active ? 0 : -1;
|
||
});
|
||
var agentName = document.querySelector('[data-agent="' + agent + '"]').textContent;
|
||
agentState.textContent = agentName + (language === 'zh' ? ' · 同一份 Skill' : ' · same Skill');
|
||
installCommand.textContent = 'npx -y skills add tt-a1i/archify --skill archify --agent ' + agent + ' --global --copy --yes';
|
||
projectCommand.textContent = 'npx -y skills add tt-a1i/archify --skill archify --agent ' + agent + ' --copy --yes';
|
||
title.textContent = copy.title;
|
||
question.textContent = copy.question;
|
||
prompt.textContent = currentPrompt();
|
||
includeList.replaceChildren.apply(includeList, copy.include.map(function (item) {
|
||
var entry = document.createElement('li');
|
||
entry.textContent = item;
|
||
return entry;
|
||
}));
|
||
proofLink.href = 'gallery.html#proof-' + encodeURIComponent(recipe.proof);
|
||
proofMeta.textContent = language === 'zh'
|
||
? '验证成品 · ' + recipe.presentation.preset + ' · ' + recipe.presentation.motion
|
||
: 'Verified proof · ' + recipe.presentation.preset + ' · ' + recipe.presentation.motion;
|
||
copyStatus.textContent = '';
|
||
updateUrl();
|
||
}
|
||
|
||
function fallbackCopy(text) {
|
||
var area = document.createElement('textarea');
|
||
area.value = text;
|
||
area.setAttribute('readonly', '');
|
||
area.style.position = 'fixed';
|
||
area.style.opacity = '0';
|
||
document.body.appendChild(area);
|
||
area.select();
|
||
var ok = document.execCommand('copy');
|
||
area.remove();
|
||
if (!ok) throw new Error('copy failed');
|
||
}
|
||
|
||
function starterText() {
|
||
var agentName = document.querySelector('[data-agent="' + agent + '"]').textContent;
|
||
if (language === 'zh') {
|
||
return '为 ' + agentName + ' 安装 Archify:\n' + installCommand.textContent.trim()
|
||
+ (input === 'description' ? '\n\n然后在任意新对话中直接告诉 ' : '\n\n然后在目标仓库中让 ') + agentName + ':\n' + currentPrompt();
|
||
}
|
||
return 'Install Archify for ' + agentName + ':\n' + installCommand.textContent.trim()
|
||
+ (input === 'description' ? '\n\nThen start any new chat and tell ' : '\n\nThen, in your target repository, ask ') + agentName + ':\n' + currentPrompt();
|
||
}
|
||
|
||
async function copyText(text, step) {
|
||
try {
|
||
if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);
|
||
else fallbackCopy(text);
|
||
if (step) recordStep(step);
|
||
copyStatus.textContent = language === 'zh' ? '已复制到剪贴板。' : 'Copied to clipboard.';
|
||
} catch (_) {
|
||
copyStatus.textContent = language === 'zh' ? '复制失败,请手动选择文本。' : 'Copy failed. Select the text manually.';
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('[data-type]').forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
type = button.dataset.type;
|
||
render();
|
||
});
|
||
button.addEventListener('keydown', function (event) {
|
||
var buttons = Array.from(document.querySelectorAll('[data-type]'));
|
||
var index = buttons.indexOf(button);
|
||
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') index = (index + 1) % buttons.length;
|
||
else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') index = (index - 1 + buttons.length) % buttons.length;
|
||
else if (event.key === 'Home') index = 0;
|
||
else if (event.key === 'End') index = buttons.length - 1;
|
||
else return;
|
||
event.preventDefault();
|
||
buttons[index].click();
|
||
buttons[index].focus();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-agent]').forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
agent = button.dataset.agent;
|
||
render();
|
||
});
|
||
button.addEventListener('keydown', function (event) {
|
||
var buttons = Array.from(document.querySelectorAll('[data-agent]'));
|
||
var index = buttons.indexOf(button);
|
||
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') index = (index + 1) % buttons.length;
|
||
else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') index = (index - 1 + buttons.length) % buttons.length;
|
||
else if (event.key === 'Home') index = 0;
|
||
else if (event.key === 'End') index = buttons.length - 1;
|
||
else return;
|
||
event.preventDefault();
|
||
buttons[index].click();
|
||
buttons[index].focus();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-input]').forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
input = button.dataset.input;
|
||
render();
|
||
});
|
||
button.addEventListener('keydown', function (event) {
|
||
var buttons = Array.from(document.querySelectorAll('[data-input]'));
|
||
var index = buttons.indexOf(button);
|
||
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') index = (index + 1) % buttons.length;
|
||
else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') index = (index - 1 + buttons.length) % buttons.length;
|
||
else if (event.key === 'Home') index = 0;
|
||
else if (event.key === 'End') index = buttons.length - 1;
|
||
else return;
|
||
event.preventDefault();
|
||
buttons[index].click();
|
||
buttons[index].focus();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-copy-source]').forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
var step = button.dataset.copySource === 'install-command' ? 'global_install_copy' : 'project_install_copy';
|
||
copyText(document.getElementById(button.dataset.copySource).textContent.trim(), step);
|
||
});
|
||
});
|
||
document.getElementById('copy-prompt').addEventListener('click', function () {
|
||
copyText(currentPrompt(), 'prompt_copy');
|
||
});
|
||
document.getElementById('copy-starter').addEventListener('click', function () {
|
||
copyText(starterText(), 'starter_copy');
|
||
});
|
||
proofLink.addEventListener('click', function () { recordStep('proof_open'); });
|
||
languageButton.addEventListener('click', function () {
|
||
language = language === 'zh' ? 'en' : 'zh';
|
||
render();
|
||
});
|
||
|
||
render();
|
||
recordStep('start_view');
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|