KeiSeiKit-1.0/skills/visual-explainer/templates/mermaid-flowchart.html
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

465 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CI/CD Pipeline — Reference Template</title>
<!--
Reference template for the visual-explainer skill: Mermaid diagrams.
Teal/cyan palette — distinctly different from terracotta (architecture)
and rose (data-table) templates so agents absorb variety.
Key patterns demonstrated:
- Teal/cyan palette (NOT indigo/violet)
- Dot-grid background atmosphere (different from radial gradient)
- Large heading (38px) for typographic contrast
- ESM import of Mermaid + @mermaid-js/layout-elk
- Mermaid theme: 'base' + full themeVariables, fontSize: 16px
- CSS overrides: .nodeLabel 16px, .edgeLabel 13px
- look: 'classic' for clean lines
- layout: 'elk' for better node positioning
- Zoom controls with scroll-to-zoom and drag-to-pan
- Both light and dark themes via prefers-color-scheme
- Staggered fade-in, reduced motion respect
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@400;500;600;700&family=Fragment+Mono:wght@400&display=swap" rel="stylesheet">
<style>
/* ============ THEME ============ */
:root {
--font-body: 'Bricolage Grotesque', system-ui, sans-serif;
--font-mono: 'Fragment Mono', 'SF Mono', Consolas, monospace;
--bg: #f0fdfa;
--surface: #ffffff;
--surface2: #e6f7f3;
--border: rgba(0, 0, 0, 0.07);
--border-bright: rgba(0, 0, 0, 0.14);
--text: #134e4a;
--text-dim: #5f8a85;
--primary: #0d9488;
--primary-dim: rgba(13, 148, 136, 0.08);
--secondary: #0369a1;
--secondary-dim: rgba(3, 105, 161, 0.08);
--tertiary: #d97706;
--tertiary-dim: rgba(217, 119, 6, 0.08);
--danger: #dc2626;
--danger-dim: rgba(220, 38, 38, 0.08);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #042f2e;
--surface: #0a3d3a;
--surface2: #115e59;
--border: rgba(255, 255, 255, 0.08);
--border-bright: rgba(255, 255, 255, 0.14);
--text: #ccfbf1;
--text-dim: #5eead4;
--primary: #2dd4bf;
--primary-dim: rgba(45, 212, 191, 0.14);
--secondary: #38bdf8;
--secondary-dim: rgba(56, 189, 248, 0.12);
--tertiary: #fbbf24;
--tertiary-dim: rgba(251, 191, 36, 0.12);
--danger: #f87171;
--danger-dim: rgba(248, 113, 113, 0.12);
}
}
/* ============ RESET + BASE ============ */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: var(--bg);
background-image: radial-gradient(circle, var(--border) 1px, transparent 1px);
background-size: 24px 24px;
color: var(--text);
font-family: var(--font-body);
padding: 40px;
min-height: 100vh;
}
/* ============ ANIMATION ============ */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.animate {
animation: fadeUp 0.4s ease-out both;
animation-delay: calc(var(--i, 0) * 0.06s);
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-delay: 0ms !important;
transition-duration: 0.01ms !important;
}
.mermaid-wrap .mermaid { transition: none; }
}
/* ============ LAYOUT ============ */
.container {
max-width: 1000px;
margin: 0 auto;
}
h1 {
font-size: 38px;
font-weight: 700;
letter-spacing: -1px;
margin-bottom: 6px;
text-wrap: balance;
}
.subtitle {
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 12px;
margin-bottom: 32px;
}
.description {
font-size: 14px;
line-height: 1.7;
color: var(--text-dim);
margin-bottom: 24px;
max-width: 700px;
}
.description code {
font-family: var(--font-mono);
font-size: 12px;
background: var(--primary-dim);
color: var(--primary);
padding: 1px 5px;
border-radius: 3px;
}
/* ============ MERMAID CONTAINER ============ */
.mermaid-wrap {
position: relative;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px 24px;
overflow: auto;
margin-bottom: 24px;
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.zoom-controls {
position: absolute;
top: 8px;
right: 8px;
display: flex;
gap: 2px;
z-index: 10;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 2px;
}
.zoom-controls button {
width: 28px;
height: 28px;
border: none;
background: transparent;
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 14px;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s ease, color 0.15s ease;
}
.zoom-controls button:hover {
background: var(--border);
color: var(--text);
}
.mermaid-wrap {
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
.mermaid-wrap::-webkit-scrollbar { width: 6px; height: 6px; }
.mermaid-wrap::-webkit-scrollbar-track { background: transparent; }
.mermaid-wrap::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
}
.mermaid-wrap::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
.mermaid-wrap { cursor: grab; }
.mermaid-wrap.is-panning { cursor: grabbing; user-select: none; }
/* ============ MERMAID SVG OVERRIDES ============ */
.mermaid .nodeLabel {
font-family: var(--font-body) !important;
font-size: 16px !important;
}
.mermaid .edgeLabel {
font-family: var(--font-mono) !important;
font-size: 13px !important;
}
.mermaid .node rect,
.mermaid .node circle,
.mermaid .node polygon {
stroke-width: 1.5px !important;
}
.mermaid .edge-pattern-solid {
stroke-width: 1.5px !important;
}
/* ============ LEGEND ============ */
.legend {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-dim);
}
.legend-swatch {
width: 12px;
height: 12px;
border-radius: 3px;
}
/* ============ CALLOUT ============ */
.callout {
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--primary);
border-radius: 0 10px 10px 0;
padding: 16px 20px;
font-size: 13px;
line-height: 1.6;
color: var(--text-dim);
margin-top: 24px;
}
.callout strong { color: var(--text); }
.callout code {
font-family: var(--font-mono);
font-size: 11px;
background: var(--primary-dim);
color: var(--primary);
padding: 1px 5px;
border-radius: 3px;
}
/* ============ RESPONSIVE ============ */
@media (max-width: 768px) {
body { padding: 16px; }
h1 { font-size: 22px; }
.mermaid-wrap { padding: 16px 12px; }
}
</style>
</head>
<body>
<div class="container">
<h1 class="animate" style="--i:0">CI/CD Pipeline</h1>
<p class="subtitle animate" style="--i:1">github actions &rarr; staging &rarr; production</p>
<p class="description animate" style="--i:2">
Every push to <code>main</code> triggers the full pipeline. Tests and linting run in parallel,
then the build step produces a Docker image. Staging deploys automatically; production requires
manual approval via a GitHub environment gate.
</p>
<div class="mermaid-wrap animate" style="--i:3">
<div class="zoom-controls">
<button onclick="zoomDiagram(this, 1.2)" title="Zoom in">+</button>
<button onclick="zoomDiagram(this, 0.8)" title="Zoom out">&minus;</button>
<button onclick="resetZoom(this)" title="Reset zoom">&#8634;</button>
<button onclick="openDiagramFullscreen(this)" title="Open full size in new tab">&#x26F6;</button>
</div>
<pre class="mermaid">
graph TD
A[Push to main] --> B{Branch?}
B -->|main| C[Run Tests]
B -->|feature| I[Run Tests]
I --> J[Preview Deploy]
C --> D[Lint + Type Check]
C --> E[Unit Tests]
C --> F[Integration Tests]
D --> G[Build Docker Image]
E --> G
F --> G
G --> H[Deploy to Staging]
H --> K{Smoke Tests Pass?}
K -->|Yes| L[Manual Approval]
K -->|No| M[Alert + Rollback]
L --> N[Deploy to Production]
N --> O[Health Check]
O --> P[Done]
</pre>
</div>
<div class="legend animate" style="--i:4">
<div class="legend-item"><div class="legend-swatch" style="background:var(--primary)"></div> Automated step</div>
<div class="legend-item"><div class="legend-swatch" style="background:var(--tertiary)"></div> Decision gate</div>
<div class="legend-item"><div class="legend-swatch" style="background:var(--danger)"></div> Failure path</div>
<div class="legend-item"><div class="legend-swatch" style="background:var(--secondary)"></div> Success</div>
</div>
<div class="callout animate" style="--i:5">
<strong>ELK layout.</strong> The <code>layout: 'elk'</code> engine provides better node positioning
for complex graphs — it requires the separate <code>@mermaid-js/layout-elk</code> package (imported above).
Without it, Mermaid silently falls back to dagre.
</div>
</div>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
import elkLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs';
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
mermaid.registerLayoutLoaders(elkLayouts);
mermaid.initialize({
startOnLoad: true,
theme: 'base',
look: 'classic',
layout: 'elk',
themeVariables: {
primaryColor: isDark ? '#115e59' : '#ccfbf1',
primaryBorderColor: isDark ? '#2dd4bf' : '#0d9488',
primaryTextColor: isDark ? '#ccfbf1' : '#134e4a',
secondaryColor: isDark ? '#0c4a6e' : '#e0f2fe',
secondaryBorderColor: isDark ? '#38bdf8' : '#0369a1',
secondaryTextColor: isDark ? '#ccfbf1' : '#134e4a',
tertiaryColor: isDark ? '#2e2618' : '#fffbeb',
tertiaryBorderColor: isDark ? '#fbbf24' : '#d97706',
tertiaryTextColor: isDark ? '#ccfbf1' : '#134e4a',
lineColor: isDark ? '#5eead4' : '#5f8a85',
fontSize: '16px',
fontFamily: "'Bricolage Grotesque', system-ui, sans-serif",
noteBkgColor: isDark ? '#115e59' : '#fefce8',
noteTextColor: isDark ? '#ccfbf1' : '#134e4a',
noteBorderColor: isDark ? '#fbbf24' : '#d97706',
}
});
</script>
<script>
var INITIAL_ZOOM = 1;
function zoomDiagram(btn, factor) {
var wrap = btn.closest('.mermaid-wrap');
var target = wrap.querySelector('.mermaid');
var current = parseFloat(target.dataset.zoom || INITIAL_ZOOM);
var next = Math.min(Math.max(current * factor, 0.5), 5);
target.dataset.zoom = next;
target.style.zoom = next;
}
function resetZoom(btn) {
var wrap = btn.closest('.mermaid-wrap');
var target = wrap.querySelector('.mermaid');
target.dataset.zoom = INITIAL_ZOOM;
target.style.zoom = INITIAL_ZOOM;
}
function openDiagramFullscreen(btn) {
var wrap = btn.closest('.mermaid-wrap');
openMermaidInNewTab(wrap);
}
function openMermaidInNewTab(wrap) {
var svg = wrap.querySelector('.mermaid svg');
if (!svg) return;
var clone = svg.cloneNode(true);
clone.style.zoom = '';
clone.style.transform = '';
var styles = getComputedStyle(document.documentElement);
var bg = styles.getPropertyValue('--bg').trim() || '#ffffff';
var html = '<!DOCTYPE html>' +
'<html lang="en"><head><meta charset="UTF-8">' +
'<meta name="viewport" content="width=device-width, initial-scale=1.0">' +
'<title>Diagram</title>' +
'<style>' +
'body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: ' + bg + '; padding: 40px; box-sizing: border-box; }' +
'svg { max-width: 100%; max-height: 90vh; height: auto; }' +
'</style></head><body>' +
clone.outerHTML +
'</body></html>';
var blob = new Blob([html], { type: 'text/html' });
window.open(URL.createObjectURL(blob), '_blank');
}
document.querySelectorAll('.mermaid-wrap').forEach(function(wrap) {
// Scroll-to-zoom (Ctrl/Cmd + wheel)
wrap.addEventListener('wheel', function(e) {
if (!e.ctrlKey && !e.metaKey) return;
e.preventDefault();
var target = wrap.querySelector('.mermaid');
var current = parseFloat(target.dataset.zoom || INITIAL_ZOOM);
var factor = e.deltaY < 0 ? 1.1 : 0.9;
var next = Math.min(Math.max(current * factor, 0.5), 5);
target.dataset.zoom = next;
target.style.zoom = next;
}, { passive: false });
// Click-and-drag to pan, click (without drag) to open full-size
var startX, startY, scrollL, scrollT, startTime, didPan;
wrap.addEventListener('mousedown', function(e) {
if (e.target.closest('.zoom-controls')) return;
wrap.classList.add('is-panning');
startX = e.clientX;
startY = e.clientY;
scrollL = wrap.scrollLeft;
scrollT = wrap.scrollTop;
startTime = Date.now();
didPan = false;
});
window.addEventListener('mousemove', function(e) {
if (!wrap.classList.contains('is-panning')) return;
var dx = e.clientX - startX;
var dy = e.clientY - startY;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) didPan = true;
wrap.scrollLeft = scrollL - dx;
wrap.scrollTop = scrollT - dy;
});
window.addEventListener('mouseup', function() {
if (!wrap.classList.contains('is-panning')) return;
wrap.classList.remove('is-panning');
var elapsed = Date.now() - startTime;
if (!didPan && elapsed < 300) {
openMermaidInNewTab(wrap);
}
});
});
</script>
</body>
</html>