<!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>
