Install
openclaw skills install @fbbyqsyea/premium-html-studioGenerate professional, publication-quality single-file HTML technical documents and proposals with premium design, dark mode, syntax highlighting, and WCAG 2...
openclaw skills install @fbbyqsyea/premium-html-studioGenerate professional, publication-quality HTML documentation pages AND structured proposals with a sophisticated design system.
Before diving into the design system, here are 3 complete working examples you can use as starting points:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redis 缓存层技术方案</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;0,800;0,900;1,400;1,700&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
<style>
:root {
--bg: #fafaf9;
--surface: #ffffff;
--border: #e5e7eb;
--text: #111827;
--text-muted: #6b7280;
--brand: #059669;
--brand-dark: #047857;
--brand-light: #d1fae5;
--brand-soft: #ecfdf5;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 12px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04);
--shadow-brand: 0 8px 24px rgba(5,150,105,0.20), 0 2px 8px rgba(5,150,105,0.10);
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
--sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
--r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 20px;
--ease: cubic-bezier(0.16, 1, 0.3, 1);
--font-display: 'Playfair Display', Georgia, serif;
--font-body: 'Inter', 'PingFang SC', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
[data-theme="dark"] {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #c9d1d9;
--text-muted: #8b949e;
--brand: #10b981;
--brand-dark: #059669;
--brand-light: #064e3b;
--brand-soft: #022c22;
}
* { box-sizing: border-box; }
body {
font-family: var(--font-body);
background: var(--bg);
color: var(--text);
line-height: 1.65;
margin: 0;
transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
.container { max-width: 1280px; margin: 0 auto; padding: 0 var(--sp-5) var(--sp-7); }
header.doc-header {
background: linear-gradient(135deg, var(--brand-dark) 0%, var(--brand) 100%);
color: white;
padding: var(--sp-8) var(--sp-7) var(--sp-6);
text-align: center;
max-width: 1280px;
margin: var(--sp-5) auto var(--sp-5);
border-radius: var(--r-xl);
box-shadow: var(--shadow-brand);
}
.eyebrow {
font-family: var(--font-mono);
font-size: 0.78em;
letter-spacing: 0.25em;
text-transform: uppercase;
opacity: 0.85;
margin-bottom: var(--sp-3);
display: inline-block;
padding: 4px 14px;
background: rgba(255,255,255,0.15);
border-radius: 20px;
backdrop-filter: blur(10px);
}
h1 {
font-family: var(--font-display);
font-weight: 800;
font-size: 3em;
line-height: 1.05;
letter-spacing: -0.02em;
margin: 0 0 var(--sp-3);
}
h1 em { font-style: italic; color: #fde68a; }
section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: var(--sp-6) var(--sp-7);
margin-bottom: var(--sp-5);
box-shadow: var(--shadow-sm);
transition: box-shadow 0.3s var(--ease);
}
section:hover { box-shadow: var(--shadow-md); }
h2 {
font-family: var(--font-display);
font-size: 1.75em;
font-weight: 800;
margin: 0 0 var(--sp-5);
padding-bottom: var(--sp-3);
border-bottom: 2px solid var(--border);
color: var(--text);
}
pre[class*="language-"] {
background: #1e1e1e !important;
border-radius: var(--r-md);
padding: var(--sp-5) !important;
margin: var(--sp-4) 0 var(--sp-5);
font-size: 0.85em !important;
box-shadow: var(--shadow-md);
}
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
box-shadow: var(--shadow-sm);
transition: all 0.2s var(--ease);
z-index: 1000;
}
.theme-toggle:hover {
transform: scale(1.1);
box-shadow: var(--shadow-md);
}
@media (max-width: 900px) {
.container { padding: 0 var(--sp-4) var(--sp-6); }
section { padding: var(--sp-5); }
}
@media (max-width: 600px) {
body { font-size: 15px; }
h1 { font-size: 2em; }
section { padding: var(--sp-4); }
}
@media print {
.theme-toggle { display: none; }
section { break-inside: avoid; }
}
</style>
</head>
<body>
<button class="theme-toggle" onclick="toggleTheme()" aria-label="切换深色模式">🌓</button>
<div class="container">
<header class="doc-header">
<span class="eyebrow">Technical Proposal · v1.0</span>
<h1>Redis 缓存层 · <em>技术方案</em></h1>
</header>
<section>
<h2>背景</h2>
<p>当前系统直接查询数据库,响应时间 200-500ms,需要优化到 50ms 以内。</p>
</section>
<section>
<h2>方案</h2>
<p>引入 Redis 作为缓存层,预计提升 8-10 倍性能。</p>
</section>
</div>
<script>
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
</body>
</html>
[See existing: docs/llm_prompt_engineering.html]
[See existing: docs/redis-cache-proposal.html]
All premium HTML documents support dark mode out of the box:
/* Light mode (default) */
:root {
--bg: #fafaf9;
--surface: #ffffff;
--text: #111827;
}
/* Dark mode */
[data-theme="dark"] {
--bg: #0d1117;
--surface: #161b22;
--text: #c9d1d9;
}
Toggle button:
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode">🌓</button>
<script>
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
}
</script>
All generated documents must meet WCAG 2.1 AA standards:
<!-- Tab buttons -->
<button class="tab active" data-tab="en"
aria-selected="true"
aria-controls="tab-en"
role="tab">English</button>
<!-- Callouts -->
<div class="callout danger" role="alert">
<div class="callout-title">⚠️ Warning</div>
</div>
<!-- Theme toggle -->
<button aria-label="Toggle dark mode">🌓</button>
/* Focus indicators */
.tab:focus, button:focus {
outline: 2px solid var(--brand);
outline-offset: 2px;
}
/* Tab navigation */
.tabs {
display: flex;
gap: var(--sp-1);
}
.tab:focus-visible {
outline: 2px solid var(--brand);
}
<!-- Decorative icons -->
<span aria-hidden="true">🌐</span> English
<!-- Meaningful icons -->
<button aria-label="Search documentation">
<svg aria-hidden="true">...</svg>
</button>
Support 5 brand color presets:
/* Emerald (default) */
:root { --brand: #059669; --brand-dark: #047857; }
/* Indigo */
:root { --brand: #4338ca; --brand-dark: #3730a3; }
/* Purple */
:root { --brand: #7c3aed; --brand-dark: #6d28d9; }
/* Sunset */
:root { --brand: #ea580c; --brand-dark: #c2410c; }
/* Ocean */
:root { --brand: #0284c7; --brand-dark: #0369a1; }
Usage:
<link rel="stylesheet" href="styles-emerald.css">
<!-- or -->
<link rel="stylesheet" href="styles-indigo.css">
:root {
--transition-fast: 150ms ease-out;
--transition-normal: 300ms ease-in-out;
--transition-slow: 500ms ease-in-out;
}
/* Cards */
.card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
transition: transform var(--transition-fast),
box-shadow var(--transition-fast);
}
/* Buttons */
button:hover {
transform: scale(1.02);
transition: transform var(--transition-fast);
}
/* Links */
a:hover {
color: var(--brand);
transition: color var(--transition-fast);
}
// Fade in on scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
}
});
});
document.querySelectorAll('.reveal').forEach(el => {
observer.observe(el);
});
Support 4 breakpoints for all screen sizes:
/* Mobile (< 600px) */
@media (max-width: 600px) {
body { font-size: 15px; }
h1 { font-size: 2em; }
.container { padding: 0 var(--sp-4) var(--sp-6); }
section { padding: var(--sp-4); }
.grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}
/* Tablet (600px - 900px) */
@media (min-width: 601px) and (max-width: 900px) {
.container { padding: 0 var(--sp-4) var(--sp-6); }
section { padding: var(--sp-5); }
.grid-3, .grid-4 { grid-template-columns: 1fr 1fr; }
}
/* Desktop (900px - 1440px) - default */
/* No media query needed */
/* Large Desktop (> 1440px) */
@media (min-width: 1441px) {
.container { max-width: 1440px; }
h1 { font-size: 3.5em; }
}
/* Ultra-wide (> 1920px) */
@media (min-width: 1921px) {
.container { max-width: 1600px; }
body { font-size: 18px; }
}
Optimize documents for printing:
@media print {
/* Hide interactive elements */
.theme-toggle, .tabs, button { display: none; }
/* Optimize for paper */
body {
font-size: 12pt;
color: black;
background: white;
}
/* Page breaks */
section {
break-inside: avoid;
page-break-inside: avoid;
}
h2 {
break-after: avoid;
page-break-after: avoid;
}
/* Links */
a {
text-decoration: underline;
color: black;
}
a[href^="http"]:after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
/* Images */
img {
max-width: 100%;
page-break-inside: avoid;
}
/* Page margins */
@page {
margin: 2cm;
}
/* Print-only content */
.print-only {
display: block !important;
}
.no-print {
display: none !important;
}
}
Print-only content:
<div class="print-only" style="display: none;">
<p>Printed on: <span id="print-date"></span></p>
<script>
document.getElementById('print-date').textContent = new Date().toLocaleDateString();
</script>
</div>
Support 15+ programming languages:
<!-- Include Prism.js with all languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
Supported languages:
Usage:
<pre class="code" data-lang="typescript">
<code class="language-typescript">
const greeting: string = "Hello";
console.log(greeting);
</code>
</pre>
Autoloader configuration:
<script>
Prism.plugins.autoloader.languages_path = "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/";
</script>
For long documents, use collapsible sections:
<details class="collapsible">
<summary class="collapsible-header">
<span class="collapsible-icon">▶</span>
<span>点击展开详细内容</span>
</summary>
<div class="collapsible-content">
<p>折叠的内容...</p>
</div>
</details>
CSS:
.collapsible {
border: 1px solid var(--border);
border-radius: var(--r-md);
margin: var(--sp-4) 0;
overflow: hidden;
}
.collapsible-header {
padding: var(--sp-3) var(--sp-4);
background: var(--surface);
cursor: pointer;
display: flex;
align-items: center;
gap: var(--sp-2);
font-weight: 600;
transition: background var(--transition-fast);
}
.collapsible-header:hover {
background: var(--primary-light);
}
.collapsible-icon {
transition: transform var(--transition-fast);
font-size: 0.8em;
}
details[open] .collapsible-icon {
transform: rotate(90deg);
}
.collapsible-content {
padding: var(--sp-4);
background: var(--bg);
border-top: 1px solid var(--border);
}
/* Accessibility */
.collapsible-header:focus {
outline: 2px solid var(--brand);
outline-offset: -2px;
}
JavaScript for smooth animation:
document.querySelectorAll('.collapsible').forEach(details => {
details.addEventListener('toggle', () => {
const content = details.querySelector('.collapsible-content');
if (details.open) {
content.style.maxHeight = content.scrollHeight + 'px';
} else {
content.style.maxHeight = '0';
}
});
});
✅ L-shape paths (simple connections):
<path d="M100 100 L100 150 L200 150" stroke="#000" stroke-width="2" fill="none"/>
✅ Bezier curves (complex flows):
<!-- Organic flows, S-curves -->
<path d="M100 100 C150 150, 200 50, 250 100" stroke="#000" stroke-width="2" fill="none"/>
✅ Curved arrows (when L-shape is impractical):
<path d="M100 100 Q150 150, 200 100" stroke="#000" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
✅ Rounded rectangles:
<rect x="50" y="50" width="100" height="80" rx="8" fill="#fff" stroke="#000"/>
When using Bezier curves (C, Q):
Keep control points reasonable
<!-- ✓ GOOD: Smooth, natural curve -->
<path d="M100 100 C150 120, 200 80, 250 100"/>
<!-- ✗ BAD: Extreme control points -->
<path d="M100 100 C500 500, -200 -200, 250 100"/>
Use for organic flows, not simple connections
<!-- ✓ Use Bezier for: Process flows, timelines -->
<path d="M50 100 C150 100, 200 150, 300 150"/>
<!-- ✗ Use L-shape for: Simple A→B connections -->
<path d="M50 100 L150 100 L150 150 L300 150"/>
Test across browsers
<!-- Add browser-specific fallbacks if needed -->
<style>
@supports not (d: path('M0 0')) {
/* Fallback for older browsers */
}
</style>
<defs>
<!-- Straight arrow -->
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#000"/>
</marker>
<!-- Curved arrow (for Bezier paths) -->
<marker id="arrow-curved" viewBox="0 0 10 10" refX="8" refY="5"
markerWidth="8" markerHeight="8" orient="auto-start-reverse">
<path d="M0,2 L8,5 L0,8 z" fill="#000"/>
</marker>
</defs>
Use Mermaid for flowcharts, sequence diagrams, and more:
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'default',
flowchart: { useMaxWidth: true }
});
</script>
Flowchart:
<div class="mermaid">
graph TD
A[用户请求] --> B{缓存命中?}
B -->|是| C[返回缓存数据]
B -->|否| D[查询数据库]
D --> E[写入缓存]
E --> C
</div>
Sequence Diagram:
<div class="mermaid">
sequenceDiagram
participant U as 用户
participant C as 客户端
participant R as Redis
participant D as 数据库
U->>C: 发起请求
C->>R: 查询缓存
alt 缓存命中
R-->>C: 返回数据
else 缓存未命中
R-->>C: 未命中
C->>D: 查询数据库
D-->>C: 返回数据
C->>R: 写入缓存
C-->>U: 返回结果
end
</div>
Supported diagram types:
Add search functionality for long documents:
<!-- Include FlexSearch -->
<script src="https://cdn.jsdelivr.net/npm/flexsearch@0.7.31/dist/flexsearch.bundle.js"></script>
<!-- Search input -->
<div class="search-container">
<input type="text" id="search-input" placeholder="搜索文档..." aria-label="搜索">
<div id="search-results" class="search-results"></div>
</div>
CSS:
.search-container {
position: relative;
max-width: 600px;
margin: var(--sp-5) auto;
}
#search-input {
width: 100%;
padding: var(--sp-3) var(--sp-4);
border: 1px solid var(--border);
border-radius: var(--r-md);
font-size: 1em;
transition: border-color var(--transition-fast);
}
#search-input:focus {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-soft);
}
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-md);
margin-top: var(--sp-2);
max-height: 400px;
overflow-y: auto;
box-shadow: var(--shadow-lg);
display: none;
}
.search-results.active {
display: block;
}
.search-result-item {
padding: var(--sp-3) var(--sp-4);
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background var(--transition-fast);
}
.search-result-item:hover {
background: var(--primary-light);
}
.search-result-item:last-child {
border-bottom: none;
}
JavaScript:
const index = new FlexSearch.Document({
document: {
id: 'id',
index: ['title', 'content']
}
});
// Index all sections
document.querySelectorAll('section').forEach((section, i) => {
const title = section.querySelector('h2')?.textContent || '';
const content = section.textContent;
index.add({ id: i, title, content });
});
// Search on input
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');
searchInput.addEventListener('input', (e) => {
const query = e.target.value;
if (query.length < 2) {
searchResults.classList.remove('active');
return;
}
const results = index.search(query);
searchResults.innerHTML = results.map(r => {
const section = document.querySelectorAll('section')[r.id];
const title = section.querySelector('h2').textContent;
return `<div class="search-result-item" onclick="scrollToSection(${r.id})">
<strong>${title}</strong>
<p>${section.textContent.substring(0, 100)}...</p>
</div>`;
}).join('');
searchResults.classList.add('active');
});
function scrollToSection(id) {
document.querySelectorAll('section')[id].scrollIntoView({ behavior: 'smooth' });
searchResults.classList.remove('active');
searchInput.value = '';
}
For interactive documentation:
<!-- Text input -->
<div class="form-group">
<label for="input-name">名称</label>
<input type="text" id="input-name" class="form-input" placeholder="输入名称">
</div>
<!-- Select -->
<div class="form-group">
<label for="select-lang">语言</label>
<select id="select-lang" class="form-select">
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</div>
<!-- Checkbox -->
<div class="form-group">
<label class="form-checkbox">
<input type="checkbox">
<span>启用深色模式</span>
</label>
</div>
CSS:
.form-group {
margin: var(--sp-4) 0;
}
.form-group label {
display: block;
margin-bottom: var(--sp-2);
font-weight: 600;
color: var(--text);
}
.form-input, .form-select {
width: 100%;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--border);
border-radius: var(--r-sm);
font-size: 0.95em;
transition: border-color var(--transition-fast);
}
.form-input:focus, .form-select:focus {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-soft);
}
.form-checkbox {
display: flex;
align-items: center;
gap: var(--sp-2);
cursor: pointer;
}
.form-checkbox input {
width: 18px;
height: 18px;
cursor: pointer;
}
Ctrl+P (Windows/Linux) or Cmd+P (Mac)@media print {
/* Hide interactive elements */
.theme-toggle, .search-container, .tabs, button, nav {
display: none !important;
}
/* Optimize for paper */
body {
font-size: 11pt;
line-height: 1.5;
color: #000;
background: #fff;
}
/* Page setup */
@page {
size: A4;
margin: 2cm;
}
/* Section breaks */
section {
break-inside: avoid;
page-break-inside: avoid;
}
h2, h3 {
break-after: avoid;
page-break-after: avoid;
}
/* Links */
a {
color: #000;
text-decoration: underline;
}
a[href^="http"]:after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
/* Images */
img {
max-width: 100%;
break-inside: avoid;
page-break-inside: avoid;
}
/* Code blocks */
pre {
font-size: 9pt;
break-inside: avoid;
page-break-inside: avoid;
}
/* Print-only content */
.print-only {
display: block !important;
}
.no-print {
display: none !important;
}
}
Print-only footer:
<div class="print-only" style="display: none; text-align: center; margin-top: 2cm; font-size: 9pt; color: #666;">
<p>Generated by Premium HTML Studio · <span id="print-date"></span></p>
<script>
document.getElementById('print-date').textContent = new Date().toLocaleDateString();
</script>
</div>
Systematic multi-language support:
<!-- Language switcher -->
<div class="lang-switcher">
<select id="lang-select" aria-label="选择语言">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
</select>
</div>
<!-- Multi-language content -->
<section>
<h2>
<span data-lang="en">Background</span>
<span data-lang="zh" hidden>背景</span>
<span data-lang="ja" hidden>背景</span>
</h2>
<p>
<span data-lang="en">Current system latency is 200-500ms.</span>
<span data-lang="zh" hidden>当前系统延迟为 200-500ms。</span>
<span data-lang="ja" hidden>現在のシステムレイテンシは 200-500ms です。</span>
</p>
</section>
CSS:
.lang-switcher {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
}
#lang-select {
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--border);
border-radius: var(--r-sm);
background: var(--surface);
cursor: pointer;
}
[data-lang] {
display: none;
}
[data-lang].active {
display: inline;
}
JavaScript:
const langSelect = document.getElementById('lang-select');
function switchLanguage(lang) {
// Hide all languages
document.querySelectorAll('[data-lang]').forEach(el => {
el.classList.remove('active');
el.hidden = true;
});
// Show selected language
document.querySelectorAll(`[data-lang="${lang}"]`).forEach(el => {
el.classList.add('active');
el.hidden = false;
});
// Save preference
localStorage.setItem('preferred-lang', lang);
}
langSelect.addEventListener('change', (e) => {
switchLanguage(e.target.value);
});
// Load saved preference
const savedLang = localStorage.getItem('preferred-lang') || 'en';
langSelect.value = savedLang;
switchLanguage(savedLang);
Show code changes:
<div class="code-diff">
<div class="diff-header">
<span class="diff-file">config.py</span>
<span class="diff-stats">+3 -1</span>
</div>
<div class="diff-content">
<div class="diff-line diff-old">
<span class="diff-line-num">-</span>
<code>timeout = 30</code>
</div>
<div class="diff-line diff-new">
<span class="diff-line-num">+</span>
<code>timeout = 60</code>
</div>
<div class="diff-line diff-context">
<span class="diff-line-num"> </span>
<code>retries = 3</code>
</div>
</div>
</div>
CSS:
.code-diff {
border: 1px solid var(--border);
border-radius: var(--r-md);
overflow: hidden;
font-family: var(--font-mono);
font-size: 0.85em;
margin: var(--sp-4) 0;
}
.diff-header {
background: var(--surface);
padding: var(--sp-2) var(--sp-3);
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.diff-file {
font-weight: 600;
}
.diff-stats {
font-size: 0.85em;
}
.diff-stats::before {
content: "+";
color: var(--success);
}
.diff-stats::after {
content: " -";
color: var(--danger);
}
.diff-content {
background: var(--bg);
}
.diff-line {
padding: 2px var(--sp-3);
display: flex;
gap: var(--sp-2);
}
.diff-line-num {
width: 20px;
text-align: right;
user-select: none;
opacity: 0.5;
}
.diff-old {
background: var(--danger-light);
color: var(--danger);
}
.diff-new {
background: var(--success-light);
color: var(--success);
}
.diff-context {
color: var(--text-muted);
}
<!-- Responsive images -->
<img src="image-800.webp"
srcset="image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="Description"
loading="lazy"
decoding="async">
<!-- Defer non-critical CSS -->
<link rel="preload" href="critical.css" as="style">
<link rel="stylesheet" href="critical.css">
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
<!-- Async JavaScript -->
<script src="analytics.js" async></script>
<script src="interactive.js" defer></script>
<!-- Lazy load images -->
<img loading="lazy" src="image.jpg" alt="...">
<!-- Lazy load iframes -->
<iframe loading="lazy" src="video.html"></iframe>
<!-- Lazy load sections -->
<section id="heavy-content" data-lazy="true">
<!-- Content loaded on scroll -->
</section>
<head>
<title>OceanBase Full Mode Technical Guide</title>
<meta name="description" content="Complete guide to OceanBase full mode implementation">
<meta name="keywords" content="OceanBase, full mode, technical guide">
<!-- Open Graph -->
<meta property="og:title" content="OceanBase Full Mode">
<meta property="og:description" content="Complete technical guide">
<meta property="og:image" content="https://example.com/preview.png">
<meta property="og:url" content="https://example.com/guide">
<meta property="og:type" content="article">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="OceanBase Full Mode">
<meta name="twitter:description" content="Complete technical guide">
<meta name="twitter:image" content="https://example.com/preview.png">
<!-- Canonical -->
<link rel="canonical" href="https://example.com/guide">
</head>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "OceanBase Full Mode Technical Guide",
"description": "Complete guide to implementation",
"author": {
"@type": "Person",
"name": "Author Name"
},
"datePublished": "2026-07-14",
"dateModified": "2026-07-14"
}
</script>
Use native HTML <details> for simple tabs:
<details class="tab-simple" open>
<summary>English</summary>
<div class="tab-content-simple">English content</div>
</details>
<details class="tab-simple">
<summary>中文</summary>
<div class="tab-content-simple">中文内容</div>
</details>
CSS (only one open at a time):
.tab-simple {
border: 1px solid var(--border);
border-radius: var(--r-sm);
margin: var(--sp-2) 0;
}
.tab-simple summary {
padding: var(--sp-2) var(--sp-3);
cursor: pointer;
font-weight: 600;
list-style: none;
}
.tab-simple summary::-webkit-details-marker {
display: none;
}
.tab-content-simple {
padding: var(--sp-3);
border-top: 1px solid var(--border);
}
/* Close others when one opens */
.tab-simple[open] ~ .tab-simple {
/* JavaScript needed for mutual exclusion */
}
JavaScript for mutual exclusion:
document.querySelectorAll('.tab-simple').forEach(details => {
details.addEventListener('toggle', () => {
if (details.open) {
document.querySelectorAll('.tab-simple').forEach(other => {
if (other !== details) other.open = false;
});
}
});
});
┌─────────────────────────────────┐
│ Header │
├─────────────────────────────────┤
│ TOC │
├─────────────────────────────────┤
│ Section 1 │
├─────────────────────────────────┤
│ Section 2 │
└─────────────────────────────────┘
<body>
<header class="doc-header">...</header>
<div class="container">
<section>...</section>
<section>...</section>
</div>
</body>
<body>
<!-- Hero section -->
<section class="hero">
<h1>Product Name</h1>
<p>Tagline</p>
<a href="#features" class="btn">Learn More</a>
</section>
<!-- Features -->
<section id="features">
<h2>Features</h2>
<div class="grid-3">
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
</div>
</section>
<!-- CTA -->
<section class="cta">
<h2>Get Started</h2>
<a href="#" class="btn">Sign Up</a>
</section>
</body>
Hero CSS:
.hero {
min-height: 80vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background: linear-gradient(135deg, var(--brand-dark), var(--brand));
color: white;
}
.hero h1 {
font-size: 4em;
margin-bottom: var(--sp-4);
}
.btn {
display: inline-block;
padding: var(--sp-3) var(--sp-6);
background: white;
color: var(--brand);
border-radius: var(--r-md);
text-decoration: none;
font-weight: 600;
transition: transform var(--transition-fast);
}
.btn:hover {
transform: translateY(-2px);
}
This skill supports two primary content categories:
When generating a proposal, use this structural template:
┌─────────────────────────────────────────────────────────────────────┐
│ Header: 方案标题 · 日期 · 作者/团队 │
│ [eyebrow] PROJECT · TYPE · STATUS │
└─────────────────────────────────────────────────────────────────────┘
┌─ Executive Summary (执行摘要) ─────────────────────────────────────┐
│ 背景 · 目标 · 核心结论(3-5 个要点,卡片式布局) │
└────────────────────────────────────────────────────────────────────┘
┌─ Background (背景分析) ────────────────────────────────────────────┐
│ 现状 · 问题 · 机会 · 数据支撑(SVG 流程图) │
└────────────────────────────────────────────────────────────────────┘
┌─ Objectives (目标与指标) ──────────────────────────────────────────┐
│ SMART 目标 · KPIs · 成功标准(表格 + 徽章标记优先级) │
└────────────────────────────────────────────────────────────────────┘
┌─ Approach (方案概述) ──────────────────────────────────────────────┐
│ 总体思路 · 核心策略 · 关键决策(SVG 架构图 + 分栏卡片) │
└────────────────────────────────────────────────────────────────────┘
┌─ Implementation (实施计划) ────────────────────────────────────────┐
│ 分阶段计划 · 里程碑 · 时间线(SVG 甘特图/时间线) │
└────────────────────────────────────────────────────────────────────┘
┌─ Resources (资源需求) ─────────────────────────────────────────────┐
│ 团队 · 预算 · 工具 · 依赖(表格 + 成本汇总) │
└────────────────────────────────────────────────────────────────────┘
┌─ Risks & Mitigation (风险与应对) ──────────────────────────────────┐
│ 风险清单 · 概率 · 影响 · 应对策略(callout + 表格) │
└────────────────────────────────────────────────────────────────────┘
┌─ Alternatives (备选方案) ──────────────────────────────────────────┐
│ 对比分析 · 推荐方案 · 决策依据(对比表格 + SVG 决策树) │
└────────────────────────────────────────────────────────────────────┘
┌─ Conclusion (结论与下一步) ────────────────────────────────────────┐
│ 总结 · 审批请求 · 后续行动 · 联系信息 │
└────────────────────────────────────────────────────────────────────┘
<div class="grid-4">
<div class="stat-card">
<div class="num">3</div>
<div class="label">核心目标</div>
</div>
<div class="stat-card">
<div class="num">Q3</div>
<div class="label">启动时间</div>
</div>
<div class="stat-card">
<div class="num">6mo</div>
<div class="label">实施周期</div>
</div>
<div class="stat-card">
<div class="num">¥2M</div>
<div class="label">总预算</div>
</div>
</div>
<!-- Timeline arrow with phase markers -->
<line x1="100" y1="500" x2="1100" y2="500"
stroke="url(#g-arrow)" stroke-width="3"/>
<!-- Phase markers -->
<g>
<circle cx="200" cy="500" r="8" fill="url(#g-brand)"/>
<text x="200" y="530" text-anchor="middle" class="mono" font-size="11">
Phase 1
</text>
<text x="200" y="548" text-anchor="middle" class="body" font-size="10" fill="#6b7280">
调研 · 1mo
</text>
</g>
<div class="callout danger">
<div class="callout-title">⚠️ 高风险:数据迁移失败</div>
概率:中 · 影响:高<br>
<strong>应对策略:</strong>分批次灰度迁移,保留回滚能力
</div>
<table class="fields">
<thead>
<tr>
<th>维度</th>
<th>方案 A</th>
<th class="hl">方案 B (推荐)</th>
<th>方案 C</th>
</tr>
</thead>
<tbody>
<tr><td>实施成本</td><td>¥1.5M</td><td>¥2M</td><td>¥3M</td></tr>
<tr><td>实施周期</td><td>9mo</td><td>6mo</td><td>4mo</td></tr>
</tbody>
</table>
<span class="badge" style="background:#059669;">已批准</span>
<span class="badge" style="background:#d97706;">待审批</span>
<span class="badge" style="background:#dc2626;">已拒绝</span>
<span class="badge" style="background:#6b7280;">已归档</span>
| Signal | Generate |
|---|---|
| 用户提到"文档/说明/介绍/流程" | Documentation |
| 用户提到"方案/计划/提案/建议" | Proposal |
| 用户给出完整内容要求呈现 | Documentation |
| 用户描述问题并要求解决思路 | Proposal |
| 用户需要对比多个选项 | Proposal (with alternatives) |
| 用户需要详细解释现有系统 | Documentation |
Mixed content: If the request contains both elements, combine them — e.g., a technical proposal with architecture documentation sections.
Always use this three-font system for maximum polish:
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;0,800;0,900;1,400;1,700&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
| Role | Font | Usage |
|---|---|---|
| Display / Headings | Playfair Display (800 weight, -0.02em letter-spacing) | Page titles, section headers, emphasis words |
| Body / UI | Inter (400/500/600/700) | Paragraphs, lists, labels, metadata |
| Code / Technical | JetBrains Mono (500/600) | Inline code, code blocks, badges, tags, numbers |
Chinese fallback chain: 'PingFang SC', 'Microsoft YaHei', -apple-system, sans-serif
Two brand palettes — pick one based on the topic:
Indigo (technical, analytical, professional):
--brand-dark: #1e3a8a;
--brand: #4338ca;
--brand-light: #e0e7ff;
--brand-soft: #eef2ff;
Use for: fast mode, performance analysis, data flows, architecture
Purple (creative, refined, premium):
--brand-dark: #5b21b6;
--brand: #7c3aed;
--brand-light: #ede9fe;
--brand-soft: #f5f3ff;
Use for: precise mode, LLM content, design topics, methodology
LLM / Emphasis accent (when LLM is involved):
--accent-llm: #be185d; /* rose */
--accent-llm-light: #fce7f3;
Semantic colors (always include):
--success: #059669; --success-light: #d1fae5;
--warning: #d97706; --warning-light: #fef3c7;
--danger: #dc2626; --danger-light: #fee2e2;
--info: #0284c7; --info-light: #e0f2fe;
Surfaces:
--bg: #fafaf9;
--surface: #ffffff;
--surface-raised: #fcfcfc;
--border: #e5e7eb;
--border-strong: #d1d5db;
--text: #111827;
--text-secondary: #374151;
--text-muted: #6b7280;
--text-faint: #9ca3af;
Always use the 8px-based scale:
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
--sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
--r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 20px;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 12px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04);
--shadow-lg: 0 12px 32px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.04);
--shadow-xl: 0 24px 48px rgba(0,0,0,0.12), 0 8px 16px rgba(0,0,0,0.06);
--shadow-brand: 0 8px 24px rgba(BRAND_RGB, 0.20), 0 2px 8px rgba(BRAND_RGB, 0.10);
Every page uses three aligned sections at 1280px max-width:
┌────────── Header (1280px, rounded 20px, gradient bg) ──────────┐
│ [eyebrow label] │
│ Page Title · <em>Emphasis Word</em> │
│ Subtitle text │
│ 📅 Date · 📦 Project · 🔖 Config │
└─────────────────────────────────────────────────────────────────┘
┌────────── TOC (1280px, rounded 14px, left brand bar) ──────────┐
│ ● Chapter 1 │
│ ● Chapter 2 │
│ ... │
└─────────────────────────────────────────────────────────────────┘
┌────────── Section 1 (1280px, rounded 14px) ────────────────────┐
│ ## Title with <em>emphasis</em> │
│ Content... │
└─────────────────────────────────────────────────────────────────┘
Key rules:
--shadow-sm → --shadow-md--shadow-brand (colored shadow matching brand)<header class="doc-header">
<div class="header-inner">
<span class="eyebrow">CATEGORY · SERIES</span>
<h1>Title · <em>emphasis</em> word</h1>
<div class="subtitle">Descriptive subtitle</div>
<div class="meta">
<span>📅 2026-07-14</span>
<span>•</span>
<span>📦 Project-Name</span>
<span>•</span>
<span>🔖 <code>key = "value"</code></span>
</div>
</div>
</header>
The eyebrow is a frosted pill badge with mono font, uppercase, 0.25em letter-spacing.
Use nav.toc with an ::before left brand-gradient bar. Two-column columns: 2 for link list. Each link has a dot (::before) that scales on hover. Must set list-style: none on nav.toc ol — since dots come from a::before, default <ol> numbers would create ugly double markers (e.g. "1. ● Chapter 1").
For parallel content that should be shown one at a time (e.g., language versions, multiple examples), use tab-based switching:
<!-- Tab bar -->
<div class="tabs" data-group="unique-group-name">
<button class="tab active" data-tab="option1">选项 1</button>
<button class="tab" data-tab="option2">选项 2</button>
</div>
<!-- Tab content panels -->
<div class="tab-content active" data-group="unique-group-name" data-tab="option1">
选项 1 的内容
</div>
<div class="tab-content" data-group="unique-group-name" data-tab="option2">
选项 2 的内容
</div>
CSS (include in <style>):
.tabs {
display: flex;
gap: var(--sp-1);
border-bottom: 2px solid var(--border);
margin-bottom: var(--sp-4);
}
.tab {
padding: var(--sp-2) var(--sp-4);
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
cursor: pointer;
font-family: var(--font-mono);
font-size: 0.85em;
font-weight: 600;
color: var(--text-muted);
transition: all 0.2s var(--ease);
}
.tab:hover { color: var(--brand); }
.tab.active {
color: var(--brand);
border-bottom-color: var(--brand);
}
.tab-content { display: none; }
.tab-content.active { display: block; }
JavaScript (include before </body>):
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.tabs').forEach(function(tabBar) {
const tabs = tabBar.querySelectorAll('.tab');
const tabGroup = tabBar.dataset.group;
const contents = document.querySelectorAll(`.tab-content[data-group="${tabGroup}"]`);
tabs.forEach(function(tab) {
tab.addEventListener('click', function() {
const target = tab.dataset.tab;
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
contents.forEach(c => c.classList.remove('active'));
const targetContent = document.querySelector(`.tab-content[data-group="${tabGroup}"][data-tab="${target}"]`);
if (targetContent) targetContent.classList.add('active');
});
});
});
});
Use cases:
Nested Tab Isolation (CRITICAL):
When nesting tabs (e.g., language tabs inside example tabs), follow these rules:
<!-- ✓ CORRECT: Each nesting level has unique data-group -->
<div class="tab-content" data-group="examples" data-tab="ex1">
<div class="tabs" data-group="example1-lang"> <!-- Unique group name -->
<button class="tab active" data-tab="en">EN</button>
<button class="tab" data-tab="zh">中文</button>
</div>
<div class="tab-content active" data-group="example1-lang" data-tab="en">
English content
</div>
<div class="tab-content" data-group="example1-lang" data-tab="zh">
Chinese content
</div>
</div>
<div class="tab-content" data-group="examples" data-tab="ex2">
<div class="tabs" data-group="example2-lang"> <!-- Different group name -->
<button class="tab active" data-tab="en">EN</button>
<button class="tab" data-tab="zh">中文</button>
</div>
<div class="tab-content active" data-group="example2-lang" data-tab="en">
English content
</div>
<div class="tab-content" data-group="example2-lang" data-tab="zh">
Chinese content
</div>
</div>
✗ WRONG: Shared data-group across nesting levels:
<div class="tab-content" data-group="examples" data-tab="ex1">
<div class="tabs" data-group="lang"> <!-- ❌ Same group name -->
...
</div>
</div>
<div class="tab-content" data-group="examples" data-tab="ex2">
<div class="tabs" data-group="lang"> <!-- ❌ Conflicts with ex1's lang tabs -->
...
</div>
</div>
No Orphaned Content:
tab-content divstab-content before starting the next oneJavaScript Scope Isolation:
The JavaScript MUST use :scope > to select only direct children, preventing cross-tab contamination:
// ✓ CORRECT: Scoped to parent element
const parentContainer = tabBar.parentElement;
const contents = parentContainer.querySelectorAll(`:scope > .tab-content[data-group="${tabGroup}"]`);
// ✗ WRONG: Selects ALL matching elements in document
const contents = document.querySelectorAll(`.tab-content[data-group="${tabGroup}"]`);
Rules:
data-group namedata-group names at each levelclass="active"data-tab to link to contentdata-group + data-tab for matching:scope > in JavaScript to scope selection to parenttab-content divs properly before starting next groupdata-group names across nesting levelsdocument.querySelectorAll for tab-content (use scoped selection).activetab-content that should be controlled by tabsInclude Prism CDN:
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-sql.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
Code blocks must have:
#0f172a (slate-900) — not pure black::after { content: '● ● ●'; } in red/orange/green::before { content: attr(data-lang); } uppercase monodata-lang attribute on the <code> element (e.g., <code class="language-python" data-lang="python">)--shadow-lg<pre class="code" data-lang="python"><code class="language-python">def hello():
print("world")
</code></pre>
<div class="card">
<h5>Card Title</h5>
<p>Card description text</p>
</div>
Cards have:
--r-md border radius--shadow-sm default, --shadow-md on hover-2px translateY lift on hover<div class="callout"> <!-- brand color -->
<div class="callout warning"> <!-- yellow -->
<div class="callout success"> <!-- green -->
<div class="callout danger"> <!-- red -->
<div class="callout llm"> <!-- rose pink, for LLM content -->
Left border 4px + gradient background fading to transparent (90deg).
<span class="badge badge-brand">BRAND</span>
<span class="badge badge-llm">LLM</span>
<span class="tag tag-query">query</span>
Badges: --r-20 pill, mono font, 0.7em size, 600 weight
Tags: --r-sm rectangle, mono font, 0.72em size
<table class="fields">
<thead><tr><th>Column 1</th><th>Column 2</th></tr></thead>
<tbody>
<tr><td><code>key</code></td><td>value</td></tr>
<tr class="hl"><td>highlighted row</td><td>...</td></tr>
</tbody>
</table>
Tables have:
--r-md radius + overflow: hidden.hl class for red-tinted emphasis rowsFor flow diagrams and architecture visuals, create inline SVG with:
<defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;600&display=swap');
.title { font-family: 'Playfair Display', Georgia, serif; font-weight: 800; }
.body { font-family: 'Inter', -apple-system, sans-serif; }
.mono { font-family: 'JetBrains Mono', monospace; font-weight: 600; }
</style>
</defs>
Important: Use raw & in CSS URLs inside <style> blocks (NOT &). The & HTML entity encoding is only needed in HTML attributes and text content, not inside <style> tags (including those embedded in SVG). This applies to both parent HTML and embedded SVGs.
<linearGradient id="g-brand" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#3730a3"/>
<stop offset="50%" stop-color="#4338ca"/>
<stop offset="100%" stop-color="#6366f1"/>
</linearGradient>
Use 3-stop gradients for richness. For precise mode, shift to purple (#5b21b6 → #7c3aed → #8b5cf6).
<filter id="shadow-soft" x="-20%" y="-20%" width="140%" height="160%">
<feDropShadow dx="0" dy="1" stdDeviation="1" flood-color="#1e3a8a" flood-opacity="0.08"/>
<feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#1e3a8a" flood-opacity="0.10"/>
<feDropShadow dx="0" dy="12" stdDeviation="16" flood-color="#1e3a8a" flood-opacity="0.06"/>
</filter>
<filter id="shadow-glow" x="-30%" y="-30%" width="160%" height="180%">
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#4338ca" flood-opacity="0.35"/>
</filter>
Three-layer shadow = realistic depth. Glow filter for important/emphasis nodes.
<g filter="url(#shadow-soft)">
<rect x="..." y="..." width="..." height="..." rx="12"
fill="url(#g-brand-card)" stroke="#c7d2fe" stroke-width="1"/>
<!-- ⚠️ CRITICAL: Use SINGLE <path> for title bar — NEVER two stacked <rect> -->
<!-- Two stacked rects create a visible seam line at their boundary -->
<!-- Path formula for top-rounded rect: -->
<!-- M x y+h V y+r Q x y x+r y H x+w-r Q x+w y x+w y+r V y+h Z -->
<path d="M {x} {y+h} V {y+r} Q {x} {y} {x+r} {y} H {x+w-r}
Q {x+w} {y} {x+w} {y+r} V {y+h} Z" fill="url(#g-brand)"/>
<!-- Traffic light dots -->
<circle cx="..." cy="..." r="4" fill="#fbbf24"/>
<circle cx="..." cy="..." r="4" fill="#fb923c"/>
<circle cx="..." cy="..." r="4" fill="#34d399"/>
<text x="..." y="..." class="mono" font-size="11" fill="white"
letter-spacing="0.5">chapter · subtitle</text>
<!-- Content text -->
<text class="body" font-size="11.5" fill="#1f2937">...</text>
</g>
Concrete example (x=40, y=100, w=320, h=90, title-h=30, r=12):
<path d="M 40 130 V 112 Q 40 100 52 100 H 348 Q 360 100 360 112 V 130 Z"
fill="url(#g-brand)"/>
<!-- Base gradient -->
<linearGradient id="g-bg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#fafaf9"/>
<stop offset="100%" stop-color="#ffffff"/>
</linearGradient>
### Background (Clean Approach)
```svg
<!-- ✓ PREFERRED: Clean solid background — NO grid patterns -->
<rect width="1200" height="520" fill="#fafaf9"/>
<!-- Subtle decorative halos (optional, very low opacity) -->
<circle cx="200" cy="200" r="300" fill="#4338ca" opacity="0.03"/>
<circle cx="1000" cy="900" r="400" fill="#6366f1" opacity="0.02"/>
Avoid grid patterns — they add visual noise and make diagrams look cluttered. Premium SVGs use clean backgrounds with subtle color halos only when needed.
#dc2626 for critical values (sizes, thresholds, counts)Text labels placed on or near arrows must always have a background — never bare text.
For diagonal arrows: keep labels horizontal and center them EXACTLY at the arrow's midpoint.
<!-- ✓ GOOD: horizontal label, rx=3 (nearly square), centered at arrow midpoint -->
<!-- Calculate midpoint: mx = (x1+x2)/2, my = (y1+y2)/2 -->
<!-- Rect positioned so its center is at (mx, my) -->
<rect x="546" y="175" width="78" height="20" rx="3"
fill="#d1fae5" stroke="#059669" stroke-width="1"/>
<text x="585" y="189" text-anchor="middle" class="mono" font-size="10"
fill="#059669">✓ Cache Hit</text>
<!-- ✗ BAD: pill/capsule shape (rx=10+) on diagonal arrow -->
<rect x="555" y="165" width="78" height="20" rx="10" fill="#d1fae5"/>
<!-- ✗ BAD: rotated label group (unnecessary, looks awkward) -->
<g transform="rotate(-32, 585, 185)">
<rect .../>
<text .../>
</g>
<!-- ✗ BAD: bare text on arrow -->
<text x="580" y="175" fill="#059669">✓ Cache Hit</text>
Positioning rules:
mx = (x1+x2)/2, my = (y1+y2)/2x = mx - width/2, y = my - height/2x = mx, y = my + 4 (baseline offset)text-anchor="middle" for centeringrx="3" (nearly square corners) — NOT rx="10" (capsule shape)Background color rules:
fill="#d1fae5" (success-light) with stroke="#059669"fill="#fee2e2" (danger-light) with stroke="#dc2626"fill="#e0f2fe" (info-light) with stroke="#0284c7"fill="var(--brand-soft)" with stroke="var(--brand)"The final arrow segment MUST be perpendicular to the edge it connects to. Diagonal arrows across the diagram are confusing and look unprofessional.
For connections between nodes at different vertical positions, use L-shaped paths:
<!-- ✗ BAD: diagonal line crossing the diagram -->
<line x1="1020" y1="260" x2="600" y2="360" stroke="#7c3aed" stroke-width="2.5" marker-end="url(#arr)"/>
<!-- ✓ GOOD: L-shaped path with perpendicular final arrow -->
<path d="M1020 260 L1020 300 L600 300 L600 360" stroke="#7c3aed" stroke-width="2.5" marker-end="url(#arr)" fill="none"/>
L-shape path formula:
Example routing:
Node A bottom (1020, 260)
│
│ (vertical down to y=300)
▼
(1020, 300)
│
│ (horizontal left to x=600)
├──────────────
│
(600, 300)
│
│ (vertical down to Node B top)
▼
Node B top (600, 360) ← arrow perpendicular to horizontal edge
Rules:
<path> with multiple L commands for L-shapesorient="auto"<line> elements between distant nodesC, Q) for simple connections — they're unpredictableFor phase-based timelines (3+ phases), use this premium layout structure:
┌─ 标题区 (Phase 卡片) ─┐ ┌─ ... ─┐ ┌─ ... ─┐
│ Phase N · Week X-Y │
│ 阶段标题 │
│ 关键任务 │
└──────────┬────────────┘
│
─────────●═════════════════════════● ✓
渐变进度条 完成节点
──────────────────────────────────────
┌─ 交付物卡片 ─┐ ┌─ ... ─┐ ┌─ ... ─┐
│ 交付物 1 │
│ 交付物 2 │
└──────────────┘
图例
Key design rules:
rx="10" rounded rect with brand gradient title bar:<g filter="url(#shadow-sm)">
<rect x="130" y="80" width="180" height="70" rx="10"
fill="url(#g-muted)" stroke="#c7d2fe" stroke-width="1"/>
<!-- Title bar using path for top-rounded corners only -->
<path d="M130 104 V 90 Q 130 80 140 80 H 300 Q 310 80 310 90 V 104 Z"
fill="url(#g-brand)"/>
<text x="145" y="97" class="mono" font-size="11" fill="white">Phase 1 · Week 1-2</text>
<text x="145" y="124" class="title" font-size="15" fill="#1e3a8a">阶段标题</text>
<text x="145" y="143" class="body" font-size="10.5" fill="#374151">任务描述</text>
</g>
<!-- Background (entire timeline, muted) -->
<rect x="100" y="178" width="1000" height="6" rx="3" fill="#e0e7ff"/>
<!-- Progress (completed portion, gradient from brand to success) -->
<rect x="100" y="178" width="850" height="6" rx="3" fill="url(#g-timeline)"/>
<circle cx="220" cy="181" r="10" fill="url(#g-brand)" stroke="white" stroke-width="3"/>
<text x="220" y="185" text-anchor="middle" class="mono" font-size="10" fill="white">1</text>
<circle cx="1090" cy="181" r="14" fill="url(#g-success)"
stroke="white" stroke-width="3" filter="url(#shadow-sm)"/>
<text x="1090" y="186" text-anchor="middle" class="mono" font-size="12" fill="white">✓</text>
<rect x="140" y="238" width="160" height="56" rx="6"
fill="#f1f5f9" stroke="#e2e8f0" stroke-width="1"/>
<text x="150" y="254" class="mono" font-size="10" fill="#4338ca" font-weight="600">交付物</text>
<text x="150" y="270">• 交付物 1</text>
<text x="150" y="286">• 交付物 2</text>
<g transform="translate(100, 325)">
<rect x="0" y="-10" width="12" height="12" rx="3" fill="url(#g-brand)"/>
<text x="18" y="0">进行中</text>
<!-- ... more legend items -->
</g>
Timeline gradient for progress bar:
<linearGradient id="g-timeline" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#e0e7ff"/>
<stop offset="50%" stop-color="#4338ca"/>
<stop offset="100%" stop-color="#059669"/>
</linearGradient>
When coloring text inside SVG code blocks, use these VS Code-inspired colors:
Keywords (.kw): #c084fc (purple)
Functions (.fn): #fde68a (yellow)
Types (.tp): #67e8f9 (cyan)
Strings (.st): #86efac (green)
Comments (.cm): #64748b (slate, italic)
Variables (.var): #7dd3fc (light blue)
Numbers (.num): #fda4af (pink)
stat-card components| Proposal Type | Recommended Palette | Reasoning |
|---|---|---|
| 技术方案 | Indigo | 专业、可信赖、强调数据 |
| 项目方案 | Indigo | 结构化、严肃、重视里程碑 |
| 架构方案 | Purple | 创意、设计感、视觉层次 |
| 设计方案 | Purple | 审美、风格、用户体验导向 |
| 实施方案 | Indigo | 严谨、时间线、可执行性 |
| 研究报告 | Indigo | 数据驱动、客观、分析性 |
<!-- Technical proposal -->
<header class="doc-header">
<div class="header-inner">
<span class="eyebrow">Technical Proposal · v2.1 · DRAFT</span>
<h1>系统架构升级方案 · <em>微服务化改造</em></h1>
<div class="subtitle">将单体架构拆分为领域驱动的微服务架构,提升系统可扩展性</div>
<div class="meta">
<span>📅 2026-07-14</span>
<span>•</span>
<span>👥 Architecture Team</span>
<span>•</span>
<span>🔖 <code>Status: Pending Review</code></span>
</div>
</div>
</header>
Before delivering:
& (NOT &)list-style: none (no double number+dot markers)<path> (NOT two stacked <rect> — causes seam line)<rect height="4">) on nodes — they create unwanted seam lines<em> with brand/rose color❌ System fonts (Arial, Helvetica) as display type
❌ Pure black (#000) backgrounds for code — use slate-900 (#0f172a)
❌ Flat design with no shadows — always use layered shadows
❌ Generic purple gradients on white — our brand purple is deeper/sophisticated
❌ Inconsistent widths between header, TOC, and content
❌ Missing data-lang attribute on code blocks
❌ SVG with different fonts than the page
❌ Flat callout backgrounds (use gradient fade)
❌ Emoji-only icons without context
❌ Generic card layouts without hover effects
❌ & in CSS URLs inside <style> blocks (use raw &)
❌ TOC with both list numbers AND bullet dots (use list-style: none on <ol>)
❌ Bare text labels on SVG arrows without background boxes
❌ Capsule-shaped labels (rx="10"+) on arrows — use rx="3" (nearly square) instead
❌ Rotated label groups on diagonal arrows — keep labels horizontal, center at arrow midpoint
❌ Diagonal <line> elements between distant nodes — use L-shaped <path> instead
❌ Bezier curves (C, Q) for simple arrow connections — unpredictable rendering
❌ Arrow final segment not perpendicular to target edge
❌ Arrow paths crossing through cards — route around instead
❌ SVG title bars made of two stacked <rect> elements (causes visible seam line — use single <path> with top-rounded corners only)
❌ Thin decorative accent bars (<rect height="4" opacity="0.3-0.6">) on top of nodes — they create visible horizontal seam lines
❌ Grid pattern backgrounds on SVG diagrams (<pattern> with lines) — adds visual noise, makes diagrams look cluttered
❌ Timeline with only bare circles on a line — use phase cards + gradient bar + deliverable cards + legend
❌ Timeline with a single solid color progress bar — use TWO stacked rects (light background + gradient progress)
| File | Purpose |
|---|---|
templates/css-system.css | Complete CSS with all components |
templates/svg-components.svg | Reusable SVG card templates |