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.
465 lines
14 KiB
HTML
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 → staging → 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">−</button>
|
|
<button onclick="resetZoom(this)" title="Reset zoom">↺</button>
|
|
<button onclick="openDiagramFullscreen(this)" title="Open full size in new tab">⛶</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>
|