
Html Doc
- 1 installs
- 8 repo stars
- Updated May 19, 2026
- oil-oil/html-doc
Renders a Markdown file with YAML frontmatter and fenced component blocks into a readable HTML document for wide tables, comparisons, flows, architecture maps, and decision matrices.
About
This skill authors a .md file with component blocks like hero and metrics, then renders it to a scannable HTML document via a bundled script. A developer uses it when Markdown is too flat for human review of comparisons, flows, or architecture maps.
- Fenced component blocks with JSON bodies
- For human review, not agent-to-agent or live two-way state
Html Doc by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,366 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oil-oil/html-doc --skill html-docAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 8 |
| Last updated | May 19, 2026 |
| Repository | oil-oil/html-doc ↗ |
What it does
Renders a Markdown file with YAML frontmatter and fenced component blocks into a readable HTML document for wide tables, comparisons, flows, architecture maps, and decision matrices.
Files
HTML Doc
Create readable HTML documents by writing a Markdown file with YAML frontmatter and fenced component blocks, then rendering it with the bundled script.
When To Use
Use this Skill when a person needs to review information that Markdown makes hard to scan: wide tables, comparisons, flows, architecture maps, state changes, code diffs, or decision matrices.
For tiny answers, raw logs, short command output, or files that already have a required format, a plain response is usually enough.
Prefer a plain response or raw HTML when:
- Another agent will read the output — plain Markdown or YAML is easier to parse than rendered HTML.
- The interface needs live two-way state — drag-and-drop sorting, real-time preview, conditional form logic. These need a frontend framework, not a document renderer.
- The content already has a required format — config file, migration script, log output.
File Format
A document is a .md file with two parts:
YAML frontmatter (optional) — document-level metadata:
---
title: My Document
subtitle: A short premise
lang: zh-CN
glossary:
MTTR: Mean Time To Recovery
SLO: Service Level Objective
---Fenced component blocks — the language identifier is the component type; the body is the block's JSON fields:
~~~markdown ~~~hero { "title": "Document Title", "body": "Short premise.", "tags": ["architecture", "review"] } ~~~
~~~metrics { "items": [ { "label": "Uptime", "value": "99.9%", "spark": [99, 100, 99, 100] } ] } ~~~ ~~~
Each block parses independently — a JSON error in one block shows an error card without breaking the rest of the document.
Workflow
1. Identify the information shape: summary, metrics, sequence, comparison, architecture, state change, or code review. 2. Create a .md file. Open references/schema.md when exact fields are needed. See examples/component-showcase.md for every component type. 3. Render it:
node scripts/render-html-doc.mjs examples/my-doc.md dist/my-doc.htmlCore Principles
These principles exist because an agent's first instinct is to pour content into a component without restructuring it. That produces something that looks like Markdown with a fancier wrapper. The goal is to make a document that a person can scan in 30 seconds and understand.
Think like a PPT designer. The best presentations follow two constraints:
- One block = one idea. If you're trying to say two things, make two blocks. A block that covers multiple points is a sign the content hasn't been focused yet.
- Title = conclusion, not topic. "System Architecture" is a topic label. "Three services share one DB — that's the bottleneck" is a conclusion. Write every block title so the reader learns something from it alone.
The scan test: Cover all body copy. Can you read just the titles and understand the document's core argument? If not, the titles are describing topics, not delivering insights.
No repetition across blocks. Every block must advance the document. If a point has already been made — even in different words — delete it. A document that circles back to what it already said is padding. Ask before writing each block: what does this add that hasn't been said yet?
---
1. Write Markdown, render HTML. Author a .md file with YAML frontmatter and ~~~componentType fenced blocks. For high-fidelity UI prototypes, custom interaction canvases, or product mockups that are too detailed for component JSON, write a standalone HTML prototype using references/prototype-html.md, reuse the exported html-doc CSS, then embed it with embed.
2. Choose by information shape — then think in the component's native language. Pick the component whose shape matches the content's structure. But selecting the right component is only the first step. Each component has its own grammar:
matrixthinks in intersections — cell values should be short, comparable, and scannable; if a cell contains a sentence, the content hasn't been restructuredsplitPanelthinks in contrast and juxtaposition — two regions must create visual tension, not just sit next to each othermotionStagethinks in state machines — what objects exist, what triggers each transition, what visibly changes at each stepflowthinks in gates — each node should represent a decision or transformation, not just a label
After choosing, restructure the information to fit that grammar. Pouring the original text into a new container without restructuring produces the wrong result in a fancier wrapper.
3. Prose in a cell or body is a warning sign. When a block reads like a paragraph or list, recast it as a table, flow, comparison, or annotation. A matrix cell that contains a sentence should become a badge, a score, a short tag, or a two-word label. A splitPanel body that runs four lines should become a nested flow or a 3col layout. Keep copy direct and specific — if the title already states the context, delete the body.
- BAD: matrix cell = "生成 6 种明显不同的方案,分别改变布局、语气和信息密度,放在 HTML 网格里并排比较,标注每个方案的取舍"
- GOOD: matrix cell =
{ "badge": "可视对比", "tone": "success" }with a short label
4. Title carries the message; delete body copy that repeats it. Write each block's title as a conclusion. Only add body copy when it contains information the title cannot hold — a number, a constraint, a specific example, a reason. If a sentence says the same thing as the title in different words, delete it.
- BAD:
"title": "发布流程" - GOOD:
"title": "草稿到三平台:五个节点,两个人工确认" - BAD body: title = "调参结果可直接粘回 Claude Code", body = "滑块调整算法参数,copy 按钮把结果直接粘贴回 Claude Code,形成闭环。" ← body says the same thing as the title
- BAD body: ends with "仅凭这一点,就已经足够了" or "这就是为什么……" ← filler that adds zero information
- Warning signs: body begins by rephrasing the title; two adjacent sentences make the same point; body could be deleted and nothing would be lost.
5. Vary visual rhythm. Alternate anchor blocks (hero, splitPanel quote, metrics) with detail blocks (splitPanel, flow, matrix). Readers lose engagement after three blocks of equal visual weight — aim for one anchor every 2–3 detail blocks.
6. Reach for `motionStage` whenever a workflow has visible state changes. A 5-step animation showing a real UI in motion communicates more than 5 splitPanel cells of text describing the same thing. If content describes a feature flow, a user journey, or any sequence where something changes on screen — motionStage is almost always the right call.
- Trigger signals: feature walkthroughs, before/after UI states, AI model loops, checkout flows, editor workflows, status progressions
- Objects must look like real UI: app windows, metric cards, status badges — not abstract colored boxes
- Each step must change something visible: content, status color, position, or opacity. Steps that only update the caption while the stage stays static add nothing.
- BAD: three colored boxes moving left to right with labels "Step 1 → Step 2 → Step 3"
- GOOD: an editor window showing draft text, a badge updating to "AI 检查中", a score metric fading in at 78
7. Use inline emphasis naturally. Use backtick code and **bold** for identifiers and conclusions. Use [[Term|definition]] for inline tooltips, or define meta.glossary for terms used in 3+ places.
8. Let the renderer own the visual system. Block content describes structure, relationships, and interactions; the renderer handles all styling automatically.
9. Add export actions where they improve review. Reviews, diffs, editors, and matrices often benefit from copy buttons. Add embed when the reader benefits from seeing a source or live page inline.
10. Use standalone prototype HTML for real interface sketches. When the task is to inspect a real UI surface, build a small publishable HTML page under dist/prototypes/, export shared CSS with node scripts/export-html-doc-css.mjs dist/html-doc.css, and embed it back into the report. Keep the main report in Markdown so the narrative stays structured.
Component Reference
Pick the component whose shape matches the content's structure.
Opening & numbers
- `hero` — Opening block: title, premise, tags. Use as the first block in every document.
- `metrics` — 2–6 key numbers with optional spark lines. Use when numbers anchor the story before detail. Supports
{ "value": "99%", "label": "Uptime", "note": "...", "spark": [98, 99, 100] }.
Sequence & flow
- `flow` — 3–7 ordered steps, states, or decisions. Each node should represent a transformation or gate, not just a label. Stop at 7 nodes. Supports date fields for time-ordered sequences.
Spatial layout
- `splitPanel` — PPT-style layouts. Choose variant by content shape:
| Variant | Choose when |
|---|---|
quote | Single powerful statement — design principle, brand philosophy, key takeaway |
4col / 3col | 3–5 parallel items of equal weight (features, steps, personas) |
2col | Two complementary or contrasting views |
lr 1:2 | Left is a short label; right holds detail or a list |
lr 2:1 | Left is full explanation; right is highlights |
tb | Top is concept or premise; bottom is example or implementation |
Comparison & decision
- `matrix` — 4+ rows × 3+ columns for dense cross-comparison. Cell values should be short: badges, scores, tags, or two-word labels. Long prose in cells means the content needs restructuring. Add
sortablefor interactive filtering. - `variantGrid` — 2–3 side-by-side options when the reader needs to pick one. Use
matrixwhen attributes matter more than the choice itself.
Code & technical review
- `diffReview` — Code or config diff with findings. Use when a human must approve a specific change.
- `codeAnnotation` — Single snippet with line-level notes. Use when explaining how specific code works.
Architecture & visual
- `layeredArchitecture` — Service map with swim-lane columns and routed arrows. Use when spatial tier (layer, zone) carries meaning.
- `stage` — Free-position diagram or UI sketch. Use when layout geometry itself is the content.
- `motionStage` — Step-by-step UI demo. Default choice for any product feature walkthrough, user journey, or UI state sequence. Objects must look like real interface elements; each step changes something visible; never use for static flowcharts.
- `relationshipMap` — Directed connections. Use when direction and cardinality tell the story, not spatial position.
Narrative & evidence
- `emphasisPanel` — Annotated prose. Use when specific entities inside running text need highlighting and a side-panel detail view.
- `kanban` — Status-grouped tasks, risks, or feedback. Use when grouping by status matters; use
matrixwhen attributes matter more than status.
Editing surfaces
- `formEditor` — Structured form with copy output. Use when the reader needs to fill in fields and export the result.
- `sliderLab` — Parameter sliders with live preview and copy output. Use for tuning numeric values (model parameters, animation settings).
- `promptEditor` — Prompt template editor with variable substitution and copy. Use when the reader needs to customize and export a prompt.
Utility
- `embed` — iframe preview with hover link. Use when the reader benefits from seeing a live page inline.
- Standalone prototype HTML — Use
references/prototype-html.mdfor high-fidelity UI prototypes that reuse html-doc CSS and embed back into the report.
.DS_Store
node_modules/
*.log
tmp/
interface:
display_name: "HTML Doc"
short_description: "Create readable HTML docs"
default_prompt: "Use $html-doc when Markdown is too flat for a review artifact. Create simple JSON for comparisons, flows, architecture maps, state changes, diffs, evidence boards, nested structures, or lightweight editors, then render it with the bundled script."
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Narrative Animation Components</title>
<style>
:root {
--bg-color: #F9F8F6;
--surface-color: #FFFFFF;
--text-primary: #2D2C2A;
--text-secondary: #76746F;
--text-tertiary: #A6A39E;
--accent-color: #D96C4A;
--border-color: #E6E4E0;
--border-light: #F2F0EC;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--radius-sm: 4px;
--radius-md: 6px;
--transition-slow: 0.5s cubic-bezier(0.4, 0, 0.2, 1);
--transition-fast: 0.3s ease;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-primary);
font-family: var(--font-family);
-webkit-font-smoothing: antialiased;
line-height: 1.5;
padding: 40px;
font-size: 14px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 32px;
max-width: 1000px;
margin: 0 auto;
}
.story-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
display: flex;
flex-direction: column;
}
.story-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-light);
font-weight: 500;
font-size: 13px;
letter-spacing: 0.5px;
text-transform: uppercase;
color: var(--text-secondary);
}
.story-stage {
height: 240px;
background-color: var(--bg-color);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.story-footer {
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.story-caption {
font-size: 13px;
color: var(--text-primary);
min-height: 40px;
}
.story-controls {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 12px;
border-top: 1px solid var(--border-light);
}
.stepper {
display: flex;
gap: 6px;
}
.step-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--border-color);
cursor: pointer;
transition: background-color var(--transition-fast);
}
.step-dot.active {
background-color: var(--accent-color);
}
.play-pause {
background: none;
border: none;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
font-family: inherit;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: color var(--transition-fast);
}
.play-pause:hover {
color: var(--text-primary);
}
/* --- Story 1: Product Story --- */
.v1-mobile {
width: 120px;
height: 180px;
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 12px;
position: absolute;
transform: translateX(0);
transition: transform var(--transition-slow);
}
.v1-line { height: 4px; background: var(--border-light); margin-bottom: 8px; border-radius: 2px; }
.v1-line.title { height: 8px; width: 60%; background: var(--border-color); margin-bottom: 12px; }
.v1-author {
position: absolute;
right: 40px;
top: 60px;
background: white;
border: 1px solid var(--border-color);
padding: 8px 12px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
gap: 8px;
opacity: 0;
transform: translateY(10px);
transition: all var(--transition-slow);
}
.v1-avatar { width: 16px; height: 16px; border-radius: 50%; background: var(--accent-color); }
.v1-name { font-size: 10px; color: var(--text-secondary); }
.v1-stats {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(10px);
background: var(--text-primary);
color: white;
padding: 6px 12px;
border-radius: 12px;
font-size: 11px;
display: flex;
gap: 12px;
opacity: 0;
transition: all var(--transition-slow);
}
[data-step="0"] .v1-mobile { transform: translateX(0); }
[data-step="1"] .v1-mobile { transform: translateX(-40px); }
[data-step="1"] .v1-author { opacity: 1; transform: translateY(0); }
[data-step="2"] .v1-mobile { transform: translateX(-40px); }
[data-step="2"] .v1-author { opacity: 1; transform: translateY(0); }
[data-step="2"] .v1-stats { opacity: 1; transform: translateX(-50%) translateY(0); }
/* --- Story 2: Workflow Story --- */
.v2-node {
width: 140px;
padding: 12px;
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
text-align: center;
font-size: 12px;
color: var(--text-secondary);
transition: all var(--transition-slow);
position: relative;
}
.v2-status {
font-size: 10px;
text-transform: uppercase;
margin-top: 8px;
letter-spacing: 0.5px;
}
.v2-spinner {
width: 12px;
height: 12px;
border: 2px solid var(--border-light);
border-top-color: var(--accent-color);
border-radius: 50%;
margin: 8px auto 0;
opacity: 0;
animation: spin 1s linear infinite;
display: none;
}
@keyframes spin { to { transform: rotate(360deg); } }
[data-step="0"] .v2-node { color: var(--text-secondary); }
[data-step="1"] .v2-node { border-color: var(--accent-color); color: var(--text-primary); }
[data-step="1"] .v2-spinner { display: block; opacity: 1; }
[data-step="1"] .v2-status { display: none; }
[data-step="2"] .v2-node { border-color: #5BA882; background: #F4FAF7; color: #2D5C45; }
[data-step="2"] .v2-spinner { display: none; }
/* --- Story 3: UI State Story --- */
.v3-panel {
width: 200px;
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
transition: all var(--transition-slow);
}
.v3-empty { font-size: 11px; color: var(--text-tertiary); text-align: center; font-style: italic; transition: opacity var(--transition-fast);}
.v3-field { display: flex; align-items: center; justify-content: space-between; opacity: 0; height: 0; overflow: hidden; transition: all var(--transition-slow); }
.v3-label { width: 40px; height: 6px; background: var(--border-color); border-radius: 3px; }
.v3-toggle { width: 24px; height: 12px; background: var(--border-light); border-radius: 6px; position: relative; }
.v3-toggle::after { content: ''; position: absolute; left: 2px; top: 2px; width: 8px; height: 8px; background: white; border-radius: 50%; }
.v3-toggle.on { background: var(--accent-color); }
.v3-toggle.on::after { left: 14px; }
.v3-btn { background: var(--border-color); height: 24px; border-radius: 4px; opacity: 0; transition: opacity var(--transition-slow); }
.v3-toast { position: absolute; top: 20px; background: var(--text-primary); color: white; padding: 4px 12px; font-size: 10px; border-radius: 12px; opacity: 0; transform: translateY(-10px); transition: all var(--transition-slow); }
[data-step="0"] .v3-empty { opacity: 1; }
[data-step="1"] .v3-empty { opacity: 0; height: 0; margin: 0; }
[data-step="1"] .v3-field { opacity: 1; height: 12px; }
[data-step="2"] .v3-empty { opacity: 0; height: 0; margin: 0; }
[data-step="2"] .v3-field { opacity: 1; height: 12px; }
[data-step="2"] .v3-toggle { background: var(--accent-color); }
[data-step="2"] .v3-toggle::after { left: 14px; }
[data-step="2"] .v3-btn { opacity: 1; background: var(--text-primary); }
[data-step="3"] .v3-empty { opacity: 0; height: 0; margin: 0; }
[data-step="3"] .v3-field { opacity: 1; height: 12px; }
[data-step="3"] .v3-toggle { background: var(--accent-color); }
[data-step="3"] .v3-toggle::after { left: 14px; }
[data-step="3"] .v3-btn { opacity: 1; background: var(--text-primary); }
[data-step="3"] .v3-toast { opacity: 1; transform: translateY(0); }
/* --- Story 4: Decision Story --- */
.v4-container { display: flex; gap: 16px; transition: all var(--transition-slow); }
.v4-option { width: 80px; height: 60px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-size: 11px; background: white; transition: all var(--transition-slow); }
.v4-result-area { position: absolute; bottom: 40px; display: flex; flex-direction: column; gap: 8px; width: 180px; }
.v4-rec, .v4-risk { padding: 8px 12px; border-radius: var(--radius-sm); font-size: 11px; opacity: 0; transform: translateY(10px); transition: all var(--transition-slow); }
.v4-rec { background: white; border: 1px solid var(--border-color); }
.v4-risk { background: #FFF4F0; color: #A84C2D; border: 1px solid #FADED5; }
[data-step="1"] .v4-container { transform: translateY(-30px); }
[data-step="1"] .v4-option:nth-child(2) { border-color: var(--accent-color); background: #FFF8F6; color: var(--accent-color); }
[data-step="2"] .v4-container { transform: translateY(-30px); }
[data-step="2"] .v4-option:nth-child(2) { border-color: var(--accent-color); background: #FFF8F6; color: var(--accent-color); }
[data-step="2"] .v4-rec { opacity: 1; transform: translateY(0); }
[data-step="3"] .v4-container { transform: translateY(-30px); }
[data-step="3"] .v4-option:nth-child(2) { border-color: var(--accent-color); background: #FFF8F6; color: var(--accent-color); }
[data-step="3"] .v4-rec { opacity: 1; transform: translateY(0); }
[data-step="3"] .v4-risk { opacity: 1; transform: translateY(0); transition-delay: 0.1s; }
</style>
</head>
<body>
<div class="grid">
<!-- FEATURE: Product Story - Content publishing flow -->
<div class="story-card" id="story1">
<div class="story-header">Product Story</div>
<div class="story-stage" data-step="0">
<div class="v1-mobile">
<div class="v1-line title"></div>
<div class="v1-line"></div>
<div class="v1-line"></div>
<div class="v1-line" style="width: 80%"></div>
</div>
<div class="v1-author">
<div class="v1-avatar"></div>
<div class="v1-name">E. Walker</div>
</div>
<div class="v1-stats">
<span>👁 1.2k</span>
<span>✓ Live</span>
</div>
</div>
<div class="story-footer">
<div class="story-caption js-caption"></div>
<div class="story-controls">
<div class="stepper js-stepper"></div>
<button class="play-pause js-play-pause">Pause</button>
</div>
</div>
</div>
<!-- FEATURE: Workflow Story - Async task state transitions -->
<div class="story-card" id="story2">
<div class="story-header">Workflow Story</div>
<div class="story-stage" data-step="0">
<div class="v2-node">
<div>Allocation Task</div>
<div class="v2-status js-v2-status">Reserved</div>
<div class="v2-spinner"></div>
</div>
</div>
<div class="story-footer">
<div class="story-caption js-caption"></div>
<div class="story-controls">
<div class="stepper js-stepper"></div>
<button class="play-pause js-play-pause">Pause</button>
</div>
</div>
</div>
<!-- FEATURE: UI State Story - Settings panel configuration -->
<div class="story-card" id="story3">
<div class="story-header">UI State Story</div>
<div class="story-stage" data-step="0">
<div class="v3-toast">Settings applied</div>
<div class="v3-panel">
<div class="v3-empty">No active configuration.</div>
<div class="v3-field"><div class="v3-label"></div><div class="v3-toggle"></div></div>
<div class="v3-field"><div class="v3-label" style="width: 60px"></div><div class="v3-toggle"></div></div>
<div class="v3-btn"></div>
</div>
</div>
<div class="story-footer">
<div class="story-caption js-caption"></div>
<div class="story-controls">
<div class="stepper js-stepper"></div>
<button class="play-pause js-play-pause">Pause</button>
</div>
</div>
</div>
<!-- FEATURE: Decision Story - User choice and implications -->
<div class="story-card" id="story4">
<div class="story-header">Decision Story</div>
<div class="story-stage" data-step="0">
<div class="v4-container">
<div class="v4-option">Standard</div>
<div class="v4-option">Aggressive</div>
</div>
<div class="v4-result-area">
<div class="v4-rec">Yield est: +4.2%</div>
<div class="v4-risk">Note: Higher volatility expected.</div>
</div>
</div>
<div class="story-footer">
<div class="story-caption js-caption"></div>
<div class="story-controls">
<div class="stepper js-stepper"></div>
<button class="play-pause js-play-pause">Pause</button>
</div>
</div>
</div>
</div>
<script>
const storyData = {
story1: [
"Draft created on the mobile editor.",
"Reviewer assigns an author profile.",
"Content published with live tracking."
],
story2: [
"System reserves resources for the task.",
"Async process is currently running.",
"Task confirmed and finalized successfully."
],
story3: [
"Panel initializes in an empty state.",
"User expands fields for configuration.",
"Toggles are set, ready to save.",
"Changes saved, notification displayed."
],
story4: [
"User presented with strategy options.",
"Selects the 'Aggressive' pathway.",
"System calculates estimated yield.",
"Risk disclosure follows selection."
]
};
class Storyboard {
constructor(id) {
this.element = document.getElementById(id);
this.stage = this.element.querySelector('.story-stage');
this.caption = this.element.querySelector('.js-caption');
this.stepper = this.element.querySelector('.js-stepper');
this.playBtn = this.element.querySelector('.js-play-pause');
this.texts = storyData[id];
this.totalSteps = this.texts.length;
this.currentStep = 0;
this.isPlaying = true;
this.interval = null;
this.init();
}
init() {
// Build stepper
for (let i = 0; i < this.totalSteps; i++) {
const dot = document.createElement('div');
dot.className = 'step-dot';
dot.addEventListener('click', () => this.goTo(i));
this.stepper.appendChild(dot);
}
this.playBtn.addEventListener('click', () => this.togglePlay());
this.updateUI();
this.startTimer();
}
updateUI() {
this.stage.setAttribute('data-step', this.currentStep);
this.caption.textContent = this.texts[this.currentStep];
const dots = this.stepper.querySelectorAll('.step-dot');
dots.forEach((dot, index) => {
dot.classList.toggle('active', index === this.currentStep);
});
// Special handling for story 2 text replacement
if (this.element.id === 'story2') {
const statusEl = this.stage.querySelector('.js-v2-status');
if (this.currentStep === 0) statusEl.textContent = 'Reserved';
if (this.currentStep === 2) statusEl.textContent = 'Confirmed';
}
}
goTo(step) {
this.currentStep = step;
this.updateUI();
if (this.isPlaying) {
this.resetTimer();
}
}
next() {
this.currentStep = (this.currentStep + 1) % this.totalSteps;
this.updateUI();
}
togglePlay() {
this.isPlaying = !this.isPlaying;
this.playBtn.textContent = this.isPlaying ? 'Pause' : 'Play';
if (this.isPlaying) {
this.next();
this.startTimer();
} else {
this.stopTimer();
}
}
startTimer() {
this.interval = setInterval(() => this.next(), 3000);
}
stopTimer() {
clearInterval(this.interval);
}
resetTimer() {
this.stopTimer();
this.startTimer();
}
}
// Initialize all storyboards
document.querySelectorAll('.story-card').forEach(card => {
new Storyboard(card.id);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html-artifact | 温暖默认主题</title>
<style>
:root {
/* Color Palette - Warm, Clean, Universal */
--bg-main: #F7F5F0;
--bg-surface: #FFFFFF;
--bg-soft: #FBF7EF;
--text-primary: #28241F;
--text-secondary: #6E675D;
--accent-primary: #B8653B;
--accent-hover: #9F5430;
--accent-success: #4F8766;
--accent-insight: #B77A2F;
--accent-cool: #4F7F8B;
--border-light: #E4DDD1;
/* Typography */
--font-family: "Avenir Next", Avenir, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;
--font-display: Optima, "Avenir Next", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", sans-serif;
/* UI Elements */
--radius-md: 10px;
--radius-lg: 12px;
--radius-full: 9999px;
--shadow-sm: none;
--shadow-md: none;
--transition: all 0.2s ease;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0) 260px),
var(--bg-main);
color: var(--text-primary);
line-height: 1.5;
padding: 2rem;
-webkit-font-smoothing: antialiased;
}
.container {
max-width: 1024px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 2rem;
}
/* Typography */
h1 { font-family: var(--font-display); font-size: 2.15rem; font-weight: 650; letter-spacing: 0; }
h2 { font-family: var(--font-display); font-size: 1.25rem; font-weight: 650; margin-bottom: 1rem; color: var(--text-primary); }
p { color: var(--text-secondary); }
/* Surface Cards */
.card {
background: var(--bg-surface);
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-light);
}
/* Hero Section */
.hero {
display: flex;
justify-content: space-between;
align-items: flex-end;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-light);
}
.hero-text p {
font-size: 1.1rem;
margin-top: 0.5rem;
}
/* Buttons & Controls */
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1.2rem;
border-radius: var(--radius-md);
font-weight: 500;
cursor: pointer;
transition: var(--transition);
border: none;
font-size: 0.9rem;
}
.btn-primary {
background-color: var(--accent-primary);
color: white;
box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
background-color: var(--accent-hover);
}
.controls {
display: flex;
gap: 0.5rem;
background: #EEE8DD;
padding: 0.25rem;
border-radius: var(--radius-md);
}
.control-tab {
padding: 0.5rem 1rem;
border-radius: 8px;
border: none;
background: transparent;
color: var(--text-secondary);
font-weight: 500;
cursor: pointer;
transition: var(--transition);
}
.control-tab.active {
background: var(--bg-surface);
color: var(--text-primary);
box-shadow: var(--shadow-sm);
}
/* Metrics Grid */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.metric-card {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
}
.metric-label {
font-size: 0.875rem;
color: var(--text-secondary);
text-transform: none;
letter-spacing: 0;
}
.metric-trend {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 0.875rem;
color: var(--accent-success);
}
/* Flowchart */
.flowchart {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
overflow-x: auto;
padding: 1rem 0;
}
.flow-node {
background: var(--bg-soft);
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 1rem;
text-align: center;
flex: 1;
min-width: 120px;
font-weight: 500;
}
.flow-arrow {
color: #B8AA99;
font-weight: bold;
font-size: 1.5rem;
}
/* Comparison Matrix */
.matrix table {
width: 100%;
border-collapse: collapse;
}
.matrix th, .matrix td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid var(--border-light);
}
.matrix th {
color: var(--text-secondary);
font-weight: 500;
font-size: 0.875rem;
}
.matrix tr:last-child td {
border-bottom: none;
}
.check { color: var(--accent-success); font-weight: bold; }
.cross { color: var(--text-secondary); opacity: 0.5; }
/* Code & Annotation */
.code-section {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 1.5rem;
}
.code-block {
background: #2B2520;
color: #F4EDE3;
padding: 1.5rem;
border-radius: var(--radius-md);
font-family: monospace;
font-size: 0.9rem;
overflow-x: auto;
position: relative;
}
.code-highlight {
color: #D9915B;
}
.annotation {
background: var(--bg-soft);
border-left: 4px solid var(--accent-primary);
padding: 1rem;
border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
/* Insight Card */
.insight-card {
background: #FFF7E8;
border-color: #EAD1A8;
display: flex;
gap: 1rem;
align-items: flex-start;
}
.insight-icon {
background: #F4DFB8;
color: var(--accent-insight);
width: 32px;
height: 32px;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
}
/* Toast */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: var(--text-primary);
color: white;
padding: 1rem 1.5rem;
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
transform: translateY(150%);
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
z-index: 1000;
}
.toast.show {
transform: translateY(0);
}
@media (max-width: 768px) {
.hero { flex-direction: column; align-items: flex-start; gap: 1rem; }
.code-section { grid-template-columns: 1fr; }
.flowchart { flex-direction: column; align-items: stretch; }
.flow-arrow { transform: rotate(90deg); text-align: center; }
}
</style>
</head>
<body>
<div class="container">
<!-- FEATURE: Hero section with title, subtitle, and primary export action -->
<header class="hero">
<div class="hero-text">
<h1>html-artifact</h1>
<p>少写 HTML,多生成好看的可视化</p>
</div>
<button class="btn btn-primary" id="exportBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
复制 Artifact
</button>
</header>
<!-- FEATURE: Interactive segmented control to filter/switch views -->
<div style="display: flex; justify-content: flex-start;">
<div class="controls">
<button class="control-tab active" data-target="overview">概览</button>
<button class="control-tab" data-target="technical">技术</button>
</div>
</div>
<div id="view-overview" class="view-content" style="display: flex; flex-direction: column; gap: 2rem;">
<!-- FEATURE: Metric cards visualizing key performance indicators -->
<section class="metrics-grid">
<div class="card metric-card">
<span class="metric-label">生成速度</span>
<span class="metric-value" id="val-speed">124ms</span>
<span class="metric-trend">提升 12%</span>
</div>
<div class="card metric-card">
<span class="metric-label">可访问性</span>
<span class="metric-value">98/100</span>
<span class="metric-trend">符合 WCAG AA</span>
</div>
<div class="card metric-card">
<span class="metric-label">文件体积</span>
<span class="metric-value">4.2kb</span>
<span class="metric-trend" style="color: var(--text-secondary)">零依赖</span>
</div>
</section>
<!-- FEATURE: Insight card highlighting a key takeaway visually -->
<section class="card insight-card">
<div class="insight-icon">!</div>
<div>
<h3 style="font-size: 1rem; margin-bottom: 0.25rem;">视觉优先</h3>
<p style="font-size: 0.9rem; color: #7A5522;">超过 120 字的解释,优先压缩成图表、流程、卡片或交互控件。</p>
</div>
</section>
<!-- FEATURE: Flowchart visualizing a multi-step process cleanly -->
<section class="card">
<h2>生成链路</h2>
<div class="flowchart">
<div class="flow-node">JSON 输入</div>
<div class="flow-arrow">→</div>
<div class="flow-node">布局编排</div>
<div class="flow-arrow">→</div>
<div class="flow-node">主题映射</div>
<div class="flow-arrow">→</div>
<div class="flow-node" style="border-color: var(--accent-primary); background: #F8ECE3;">最终 HTML</div>
</div>
</section>
</div>
<div id="view-technical" class="view-content" style="display: none; flex-direction: column; gap: 2rem;">
<!-- FEATURE: Comparison matrix comparing features clearly -->
<section class="card matrix">
<h2>架构对比</h2>
<table>
<thead>
<tr>
<th>能力</th>
<th>直接生成 HTML</th>
<th>JSON 编译器</th>
</tr>
</thead>
<tbody>
<tr>
<td>语义结构</td>
<td class="cross">容易漂移</td>
<td class="check">组件保证</td>
</tr>
<tr>
<td>响应式</td>
<td class="cross">依赖提示</td>
<td class="check">默认内置</td>
</tr>
<tr>
<td>交互状态</td>
<td class="cross">手写脚本</td>
<td class="check">组件挂载</td>
</tr>
</tbody>
</table>
</section>
<!-- FEATURE: Code block with an contextual annotation card -->
<section class="code-section">
<div class="card code-block" id="code-snippet">
<span style="color: #9B9083;">// 由组件自动挂载交互</span>
document.querySelectorAll(<span class="code-highlight">'.tab'</span>).forEach(t => {
t.addEventListener('click', (e) => {
<span style="color: #9B9083;">// 清理旧状态</span>
clearActiveStates();
<span style="color: #9B9083;">// 显示新视图</span>
e.target.classList.add(<span class="code-highlight">'active'</span>);
renderView(e.target.dataset.id);
});
});</div>
<div class="annotation">
<h3 style="font-size: 0.9rem; margin-bottom: 0.5rem; color: var(--text-primary)">轻量交互</h3>
<p style="font-size: 0.85rem;">组件只暴露语义配置,渲染器负责绑定状态、复制、筛选和导出。</p>
</div>
</section>
</div>
</div>
<div class="toast" id="toastMessage">已复制 Artifact 配置</div>
<script>
// FEATURE: Interactive Tab Switching logic
const tabs = document.querySelectorAll('.control-tab');
const views = document.querySelectorAll('.view-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// Update active tab styling
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Show corresponding view
const targetId = `view-${tab.dataset.target}`;
views.forEach(view => {
if(view.id === targetId) {
view.style.display = 'flex';
} else {
view.style.display = 'none';
}
});
});
});
// FEATURE: Export button interaction with Toast notification
const exportBtn = document.getElementById('exportBtn');
const toast = document.getElementById('toastMessage');
let toastTimeout;
exportBtn.addEventListener('click', () => {
// Change button state temporarily
const originalText = exportBtn.innerHTML;
exportBtn.innerHTML = '复制中...';
exportBtn.style.opacity = '0.7';
// Simulate network request/action
setTimeout(() => {
exportBtn.innerHTML = originalText;
exportBtn.style.opacity = '1';
// Show toast
toast.classList.add('show');
clearTimeout(toastTimeout);
toastTimeout = setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}, 600);
});
// FEATURE: Subtle interaction on metric cards (randomly updating value to feel 'alive' occasionally)
setInterval(() => {
const speedVal = document.getElementById('val-speed');
const current = parseInt(speedVal.innerText);
// Fluctuate between 110ms and 140ms
const newVal = Math.floor(Math.random() * (140 - 110 + 1) + 110);
speedVal.innerText = `${newVal}ms`;
}, 5000);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html-artifact Design System Showcase</title>
<style>
:root {
/* Palette: Slightly warm, clean, universal */
--bg-color: #F7F6F3;
--surface-color: #FFFFFF;
--surface-alt: #F0EFEA;
--border-color: #E2DFD8;
--border-strong: #CFCBC2;
--text-primary: #2D2B2A;
--text-secondary: #6B6763;
--text-tertiary: #96938D;
--accent-primary: #D16947; /* Warm terracotta */
--accent-secondary: #4A7A9C; /* Muted blue */
--accent-success: #558763;
--accent-warning: #D99C3B;
/* Typography: Humanist, compact */
--font-family: "Avenir Next", "Avenir", "Optima", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--font-mono: "Menlo", "Consolas", "Courier New", monospace;
--font-size-xs: 11px;
--font-size-sm: 13px;
--font-size-base: 14px;
--font-size-lg: 16px;
--font-size-xl: 20px;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--radius-sm: 2px;
--radius-md: 4px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
color: var(--text-primary);
background-color: var(--bg-color);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
/* Typography */
h1, h2, h3, h4 { font-weight: 500; color: var(--text-primary); }
h1 { font-size: var(--font-size-xl); }
h2 { font-size: var(--font-size-lg); margin-bottom: var(--spacing-md); }
h3 { font-size: var(--font-size-base); }
p { color: var(--text-secondary); }
.text-xs { font-size: var(--font-size-xs); }
.text-sm { font-size: var(--font-size-sm); }
.text-muted { color: var(--text-tertiary); }
.mono { font-family: var(--font-mono); font-size: var(--font-size-sm); }
/* Layout */
.container {
max-width: 1600px;
margin: 0 auto;
padding: 80px var(--spacing-lg) var(--spacing-xl);
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: var(--spacing-md);
}
.col-12 { grid-column: span 12; }
.col-8 { grid-column: span 8; }
.col-6 { grid-column: span 6; }
.col-4 { grid-column: span 4; }
.col-3 { grid-column: span 3; }
/* Flat UI Primitives */
.panel {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--spacing-md);
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color);
padding-bottom: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
}
button {
background: var(--surface-alt);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-sm);
border-radius: var(--radius-sm);
cursor: pointer;
font-family: inherit;
transition: all 0.2s;
}
button:hover { background: var(--border-color); }
button.primary {
background: var(--accent-primary);
color: white;
border-color: var(--accent-primary);
}
button.primary:hover { filter: brightness(0.9); }
.tag {
display: inline-block;
padding: 2px 6px;
background: var(--surface-alt);
border: 1px solid var(--border-color);
font-size: var(--font-size-xs);
border-radius: var(--radius-sm);
color: var(--text-secondary);
}
/* Specific Components */
/* Sticky Nav */
.sticky-nav {
position: fixed;
top: 0; left: 0; right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-sm) var(--spacing-lg);
display: flex;
justify-content: space-between;
align-items: center;
z-index: 100;
}
/* Hero & Metrics */
.metric-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-sm);
}
.metric-card {
padding: var(--spacing-sm);
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
}
.metric-value { font-size: var(--font-size-xl); color: var(--text-primary); margin: var(--spacing-xs) 0; }
.trend-up { color: var(--accent-success); }
.trend-down { color: var(--accent-primary); }
/* Prompt Editor & Code */
.editor-container {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
height: 100%;
}
textarea.prompt-box {
width: 100%;
height: 100px;
background: var(--surface-alt);
border: 1px solid var(--border-color);
padding: var(--spacing-sm);
font-family: var(--font-family);
font-size: var(--font-size-sm);
resize: vertical;
border-radius: var(--radius-sm);
color: var(--text-primary);
}
textarea:focus { outline: 1px solid var(--accent-secondary); }
.code-block {
background: #FAFAFA;
border: 1px solid var(--border-color);
padding: var(--spacing-sm);
border-radius: var(--radius-sm);
overflow-x: auto;
}
.code-comment { color: var(--text-tertiary); }
.diff-add { background: #e6ffed; color: var(--accent-success); display: block; }
.diff-remove { background: #ffeef0; color: var(--accent-primary); display: block; }
/* Comparison Matrix */
.matrix-table {
width: 100%;
border-collapse: collapse;
font-size: var(--font-size-sm);
}
.matrix-table th, .matrix-table td {
border: 1px solid var(--border-color);
padding: var(--spacing-sm);
text-align: left;
}
.matrix-table th { background: var(--surface-alt); font-weight: 500; }
/* Flow / Architecture */
.diagram-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md) 0;
position: relative;
}
.node {
border: 1px solid var(--border-strong);
background: var(--surface-color);
padding: var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
text-align: center;
z-index: 2;
}
.connector {
flex-grow: 1;
height: 1px;
background: var(--border-strong);
position: relative;
}
.connector::after {
content: '';
position: absolute;
right: 0; top: -4px;
border-width: 4px 0 4px 6px;
border-style: solid;
border-color: transparent transparent transparent var(--border-strong);
}
/* Timeline */
.timeline {
border-left: 2px solid var(--border-color);
margin-left: var(--spacing-sm);
padding-left: var(--spacing-md);
}
.timeline-item {
position: relative;
margin-bottom: var(--spacing-md);
}
.timeline-item::before {
content: '';
position: absolute;
left: calc(var(--spacing-md) * -1 - 6px);
top: 4px;
width: 8px; height: 8px;
background: var(--surface-color);
border: 2px solid var(--accent-secondary);
border-radius: 50%;
}
/* Kanban / Boards */
.board-cols {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-sm);
}
.board-col {
background: var(--surface-alt);
padding: var(--spacing-sm);
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
}
.card {
background: var(--surface-color);
border: 1px solid var(--border-color);
padding: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
}
/* Form / Slider Lab */
.form-group {
margin-bottom: var(--spacing-sm);
display: flex;
align-items: center;
justify-content: space-between;
}
.form-group label { font-size: var(--font-size-sm); }
input[type="range"] {
-webkit-appearance: none;
width: 60%;
height: 2px;
background: var(--border-strong);
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px; height: 12px;
border-radius: 50%;
background: var(--accent-secondary);
cursor: pointer;
}
.toggle {
appearance: none;
width: 32px; height: 16px;
background: var(--border-strong);
border-radius: 16px;
position: relative;
cursor: pointer;
outline: none;
}
.toggle::after {
content: '';
position: absolute;
top: 2px; left: 2px;
width: 12px; height: 12px;
background: white;
border-radius: 50%;
transition: 0.2s;
}
.toggle:checked { background: var(--accent-success); }
.toggle:checked::after { left: 18px; }
</style>
</head>
<body>
<!-- FEATURE: Sticky Navigation and Toolbar -->
<nav class="sticky-nav">
<div style="display: flex; align-items: center; gap: var(--spacing-md);">
<strong>html-artifact</strong>
<span class="text-sm text-muted">Theme Sandbox & Component Registry</span>
</div>
<div style="display: flex; gap: var(--spacing-sm);">
<button id="copyBtn">Copy Config</button>
<button class="primary" id="exportBtn">Export Layout</button>
</div>
</nav>
<main class="container">
<!-- FEATURE: Hero section and quick metrics overview utilizing horizontal space -->
<div class="col-4 panel">
<h2>Context Engine v2.4</h2>
<p class="text-sm" style="margin-bottom: var(--spacing-sm);">Active environment profile determining generation constraints and formatting rules for artifact outputs.</p>
<div style="display: flex; gap: var(--spacing-xs); flex-wrap: wrap;">
<span class="tag">Language: Typescript</span>
<span class="tag">Framework: React</span>
<span class="tag">Style: Flat</span>
</div>
</div>
<div class="col-8 panel">
<div class="panel-header">
<span class="text-sm font-weight: 500">Generation Metrics (Last 7 Days)</span>
<button class="text-xs">View Logs</button>
</div>
<div class="metric-grid">
<div class="metric-card">
<div class="text-xs text-muted">Total Artifacts</div>
<div class="metric-value">1,248</div>
<div class="text-xs trend-up">↑ 12% vs prior</div>
</div>
<div class="metric-card">
<div class="text-xs text-muted">Avg. Token Usage</div>
<div class="metric-value">4.2k</div>
<div class="text-xs trend-down">↓ 3% optimization</div>
</div>
<div class="metric-card">
<div class="text-xs text-muted">Syntax Valid</div>
<div class="metric-value">99.8%</div>
<div class="text-xs text-muted">- Stable</div>
</div>
<div class="metric-card">
<div class="text-xs text-muted">Render Time</div>
<div class="metric-value">240ms</div>
<div class="text-xs trend-up">↑ 15ms latency</div>
</div>
</div>
</div>
<!-- FEATURE: Prompt Editor and Code Diff Review -->
<div class="col-4 panel">
<div class="panel-header">
<span class="text-sm">System Prompt Modifier</span>
</div>
<div class="editor-container">
<textarea class="prompt-box">Ensure all UI components strictly adhere to a flat design language. Eliminate drop shadows, utilize 1px solid borders for depth, and maintain a high data-to-ink ratio in layouts.</textarea>
<div style="display: flex; justify-content: flex-end; gap: var(--spacing-xs);">
<button class="text-xs">Revert</button>
<button class="text-xs primary">Apply Rule</button>
</div>
<hr style="border:0; border-top:1px solid var(--border-color); margin: var(--spacing-sm) 0;">
<!-- FEATURE: Slider/Toggle Lab -->
<div class="form-group">
<label>Temperature</label>
<input type="range" min="0" max="100" value="25" id="tempSlider">
<span class="text-xs mono" id="tempVal">0.25</span>
</div>
<div class="form-group">
<label>Stream Response</label>
<input type="checkbox" class="toggle" checked>
</div>
</div>
</div>
<div class="col-8 panel">
<div class="panel-header">
<span class="text-sm">Diff Review: Component Refactor</span>
<div style="display:flex; gap: 4px;">
<span class="tag">src/components/Card.tsx</span>
</div>
</div>
<div class="code-block mono text-xs">
<div class="code-comment">// Applied flat design constraints</div>
<div>const Card = ({ children, title }) => {</div>
<div class="diff-remove">- return <div style={{ boxShadow: '0 4px 6px rgba(0,0,0,0.1)', borderRadius: '8px' }}></div>
<div class="diff-add">+ return <div style={{ border: '1px solid var(--border)', borderRadius: '2px', background: '#fff' }}></div>
<div> {title && <h3 className="card-title">{title}</h3>}</div>
<div> <div className="card-body">{children}</div></div>
<div> </div>;</div>
<div>};</div>
</div>
</div>
<!-- FEATURE: Comparison Matrix -->
<div class="col-12 panel">
<div class="panel-header">
<span class="text-sm">Variant Matrix Evaluation</span>
</div>
<table class="matrix-table">
<thead>
<tr>
<th>Constraint Profile</th>
<th>Visual Density</th>
<th>Readability Score</th>
<th>Render Complexity</th>
<th>Recommended Use Case</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Strict Flat</strong> (Current)</td>
<td>High</td>
<td>92/100</td>
<td>Low (O(1) paint)</td>
<td>Data-heavy dashboards, admin panels</td>
</tr>
<tr>
<td>Skeuomorphic Lite</td>
<td>Medium</td>
<td>85/100</td>
<td>High (Shadows/Gradients)</td>
<td>Consumer landing pages</td>
</tr>
<tr>
<td>Neumorphic</td>
<td>Low</td>
<td>60/100</td>
<td>Very High</td>
<td>Not recommended</td>
</tr>
</tbody>
</table>
</div>
<!-- FEATURE: Architecture Diagram & Timeline -->
<div class="col-8 panel">
<div class="panel-header">
<span class="text-sm">Pipeline Architecture</span>
</div>
<div class="diagram-container">
<div class="node">User Prompt</div>
<div class="connector"></div>
<div class="node">Context<br>Analyzer</div>
<div class="connector"></div>
<div class="node" style="border-color: var(--accent-secondary); border-width: 2px;">Artifact<br>Generator</div>
<div class="connector"></div>
<div class="node">Validation<br>DOM Render</div>
</div>
<p class="text-xs text-muted" style="text-align: center; margin-top: var(--spacing-sm);">Data flow visualization across primary generation nodes.</p>
</div>
<div class="col-4 panel">
<div class="panel-header">
<span class="text-sm">Execution Timeline</span>
</div>
<div class="timeline">
<div class="timeline-item">
<div class="text-xs font-weight: 500">10:42:01.050</div>
<div class="text-sm">Received payload</div>
</div>
<div class="timeline-item">
<div class="text-xs font-weight: 500">10:42:01.200</div>
<div class="text-sm">Resolved dependencies</div>
</div>
<div class="timeline-item">
<div class="text-xs font-weight: 500" style="color: var(--accent-secondary);">10:42:03.150</div>
<div class="text-sm">Stream complete (2.1s)</div>
</div>
</div>
</div>
<!-- FEATURE: Evidence Board / Kanban -->
<div class="col-12 panel">
<div class="panel-header">
<span class="text-sm">Source Evidence & Insights Board</span>
<button class="text-xs">Add Note</button>
</div>
<div class="board-cols">
<div class="board-col">
<div class="text-xs text-muted" style="margin-bottom: 8px;">Raw Extraction</div>
<div class="card">
"Users preferred layouts that didn't feel like nested boxes within boxes."
<div class="text-xs text-muted" style="margin-top:4px;">- UX Study Q3</div>
</div>
<div class="card">
Avoid large font sizes; rely on layout structure for hierarchy.
</div>
</div>
<div class="board-col">
<div class="text-xs text-muted" style="margin-bottom: 8px;">Synthesized Rules</div>
<div class="card" style="border-left: 3px solid var(--accent-warning);">
Minimize DOM depth. Maximize horizontal grid usage.
</div>
<div class="card" style="border-left: 3px solid var(--accent-success);">
Typography: Humanist sans, 13-14px base size.
</div>
</div>
<div class="board-col">
<div class="text-xs text-muted" style="margin-bottom: 8px;">Applied Output</div>
<div class="card">
Implemented CSS Grid layout template.
</div>
<div class="card">
Removed CSS box-shadow globally.
</div>
</div>
</div>
</div>
</main>
<script>
// FEATURE: JavaScript Interactions to make prototype feel alive
// Slider interaction
const slider = document.getElementById('tempSlider');
const valDisplay = document.getElementById('tempVal');
slider.addEventListener('input', (e) => {
const val = (e.target.value / 100).toFixed(2);
valDisplay.textContent = val;
});
// Copy button interaction
const copyBtn = document.getElementById('copyBtn');
copyBtn.addEventListener('click', () => {
const originalText = copyBtn.textContent;
copyBtn.textContent = 'Copied!';
copyBtn.style.background = 'var(--accent-success)';
copyBtn.style.color = 'white';
copyBtn.style.borderColor = 'var(--accent-success)';
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.style.background = '';
copyBtn.style.color = '';
copyBtn.style.borderColor = '';
}, 1500);
});
// Export button interaction
const exportBtn = document.getElementById('exportBtn');
exportBtn.addEventListener('click', () => {
exportBtn.textContent = 'Exporting...';
setTimeout(() => {
exportBtn.textContent = 'Export Layout';
}, 1000);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Warm Nomad Component Showcase</title>
<style>
:root {
--bg-color: #faf9f5;
--text-main: #33312e;
--text-muted: #8c8984;
--border-color: #e6e4dc;
--accent-warm: #c46247;
--accent-cool: #6b7a6f;
--bg-hover: #f0eee6;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-serif: 'Georgia', 'Times New Roman', serif;
--font-mono: 'Menlo', 'Monaco', 'Courier New', monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: var(--font-sans);
font-size: 13px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
display: flex;
min-height: 100vh;
}
/* Typography */
h1, h2, h3, h4 { font-weight: 500; margin-bottom: 0.5rem; }
.font-serif { font-family: var(--font-serif); }
.text-xs { font-size: 11px; }
.text-sm { font-size: 12px; }
.text-lg { font-size: 16px; }
.text-xl { font-size: 24px; }
.text-xxl { font-size: 48px; letter-spacing: -0.02em; line-height: 1.1; }
.text-muted { color: var(--text-muted); }
.text-accent { color: var(--accent-warm); }
/* Layout Utilities */
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.grid { display: grid; }
/* Borders & Flats */
.border { border: 1px solid var(--border-color); }
.border-t { border-top: 1px solid var(--border-color); }
.border-b { border-bottom: 1px solid var(--border-color); }
.border-l { border-left: 1px solid var(--border-color); }
.border-r { border-right: 1px solid var(--border-color); }
/* Layout */
nav {
width: 200px;
border-right: 1px solid var(--border-color);
padding: 2rem 1rem;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
}
nav ul { list-style: none; }
nav li { margin-bottom: 0.5rem; }
nav a {
text-decoration: none;
color: var(--text-muted);
display: block;
padding: 4px 8px;
transition: all 0.2s;
}
nav a:hover, nav a.active {
color: var(--text-main);
background: var(--bg-hover);
}
nav .section-title {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin: 1.5rem 0 0.5rem 8px;
}
main {
flex: 1;
padding: 3rem 4rem;
max-width: 1000px;
}
section { margin-bottom: 4rem; }
.section-header { margin-bottom: 1.5rem; border-bottom: 1px solid var(--text-main); padding-bottom: 0.5rem; }
/* --- Components --- */
/* Progress Tracker */
.progress-tracker {
display: flex;
border: 1px solid var(--border-color);
height: 24px;
}
.progress-segment {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
border-right: 1px solid var(--border-color);
background: var(--bg-color);
color: var(--text-muted);
}
.progress-segment:last-child { border-right: none; }
.progress-segment.done { background: var(--bg-hover); color: var(--text-main); }
.progress-segment.active { color: var(--accent-warm); font-weight: 500; }
/* Task Table */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.data-table th, .data-table td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.data-table th {
font-weight: normal;
color: var(--text-muted);
padding-top: 0;
}
.data-table tr:hover td { background-color: var(--bg-hover); }
.status-dot {
display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 6px;
}
.status-todo .status-dot { background: var(--border-color); }
.status-doing .status-dot { background: var(--accent-warm); }
.status-done .status-dot { background: var(--accent-cool); }
/* Checkbox List */
.check-list { list-style: none; border-top: 1px solid var(--border-color); }
.check-item {
display: flex;
align-items: flex-start;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
gap: 12px;
cursor: pointer;
}
.check-item:hover { background: var(--bg-hover); }
.custom-checkbox {
appearance: none;
width: 14px; height: 14px;
border: 1px solid var(--text-muted);
border-radius: 0;
outline: none;
cursor: pointer;
position: relative;
margin-top: 2px;
}
.custom-checkbox:checked {
background-color: var(--text-main);
border-color: var(--text-main);
}
.custom-checkbox:checked::after {
content: '';
position: absolute;
left: 3px; top: 1px;
width: 3px; height: 7px;
border: solid var(--bg-color);
border-width: 0 1px 1px 0;
transform: rotate(45deg);
}
.check-item.completed .check-text {
text-decoration: line-through;
color: var(--text-muted);
}
/* Dependency List */
.dep-list { border-left: 1px solid var(--border-color); padding-left: 1rem; margin-left: 0.5rem;}
.dep-item { position: relative; padding: 4px 0; display: flex; align-items: center; gap: 8px;}
.dep-item::before {
content: '';
position: absolute;
left: -1rem; top: 50%;
width: 0.8rem; height: 1px;
background: var(--border-color);
}
/* --- Slide Components --- */
.slide-wrapper {
aspect-ratio: 16 / 9;
border: 1px solid var(--border-color);
background: #fff; /* Slightly brighter for slides */
padding: 2rem;
display: flex;
flex-direction: column;
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.slide-footer {
margin-top: auto;
display: flex;
justify-content: space-between;
font-size: 10px;
color: var(--text-muted);
border-top: 1px solid var(--border-color);
padding-top: 8px;
}
/* Specific Slides */
.slide-title { justify-content: center; text-align: center; }
.slide-title .font-serif { font-size: 3rem; margin-bottom: 1rem; color: var(--text-main); }
.slide-big-number { flex-direction: row; align-items: center; justify-content: center; gap: 3rem; }
.slide-big-number .number { font-size: 6rem; font-family: var(--font-serif); color: var(--accent-warm); line-height: 1; }
.slide-quote { justify-content: center; padding: 0 4rem; }
.slide-quote blockquote { font-size: 1.5rem; font-family: var(--font-serif); font-style: italic; border-left: 1px solid var(--accent-warm); padding-left: 2rem; margin-bottom: 1rem; }
.slide-timeline { justify-content: center; }
.timeline-track { position: relative; height: 1px; background: var(--border-color); width: 100%; margin: 2rem 0; display: flex; justify-content: space-between; }
.timeline-node { position: relative; display: flex; flex-direction: column; align-items: center; }
.timeline-node::before { content: ''; width: 5px; height: 5px; background: var(--text-main); position: absolute; top: -2px; }
.timeline-content { position: absolute; top: 1rem; text-align: center; width: 100px; font-size: 11px; }
.timeline-node:nth-child(even) .timeline-content { top: auto; bottom: 1rem; }
.slide-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; flex: 1; margin-top: 1rem; }
.metric-box { border: 1px solid var(--border-color); padding: 1rem; display: flex; flex-direction: column; justify-content: center; align-items: center;}
/* Filters */
.filter-bar { display: flex; gap: 1rem; margin-bottom: 1rem; font-size: 11px; }
.filter-btn { background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 2px 0; border-bottom: 1px solid transparent; }
.filter-btn.active { color: var(--text-main); border-bottom-color: var(--text-main); }
</style>
</head>
<body>
<nav>
<div class="section-title">Task & Mgmt</div>
<ul>
<li><a href="#progress">Progress Tracker</a></li>
<li><a href="#tasks">Task Table</a></li>
<li><a href="#checklist">Checklist</a></li>
<li><a href="#dependencies">Dependencies</a></li>
</ul>
<div class="section-title">Narrative Slides</div>
<ul>
<li><a href="#slide-title">Title & Section</a></li>
<li><a href="#slide-number">Big Number</a></li>
<li><a href="#slide-quote">Quote</a></li>
<li><a href="#slide-timeline">Timeline</a></li>
<li><a href="#slide-comparison">Comparison</a></li>
</ul>
</nav>
<main>
<!-- FEATURE: Progress Tracker - Visualizes linear flow of phases -->
<section id="progress">
<h2 class="section-header font-serif">Project Phases</h2>
<div class="progress-tracker">
<div class="progress-segment done">Discovery</div>
<div class="progress-segment done">Design</div>
<div class="progress-segment active">Implementation</div>
<div class="progress-segment">Testing</div>
<div class="progress-segment">Deployment</div>
</div>
</section>
<!-- FEATURE: Task Table - High density data view with minimal borders and inline status -->
<section id="tasks">
<h2 class="section-header font-serif">Action Items</h2>
<div class="filter-bar" id="task-filters">
<button class="filter-btn active" data-filter="all">All Items</button>
<button class="filter-btn" data-filter="doing">In Progress</button>
<button class="filter-btn" data-filter="todo">Backlog</button>
</div>
<table class="data-table">
<thead>
<tr>
<th style="width: 50%">Task Description</th>
<th>Assignee</th>
<th>Status</th>
<th>Target</th>
</tr>
</thead>
<tbody id="task-body">
<tr class="task-row" data-status="doing">
<td>Finalize database schema migration</td>
<td>A. Chen</td>
<td class="status-doing"><span class="status-dot"></span>In Progress</td>
<td>Oct 12</td>
</tr>
<tr class="task-row" data-status="todo">
<td>Draft Q3 compliance report</td>
<td>M. Dubois</td>
<td class="status-todo"><span class="status-dot"></span>Backlog</td>
<td>Oct 15</td>
</tr>
<tr class="task-row" data-status="done">
<td>Review external vendor contracts</td>
<td>S. Kim</td>
<td class="status-done"><span class="status-dot"></span>Completed</td>
<td>Oct 08</td>
</tr>
<tr class="task-row" data-status="doing">
<td>Update client API documentation</td>
<td>A. Chen</td>
<td class="status-doing"><span class="status-dot"></span>In Progress</td>
<td>Oct 14</td>
</tr>
</tbody>
</table>
</section>
<!-- FEATURE: Checklist - Interactive todo list with custom checkboxes and strikethrough state -->
<section id="checklist">
<h2 class="section-header font-serif">Pre-flight Checklist</h2>
<ul class="check-list">
<li class="check-item">
<input type="checkbox" class="custom-checkbox">
<span class="check-text">Verify environment variables in staging</span>
</li>
<li class="check-item completed">
<input type="checkbox" class="custom-checkbox" checked>
<span class="check-text">Run full integration test suite</span>
</li>
<li class="check-item">
<input type="checkbox" class="custom-checkbox">
<span class="check-text">Notify stakeholder group of downtime</span>
</li>
<li class="check-item">
<input type="checkbox" class="custom-checkbox">
<span class="check-text">Backup production database snapshot</span>
</li>
</ul>
</section>
<!-- FEATURE: Dependency List - Visualizes parent/child relationships without deep nesting -->
<section id="dependencies">
<h2 class="section-header font-serif">Blockers & Dependencies</h2>
<div class="text-sm" style="margin-bottom: 8px;">Launch Marketing Campaign</div>
<div class="dep-list text-muted">
<div class="dep-item">Approve final ad copy <span style="font-size: 10px; border: 1px solid var(--border-color); padding: 1px 4px;">Blocked</span></div>
<div class="dep-item">Confirm media spend allocation</div>
<div class="dep-list">
<div class="dep-item">Finance Q4 budget approval</div>
</div>
</div>
</section>
<div style="height: 4rem;"></div>
<!-- FEATURE: Title Slide - Minimalist, large typography for presentation start -->
<section id="slide-title">
<div class="slide-wrapper slide-title">
<div class="font-serif">Q4 Strategic Alignment</div>
<div class="text-muted">Navigating market shifts and operational efficiency.</div>
<div class="slide-footer" style="position: absolute; bottom: 2rem; left: 2rem; right: 2rem; border:none; padding:0;">
<span>Oct 2023</span>
<span>Internal Draft v2</span>
</div>
</div>
</section>
<!-- FEATURE: Big Number Slide - Highlights a single key metric for impact -->
<section id="slide-number">
<div class="slide-wrapper slide-big-number">
<div class="number">42<span style="font-size: 2rem;">%</span></div>
<div style="max-width: 200px;">
<div class="font-serif text-lg border-b" style="padding-bottom: 8px; margin-bottom: 8px;">Reduction in Latency</div>
<div class="text-muted text-xs">Achieved post-migration to the new distributed architecture, exceeding initial targets.</div>
</div>
<div class="slide-footer" style="position: absolute; bottom: 2rem; left: 2rem; right: 2rem;">
<span>Performance Metrics</span>
<span>Slide 04</span>
</div>
</div>
</section>
<!-- FEATURE: Quote Slide - Editorial typography for qualitative insights -->
<section id="slide-quote">
<div class="slide-wrapper slide-quote">
<blockquote>
The bottleneck wasn't the technology, it was how our teams were communicating across the stack.
</blockquote>
<div class="text-muted text-sm" style="padding-left: 2rem;">— Director of Engineering, Post-Mortem</div>
<div class="slide-footer" style="position: absolute; bottom: 2rem; left: 2rem; right: 2rem;">
<span>Qualitative Feedback</span>
<span>Slide 07</span>
</div>
</div>
</section>
<!-- FEATURE: Timeline Slide - Flat visual representation of chronological events -->
<section id="slide-timeline">
<div class="slide-wrapper slide-timeline">
<div class="font-serif text-lg" style="margin-bottom: 1rem; text-align: center;">Rollout Schedule</div>
<div class="timeline-track">
<div class="timeline-node">
<div class="timeline-content"><strong>Phase 1</strong><br>Alpha Testers</div>
</div>
<div class="timeline-node">
<div class="timeline-content"><strong>Phase 2</strong><br>Beta Group (10%)</div>
</div>
<div class="timeline-node">
<div class="timeline-content"><strong>Phase 3</strong><br>General Availability</div>
</div>
<div class="timeline-node">
<div class="timeline-content"><strong>Phase 4</strong><br>Deprecate Legacy</div>
</div>
</div>
<div class="slide-footer" style="position: absolute; bottom: 2rem; left: 2rem; right: 2rem;">
<span>Roadmap</span>
<span>Slide 12</span>
</div>
</div>
</section>
</main>
<script>
// Interactive Checklist
document.querySelectorAll('.check-item').forEach(item => {
const checkbox = item.querySelector('input[type="checkbox"]');
// Handle clicking anywhere on the row
item.addEventListener('click', (e) => {
if(e.target !== checkbox) {
checkbox.checked = !checkbox.checked;
}
if (checkbox.checked) {
item.classList.add('completed');
} else {
item.classList.remove('completed');
}
});
// Handle direct checkbox clicks
checkbox.addEventListener('change', (e) => {
e.stopPropagation();
if (e.target.checked) {
item.classList.add('completed');
} else {
item.classList.remove('completed');
}
});
});
// Task Table Filtering
const filterBtns = document.querySelectorAll('.filter-btn');
const taskRows = document.querySelectorAll('.task-row');
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
// Update active state
filterBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const filter = btn.dataset.filter;
// Filter rows
taskRows.forEach(row => {
if (filter === 'all' || row.dataset.status === filter) {
row.style.display = 'table-row';
} else {
row.style.display = 'none';
}
});
});
});
// Simple smooth scroll for nav
document.querySelectorAll('nav a').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML Artifact Visual Reference</title>
<style>
:root {
--bg-color: #faf8f5;
--text-main: #2c2b29;
--text-muted: #6b6965;
--border-color: #e5e2db;
--color-add: #edf5e8;
--color-add-text: #3c7a26;
--color-remove: #faeaea;
--color-remove-text: #ad3232;
--accent-warm: #d97757;
--accent-cool: #567e96;
--accent-neutral: #e0ddd6;
--font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-stack);
background-color: var(--bg-color);
color: var(--text-main);
line-height: 1.6;
font-size: 14px;
-webkit-font-smoothing: antialiased;
padding: 40px 60px;
max-width: 1400px;
margin: 0 auto;
}
header {
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: flex-end;
}
h1 {
font-size: 20px;
font-weight: 500;
letter-spacing: 0.5px;
}
.subtitle {
color: var(--text-muted);
font-size: 13px;
}
section {
margin-bottom: 60px;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 24px;
gap: 12px;
}
.section-number {
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
color: var(--accent-warm);
font-size: 12px;
border: 1px solid var(--accent-warm);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
h2 {
font-size: 16px;
font-weight: 600;
color: var(--text-main);
}
.desc {
color: var(--text-muted);
font-size: 13px;
margin-left: auto;
}
/* Visualization Containers */
.vis-container {
width: 100%;
overflow-x: auto;
}
/* 1. Differences */
.diff-block {
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
font-size: 13px;
background: #fff;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 16px;
}
.diff-line {
display: flex;
padding: 2px 8px;
border-radius: 2px;
}
.diff-line.add { background-color: var(--color-add); color: var(--color-add-text); }
.diff-line.remove { background-color: var(--color-remove); color: var(--color-remove-text); }
.diff-line.context { color: var(--text-muted); }
.diff-num { width: 30px; user-select: none; opacity: 0.5; }
/* 2. Relationships */
.rel-grid {
display: grid;
grid-template-columns: 1fr 100px 1fr;
gap: 20px;
align-items: center;
}
.rel-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.rel-item {
padding: 12px 16px;
background: #fff;
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.rel-item:hover, .rel-item.active {
border-color: var(--accent-cool);
background-color: rgba(86, 126, 150, 0.05);
}
.rel-item-title { font-weight: 500; }
.rel-item-meta { font-size: 12px; color: var(--text-muted); }
.rel-lines {
position: relative;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.rel-lines svg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
path.link {
fill: none;
stroke: var(--border-color);
stroke-width: 1.5;
transition: stroke 0.3s ease;
}
path.link.active {
stroke: var(--accent-cool);
stroke-width: 2;
}
/* 3. Sequences */
.timeline {
display: flex;
align-items: flex-start;
position: relative;
padding-top: 10px;
}
.timeline::before {
content: '';
position: absolute;
top: 15px;
left: 0;
right: 0;
height: 1px;
background: var(--border-color);
z-index: 1;
}
.timeline-node {
flex: 1;
position: relative;
z-index: 2;
padding-right: 20px;
}
.timeline-dot {
width: 11px;
height: 11px;
background: var(--bg-color);
border: 2px solid var(--accent-cool);
border-radius: 50%;
margin-bottom: 12px;
transition: background 0.2s;
cursor: pointer;
}
.timeline-dot:hover { background: var(--accent-cool); }
.timeline-date { font-size: 12px; color: var(--text-muted); margin-bottom: 4px; }
.timeline-title { font-weight: 500; font-size: 13px; }
/* 4. Structures */
.tree-container {
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
font-size: 13px;
line-height: 1.8;
}
.tree-node {
display: flex;
align-items: center;
cursor: pointer;
padding: 4px 0;
}
.tree-node:hover { background: rgba(0,0,0,0.02); }
.tree-toggle {
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
margin-right: 8px;
transition: transform 0.2s;
}
.tree-toggle.open { transform: rotate(90deg); }
.tree-children {
padding-left: 24px;
border-left: 1px solid var(--border-color);
margin-left: 7px;
}
.tree-key { color: var(--accent-cool); }
.tree-value { color: var(--text-main); margin-left: 8px; }
.tree-type { color: var(--text-muted); font-size: 11px; margin-left: 8px; }
/* 5. State Changes */
.state-flow {
display: flex;
gap: 8px;
align-items: center;
}
.state-pill {
padding: 6px 16px;
background: #fff;
border: 1px solid var(--border-color);
border-radius: 16px;
font-size: 13px;
color: var(--text-muted);
cursor: pointer;
transition: all 0.2s;
}
.state-pill.active {
background: var(--accent-warm);
border-color: var(--accent-warm);
color: #fff;
}
.state-arrow {
color: var(--border-color);
}
.state-details {
margin-top: 20px;
padding: 16px;
background: #fff;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 13px;
}
/* 6. Emphasis */
.emphasis-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 40px;
}
.text-block {
font-size: 14px;
line-height: 1.8;
color: var(--text-muted);
}
.hl {
color: var(--text-main);
background: rgba(217, 119, 87, 0.1);
padding: 0 4px;
border-bottom: 1px solid rgba(217, 119, 87, 0.3);
cursor: pointer;
transition: background 0.2s;
}
.hl:hover, .hl.active {
background: rgba(217, 119, 87, 0.2);
}
.key-info-panel {
border-left: 2px solid var(--accent-warm);
padding-left: 20px;
}
.info-item {
margin-bottom: 16px;
display: none;
}
.info-item.active { display: block; }
.info-label { font-size: 11px; text-transform: uppercase; color: var(--text-muted); letter-spacing: 0.5px; }
.info-value { font-size: 14px; font-weight: 500; margin-top: 4px; }
</style>
</head>
<body>
<header>
<div>
<h1>HTML Artifact Component Design</h1>
<div class="subtitle">Visualizing structural data beyond Markdown constraints</div>
</div>
<div class="desc">JSON Renderer Reference</div>
</header>
<!-- FEATURE: Visualizes text or code differences using an inline diff approach with subtle color coding. -->
<section>
<div class="section-header">
<div class="section-number">1</div>
<h2>看差异 (Differences)</h2>
<div class="desc">Configuration Drift Analysis</div>
</div>
<div class="vis-container">
<div class="diff-block">
<div class="diff-line context"><span class="diff-num">41</span><span> "server": {</span></div>
<div class="diff-line context"><span class="diff-num">42</span><span> "host": "0.0.0.0",</span></div>
<div class="diff-line remove"><span class="diff-num">43</span><span>- "port": 8080,</span></div>
<div class="diff-line add"><span class="diff-num">43</span><span>+ "port": 8443,</span></div>
<div class="diff-line context"><span class="diff-num">44</span><span> "ssl": {</span></div>
<div class="diff-line remove"><span class="diff-num">45</span><span>- "enabled": false</span></div>
<div class="diff-line add"><span class="diff-num">45</span><span>+ "enabled": true,</span></div>
<div class="diff-line add"><span class="diff-num">46</span><span>+ "certPath": "/etc/ssl/certs/main.pem"</span></div>
<div class="diff-line context"><span class="diff-num">47</span><span> }</span></div>
<div class="diff-line context"><span class="diff-num">48</span><span> }</span></div>
</div>
</div>
</section>
<!-- FEATURE: Visualizes relationships between entities. Hovering over a source item highlights the connecting SVG path to the target. -->
<section>
<div class="section-header">
<div class="section-number">2</div>
<h2>看关系 (Relationships)</h2>
<div class="desc">Data Pipeline Mapping</div>
</div>
<div class="vis-container">
<div class="rel-grid">
<ul class="rel-list" id="rel-source">
<li class="rel-item" data-target="target-1">
<div class="rel-item-title">User Profile Events</div>
<div class="rel-item-meta">Kafka Topic: events.user</div>
</li>
<li class="rel-item" data-target="target-2">
<div class="rel-item-title">Transaction Logs</div>
<div class="rel-item-meta">S3 Bucket: prod-tx-logs</div>
</li>
</ul>
<div class="rel-lines">
<svg>
<path class="link" id="link-1" d="M 0 35 C 50 35, 50 35, 100 35" />
<path class="link" id="link-2" d="M 0 115 C 50 115, 50 35, 100 35" />
</svg>
</div>
<ul class="rel-list">
<li class="rel-item" id="target-1">
<div class="rel-item-title">Customer Analytics Warehouse</div>
<div class="rel-item-meta">Snowflake schema: analytics.core</div>
</li>
</ul>
</div>
</div>
</section>
<!-- FEATURE: Visualizes a sequence of events over time on a horizontal timeline. -->
<section>
<div class="section-header">
<div class="section-number">3</div>
<h2>看顺序 (Sequences)</h2>
<div class="desc">Deployment Lifecycle</div>
</div>
<div class="vis-container">
<div class="timeline">
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-date">10:02 AM</div>
<div class="timeline-title">Build triggered</div>
</div>
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-date">10:05 AM</div>
<div class="timeline-title">Tests passed</div>
</div>
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-date">10:08 AM</div>
<div class="timeline-title">Image pushed to ECR</div>
</div>
<div class="timeline-node">
<div class="timeline-dot" style="border-color: var(--accent-warm);"></div>
<div class="timeline-date">10:12 AM</div>
<div class="timeline-title" style="color: var(--accent-warm);">Canary rollout</div>
</div>
<div class="timeline-node">
<div class="timeline-dot" style="border-color: var(--border-color);"></div>
<div class="timeline-date">Pending</div>
<div class="timeline-title" style="color: var(--text-muted);">Full promotion</div>
</div>
</div>
</div>
</section>
<!-- FEATURE: Visualizes hierarchical structure using an expandable/collapsible tree layout. -->
<section>
<div class="section-header">
<div class="section-number">4</div>
<h2>看结构 (Structures)</h2>
<div class="desc">API Response Payload</div>
</div>
<div class="vis-container">
<div class="tree-container">
<div class="tree-node" onclick="toggleTree(this)">
<span class="tree-toggle open">▶</span>
<span class="tree-key">data</span>
<span class="tree-type">Object</span>
</div>
<div class="tree-children">
<div class="tree-node">
<span class="tree-toggle" style="visibility: hidden;">▶</span>
<span class="tree-key">id</span>:
<span class="tree-value">"usr_9x8f72a"</span>
<span class="tree-type">String</span>
</div>
<div class="tree-node" onclick="toggleTree(this)">
<span class="tree-toggle open">▶</span>
<span class="tree-key">permissions</span>
<span class="tree-type">Array[2]</span>
</div>
<div class="tree-children">
<div class="tree-node">
<span class="tree-toggle" style="visibility: hidden;">▶</span>
<span class="tree-key">0</span>:
<span class="tree-value">"read:invoices"</span>
</div>
<div class="tree-node">
<span class="tree-toggle" style="visibility: hidden;">▶</span>
<span class="tree-key">1</span>:
<span class="tree-value">"write:reports"</span>
</div>
</div>
<div class="tree-node">
<span class="tree-toggle" style="visibility: hidden;">▶</span>
<span class="tree-key">lastLogin</span>:
<span class="tree-value">"2023-10-24T08:12:00Z"</span>
<span class="tree-type">DateTime</span>
</div>
</div>
</div>
</div>
</section>
<!-- FEATURE: Visualizes state transitions. Clicking a pill updates the details area below. -->
<section>
<div class="section-header">
<div class="section-number">5</div>
<h2>看状态变化 (State Changes)</h2>
<div class="desc">Order Fulfillment State Machine</div>
</div>
<div class="vis-container">
<div class="state-flow" id="state-flow">
<div class="state-pill" data-state="created">Created</div>
<span class="state-arrow">→</span>
<div class="state-pill active" data-state="processing">Processing</div>
<span class="state-arrow">→</span>
<div class="state-pill" data-state="shipped">Shipped</div>
<span class="state-arrow">→</span>
<div class="state-pill" data-state="delivered">Delivered</div>
</div>
<div class="state-details" id="state-details">
Inventory allocated. Awaiting carrier pickup schedule from warehouse C.
</div>
</div>
</section>
<!-- FEATURE: Visualizes emphasis within text. Clicking highlighted text reveals structured metadata in the side panel. -->
<section>
<div class="section-header">
<div class="section-number">6</div>
<h2>看重点 (Emphasis)</h2>
<div class="desc">Entity Extraction from Unstructured Log</div>
</div>
<div class="vis-container">
<div class="emphasis-grid">
<div class="text-block" id="emphasis-text">
System detected unusual memory consumption on node <span class="hl active" data-info="info-node">worker-pool-b-04</span>.
Process <span class="hl" data-info="info-proc">java-ml-service</span> exceeded threshold, peaking at
<span class="hl" data-info="info-metric">14.2 GB</span> RAM usage. Auto-scaling policy triggered eviction
at <span class="hl" data-info="info-time">2023-10-25 14:33:02 UTC</span>.
</div>
<div class="key-info-panel" id="emphasis-panel">
<div class="info-item active" id="info-node">
<div class="info-label">Infrastructure Node</div>
<div class="info-value">worker-pool-b-04</div>
<div class="info-label" style="margin-top: 8px;">Zone</div>
<div class="info-value" style="font-size: 13px; color: var(--text-muted);">us-east-1c</div>
</div>
<div class="info-item" id="info-proc">
<div class="info-label">Target Process</div>
<div class="info-value">java-ml-service</div>
</div>
<div class="info-item" id="info-metric">
<div class="info-label">Metric Anomaly</div>
<div class="info-value">Memory: 14.2 GB</div>
</div>
<div class="info-item" id="info-time">
<div class="info-label">Timestamp</div>
<div class="info-value">2023-10-25 14:33:02 UTC</div>
</div>
</div>
</div>
</div>
</section>
<script>
// 2. Relationships Interaction
const relItems = document.querySelectorAll('#rel-source .rel-item');
const links = document.querySelectorAll('.link');
const targetItem = document.getElementById('target-1');
relItems.forEach((item, index) => {
item.addEventListener('mouseenter', () => {
links[index].classList.add('active');
targetItem.classList.add('active');
});
item.addEventListener('mouseleave', () => {
links[index].classList.remove('active');
targetItem.classList.remove('active');
});
});
// 4. Structures Interaction
function toggleTree(element) {
const toggle = element.querySelector('.tree-toggle');
const children = element.nextElementSibling;
if (toggle && children && children.classList.contains('tree-children')) {
const isOpen = toggle.classList.contains('open');
if (isOpen) {
toggle.classList.remove('open');
toggle.style.transform = 'rotate(0deg)';
children.style.display = 'none';
} else {
toggle.classList.add('open');
toggle.style.transform = 'rotate(90deg)';
children.style.display = 'block';
}
}
}
// 5. State Changes Interaction
const statePills = document.querySelectorAll('.state-pill');
const stateDetails = document.getElementById('state-details');
const stateMessages = {
'created': 'Order received and payment verified. Forwarded to fulfillment system.',
'processing': 'Inventory allocated. Awaiting carrier pickup schedule from warehouse C.',
'shipped': 'Package handed over to FedEx. Tracking number generated: FX839201992.',
'delivered': 'Proof of delivery recorded at front desk.'
};
statePills.forEach(pill => {
pill.addEventListener('click', () => {
statePills.forEach(p => p.classList.remove('active'));
pill.classList.add('active');
stateDetails.textContent = stateMessages[pill.dataset.state];
});
});
// 6. Emphasis Interaction
const hls = document.querySelectorAll('.hl');
const infoItems = document.querySelectorAll('.info-item');
hls.forEach(hl => {
hl.addEventListener('mouseenter', () => {
hls.forEach(h => h.classList.remove('active'));
infoItems.forEach(i => i.classList.remove('active'));
hl.classList.add('active');
document.getElementById(hl.dataset.info).classList.add('active');
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Architecture Animation Patterns</title>
<style>
/* Design System: warm-nomad */
:root {
--bg-base: #FAF9F6;
--bg-surface: #FFFFFF;
--border-color: #E5E2DC;
--text-main: #33312E;
--text-muted: #85827D;
--accent-warm: #D4A373;
--accent-green: #8A9A5B;
--accent-red: #C97A7E;
--accent-blue: #7393B3;
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 2px;
--timing: 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-base);
color: var(--text-main);
font-family: var(--font-ui);
font-size: 13px;
line-height: 1.5;
padding: 2rem;
-webkit-font-smoothing: antialiased;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
h1 {
font-size: 16px;
font-weight: 500;
letter-spacing: -0.02em;
}
button.toggle-motion {
background: transparent;
border: 1px solid var(--border-color);
padding: 4px 12px;
border-radius: var(--radius);
color: var(--text-main);
font-family: var(--font-ui);
font-size: 12px;
cursor: pointer;
transition: background 0.2s;
}
button.toggle-motion:hover {
background: var(--bg-surface);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.card {
background: var(--bg-surface);
border: 1px solid var(--border-color);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
position: relative;
}
.card-title {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
border-bottom: 1px dashed var(--border-color);
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
/* Animation Pause State */
body.motion-paused * {
animation-play-state: paused !important;
transition: none !important;
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* --- 1. Data Flow --- */
.flow-container {
position: relative;
height: 120px;
display: flex;
align-items: center;
justify-content: space-between;
}
.node {
border: 1px solid var(--border-color);
padding: 0.5rem;
background: var(--bg-base);
z-index: 2;
font-family: var(--font-mono);
font-size: 11px;
}
.flow-svg {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
z-index: 1;
pointer-events: none;
}
.flow-path {
fill: none;
stroke: var(--border-color);
stroke-width: 1;
}
.flow-packet {
fill: var(--accent-warm);
offset-path: path('M 50 60 L 250 60');
animation: movePacket 2s linear infinite;
}
@keyframes movePacket {
0% { offset-distance: 0%; opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { offset-distance: 100%; opacity: 0; }
}
/* --- 2. State Machine --- */
.state-machine {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: center;
height: 120px;
}
.state-node {
padding: 4px 12px;
border: 1px solid var(--border-color);
color: var(--text-muted);
transition: all var(--timing);
font-size: 12px;
}
.state-node.active {
border-color: var(--text-main);
color: var(--text-main);
background: var(--bg-base);
}
.state-arrow {
color: var(--border-color);
font-size: 10px;
}
/* --- 3. Async Task Flow (Stepper) --- */
.stepper {
display: flex;
flex-direction: column;
gap: 1rem;
height: 120px;
justify-content: center;
}
.step {
display: flex;
align-items: center;
gap: 1rem;
opacity: 0.4;
transition: opacity var(--timing);
}
.step.active, .step.completed {
opacity: 1;
}
.step-indicator {
width: 12px; height: 12px;
border: 1px solid var(--border-color);
border-radius: 50%;
position: relative;
transition: all var(--timing);
}
.step.completed .step-indicator {
background: var(--border-color);
}
.step.active .step-indicator {
border-color: var(--accent-blue);
background: var(--bg-surface);
}
.step.active .step-indicator::after {
content: '';
position: absolute;
top: 2px; left: 2px; right: 2px; bottom: 2px;
background: var(--accent-blue);
border-radius: 50%;
animation: pulse 2s infinite;
}
.step-label { font-size: 12px; }
@keyframes pulse {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1); }
}
/* --- 4. Diff Review --- */
.diff-container {
font-family: var(--font-mono);
font-size: 11px;
height: 120px;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 2px;
}
.diff-line {
padding: 2px 4px;
color: var(--text-muted);
transition: background-color var(--timing), color var(--timing);
white-space: pre;
}
.diff-line.add.highlight {
background-color: rgba(138, 154, 91, 0.15); /* accent-green */
color: var(--text-main);
}
.diff-line.remove.highlight {
background-color: rgba(201, 122, 126, 0.15); /* accent-red */
color: var(--text-main);
text-decoration: line-through;
}
/* --- 5. Metrics & Sparkline --- */
.metric-container {
display: flex;
flex-direction: column;
justify-content: center;
height: 120px;
}
.metric-value {
font-size: 24px;
font-weight: 400;
margin-bottom: 0.5rem;
font-family: var(--font-mono);
}
.sparkline-svg {
width: 100%;
height: 40px;
}
.sparkline-path {
fill: none;
stroke: var(--accent-green);
stroke-width: 1.5;
stroke-dasharray: 200;
stroke-dashoffset: 200;
}
.sparkline-path.draw {
animation: drawLine 2s forwards ease-out;
}
@keyframes drawLine {
to { stroke-dashoffset: 0; }
}
/* --- 6. Attention Spotlight --- */
.spotlight-wrapper {
position: relative;
height: 120px;
display: flex;
gap: 0.5rem;
}
.spotlight-card {
flex: 1;
border: 1px solid var(--border-color);
padding: 0.5rem;
font-size: 11px;
transition: all var(--timing);
background: var(--bg-surface);
}
.spotlight-overlay {
position: absolute;
top: -10px; left: -10px; right: -10px; bottom: -10px;
background: rgba(250, 249, 246, 0.8); /* bg-base with opacity */
pointer-events: none;
opacity: 0;
transition: opacity var(--timing);
z-index: 10;
}
.spotlight-wrapper.active .spotlight-overlay {
opacity: 1;
}
.spotlight-wrapper.active .spotlight-card.risk {
position: relative;
z-index: 20;
border-color: var(--accent-red);
background: var(--bg-surface);
box-shadow: 0 0 0 1px var(--accent-red);
}
</style>
</head>
<body>
<header>
<h1>Architecture Animation Patterns</h1>
<button class="toggle-motion" id="motionToggle">Pause Animations</button>
</header>
<div class="grid">
<!-- FEATURE: Data flow animation along an SVG path to represent API calls or capacity consumption -->
<div class="card">
<div class="card-title">1. Data Flow</div>
<div class="flow-container">
<div class="node">Gateway</div>
<svg class="flow-svg" viewBox="0 0 300 120">
<path class="flow-path" d="M 50 60 L 250 60" />
<circle class="flow-packet" r="3" />
</svg>
<div class="node">Ledger Svc</div>
</div>
</div>
<!-- FEATURE: State machine node switching to visualize account lifecycle -->
<div class="card">
<div class="card-title">2. State Machine</div>
<div class="state-machine" id="stateMachine">
<div class="state-node active" data-state="0">Pending</div>
<div class="state-arrow">→</div>
<div class="state-node" data-state="1">Trialing</div>
<div class="state-arrow">→</div>
<div class="state-node" data-state="2">Active</div>
</div>
</div>
<!-- FEATURE: Asynchronous task flow stepper (reserve -> confirm) -->
<div class="card">
<div class="card-title">3. Async Flow</div>
<div class="stepper" id="stepper">
<div class="step completed" data-step="0">
<div class="step-indicator"></div>
<div class="step-label">Reserve Funds</div>
</div>
<div class="step active" data-step="1">
<div class="step-indicator"></div>
<div class="step-label">Await Callback</div>
</div>
<div class="step" data-step="2">
<div class="step-indicator"></div>
<div class="step-label">Commit Transaction</div>
</div>
</div>
</div>
<!-- FEATURE: Code diff review with sequential row-level highlighting -->
<div class="card">
<div class="card-title">4. Diff Review</div>
<div class="diff-container" id="diffViewer">
<div class="diff-line"> func Calculate() {</div>
<div class="diff-line remove"> rate := 0.05</div>
<div class="diff-line add"> rate := getDynamicRate()</div>
<div class="diff-line add"> if rate < 0 { return err }</div>
<div class="diff-line"> return amount * rate</div>
<div class="diff-line"> }</div>
</div>
</div>
<!-- FEATURE: Metric number counting up and sparkline drawing -->
<div class="card">
<div class="card-title">5. Metric & Trend</div>
<div class="metric-container">
<div class="metric-value" id="metricCounter">99.00%</div>
<svg class="sparkline-svg" viewBox="0 0 100 40" preserveAspectRatio="none">
<path class="sparkline-path" id="sparkline" d="M 0 30 L 20 25 L 40 28 L 60 15 L 80 20 L 100 5" />
</svg>
</div>
</div>
<!-- FEATURE: Attention spotlight to gently guide focus to a risk element -->
<div class="card">
<div class="card-title">6. Attention Spotlight</div>
<div class="spotlight-wrapper" id="spotlight">
<div class="spotlight-overlay"></div>
<div class="spotlight-card">
<div>Node A</div>
<div style="color:var(--text-muted);margin-top:4px;">Healthy</div>
</div>
<div class="spotlight-card risk">
<div>Node B</div>
<div style="color:var(--accent-red);margin-top:4px;">Latency spike</div>
</div>
<div class="spotlight-card">
<div>Node C</div>
<div style="color:var(--text-muted);margin-top:4px;">Healthy</div>
</div>
</div>
</div>
</div>
<script>
let isPaused = false;
// Toggle Motion
const toggleBtn = document.getElementById('motionToggle');
toggleBtn.addEventListener('click', () => {
isPaused = !isPaused;
document.body.classList.toggle('motion-paused', isPaused);
toggleBtn.textContent = isPaused ? 'Play Animations' : 'Pause Animations';
});
// 2. State Machine Logic
const stateNodes = document.querySelectorAll('#stateMachine .state-node');
let currentState = 0;
setInterval(() => {
if (isPaused) return;
stateNodes.forEach(n => n.classList.remove('active'));
currentState = (currentState + 1) % stateNodes.length;
stateNodes[currentState].classList.add('active');
}, 2500);
// 3. Stepper Logic
const steps = document.querySelectorAll('#stepper .step');
let currentStep = 1;
setInterval(() => {
if (isPaused) return;
steps.forEach(s => {
s.classList.remove('active', 'completed');
const stepNum = parseInt(s.dataset.step);
if (stepNum < currentStep) s.classList.add('completed');
if (stepNum === currentStep) s.classList.add('active');
});
currentStep = (currentStep + 1) % 4; // 0, 1, 2, 3(reset)
if(currentStep === 3) {
currentStep = 0; // reset to beginning quickly
}
}, 2000);
// 4. Diff Review Logic
const diffLines = document.querySelectorAll('#diffViewer .add, #diffViewer .remove');
let diffIndex = 0;
setInterval(() => {
if (isPaused) return;
if (diffIndex === 0) {
diffLines.forEach(l => l.classList.remove('highlight'));
}
if (diffIndex < diffLines.length) {
diffLines[diffIndex].classList.add('highlight');
}
diffIndex++;
if (diffIndex > diffLines.length + 2) diffIndex = 0;
}, 800);
// 5. Metric Logic
const counterEl = document.getElementById('metricCounter');
const sparkline = document.getElementById('sparkline');
let val = 99.00;
function resetMetric() {
if (isPaused) return;
val = 99.00;
sparkline.classList.remove('draw');
// trigger reflow
void sparkline.offsetWidth;
sparkline.classList.add('draw');
let interval = setInterval(() => {
if (isPaused) {
clearInterval(interval);
return;
}
val += 0.05;
counterEl.textContent = val.toFixed(2) + '%';
if (val >= 99.99) {
counterEl.textContent = '99.99%';
clearInterval(interval);
}
}, 50);
}
resetMetric();
setInterval(resetMetric, 4000);
// 6. Spotlight Logic
const spotlightWrapper = document.getElementById('spotlight');
setInterval(() => {
if (isPaused) return;
spotlightWrapper.classList.toggle('active');
}, 3000);
</script>
</body>
</html>
html-doc
html-doc is a general-purpose Skill for turning compact JSON into readable, visual-first HTML review documents.
Use it when Markdown makes the information too flat: wide comparisons, architecture maps, state changes, code diffs, evidence boards, nested structures, decision matrices, and lightweight editors.
What It Does
- Write simple JSON.
- The bundled renderer creates a warm, flat, human-readable HTML document.
- The visual system is handled by the renderer, so the JSON stays focused on content, components, relationships, and useful interactions.
- Tables include sticky headers, frozen first columns, default stripe rows, compact badges, progress cells, search, filters, sorting, grouping, and expandable details.
- Visible text supports light inline
codeand**bold**formatting across components.
Render An Example
node scripts/render-html-doc.mjs examples/component-showcase.json dist/component-showcase.htmlThen open the generated HTML file in a browser.
Files
SKILL.md: the Skill entrypointreferences/schema.md: JSON component referencescripts/render-html-doc.mjs: renderer CLIsrc/: renderer, styles, and client interactionsexamples/: JSON examplesdist/: generated HTML examples
Install Locally
Copy this folder into your local skills directory:
cp -R . /path/to/skills/html-doc