Last active
March 30, 2026 15:18
-
-
Save daneuchar/07c27feaf36ac02e40120d19b6e2072b to your computer and use it in GitHub Desktop.
workflwo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Feature Development Workflow</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } | |
| :root { | |
| --bg-deepest: #050810; | |
| --bg-primary: #0a0f1c; | |
| --bg-elevated: #111827; | |
| --bg-surface: #1e293b; | |
| --text-primary: #e2e8f0; | |
| --text-secondary:#94a3b8; | |
| --text-tertiary: #64748b; | |
| --text-ghost: #334155; | |
| --accent-teal: #64ffda; | |
| --accent-purple: #a78bfa; | |
| --accent-blue: #60a5fa; | |
| --accent-amber: #fbbf24; | |
| --accent-rose: #fb7185; | |
| --accent-green: #34d399; | |
| --border-subtle: rgba(148,163,184,0.08); | |
| --border-accent: rgba(100,255,218,0.25); | |
| } | |
| html, body { | |
| width: 100%; height: 100%; | |
| overflow: hidden; | |
| background: var(--bg-deepest); | |
| color: var(--text-primary); | |
| font-family: 'Inter', -apple-system, sans-serif; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| /* ── Ambient Background ── */ | |
| .ambient-bg { | |
| position: fixed; inset: 0; z-index: 0; | |
| background: linear-gradient(135deg, #050810 0%, #0a0f1c 25%, #0d1520 50%, #10172a 75%, #050810 100%); | |
| background-size: 400% 400%; | |
| animation: gradientShift 25s ease-in-out infinite; | |
| } | |
| @keyframes gradientShift { | |
| 0% { background-position: 0% 0%; } | |
| 25% { background-position: 100% 0%; } | |
| 50% { background-position: 100% 100%; } | |
| 75% { background-position: 0% 100%; } | |
| 100% { background-position: 0% 0%; } | |
| } | |
| .grid-overlay { | |
| position: fixed; inset: 0; z-index: 1; pointer-events: none; | |
| background-image: | |
| linear-gradient(rgba(100,255,218,0.02) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(100,255,218,0.02) 1px, transparent 1px); | |
| background-size: 80px 80px; | |
| } | |
| .radial-glow { | |
| position: fixed; inset: 0; z-index: 1; pointer-events: none; | |
| background: radial-gradient(ellipse 800px 600px at 30% 40%, rgba(100,255,218,0.04), transparent 70%), | |
| radial-gradient(ellipse 600px 500px at 70% 60%, rgba(167,139,250,0.03), transparent 70%); | |
| animation: glowDrift 20s ease-in-out infinite alternate; | |
| } | |
| @keyframes glowDrift { | |
| 0% { opacity: 1; transform: translate(0, 0); } | |
| 100% { opacity: 0.7; transform: translate(40px, -30px); } | |
| } | |
| .noise-overlay { | |
| position: fixed; inset: 0; z-index: 2; pointer-events: none; | |
| opacity: 0.03; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); | |
| } | |
| /* ── Particles ── */ | |
| .particles { position: fixed; inset: 0; z-index: 2; pointer-events: none; overflow: hidden; } | |
| .particle { | |
| position: absolute; border-radius: 50%; | |
| background: radial-gradient(circle, rgba(100,255,218,0.5), transparent 70%); | |
| animation: particleFloat linear infinite; | |
| } | |
| @keyframes particleFloat { | |
| 0% { transform: translateY(100vh) scale(0); opacity: 0; } | |
| 10% { opacity: 0.8; transform: translateY(90vh) scale(1); } | |
| 90% { opacity: 0.3; } | |
| 100% { transform: translateY(-5vh) scale(0.3); opacity: 0; } | |
| } | |
| /* ── Progress Bar ── */ | |
| .progress-bar { | |
| position: fixed; top: 0; left: 0; height: 3px; z-index: 100; | |
| background: linear-gradient(90deg, var(--accent-teal), var(--accent-purple)); | |
| box-shadow: 0 0 12px rgba(100,255,218,0.4); | |
| transition: width 0.7s cubic-bezier(0.16, 1, 0.3, 1); | |
| width: 0%; | |
| } | |
| /* ── Slide Counter ── */ | |
| .slide-counter { | |
| position: fixed; bottom: 2rem; right: 2.5rem; z-index: 100; | |
| font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; | |
| color: var(--text-tertiary); letter-spacing: 0.15em; | |
| opacity: 0.6; | |
| } | |
| /* ── Nav Hint ── */ | |
| .nav-hint { | |
| position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%); z-index: 100; | |
| font-size: 0.75rem; color: var(--text-ghost); letter-spacing: 0.1em; | |
| animation: hintPulse 3s ease-in-out infinite; | |
| } | |
| @keyframes hintPulse { 0%,100% { opacity: 0.3; } 50% { opacity: 0.7; } } | |
| /* ── Slides Container ── */ | |
| .slides-container { position: fixed; inset: 0; z-index: 10; } | |
| .slide { | |
| position: absolute; inset: 0; | |
| display: flex; flex-direction: column; | |
| justify-content: center; align-items: center; | |
| padding: 4rem 8rem; | |
| opacity: 0; pointer-events: none; | |
| will-change: transform, opacity, clip-path; | |
| } | |
| .slide.active { opacity: 1; pointer-events: all; } | |
| /* ── Typography ── */ | |
| h1 { font-family: 'Space Grotesk', sans-serif; font-size: clamp(2.5rem, 5vw, 4.2rem); font-weight: 700; letter-spacing: -0.03em; line-height: 1.15; } | |
| h2 { font-family: 'Space Grotesk', sans-serif; font-size: clamp(1.8rem, 3.5vw, 2.8rem); font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; } | |
| h3 { font-family: 'Space Grotesk', sans-serif; font-size: clamp(1.3rem, 2.5vw, 1.8rem); font-weight: 500; letter-spacing: -0.01em; } | |
| p, li { font-size: clamp(1rem, 1.5vw, 1.25rem); line-height: 1.75; color: var(--text-secondary); } | |
| .mono { font-family: 'JetBrains Mono', monospace; } | |
| .uppercase { text-transform: uppercase; letter-spacing: 0.2em; font-size: 0.75rem; font-weight: 500; } | |
| /* ── Accent Text ── */ | |
| .text-teal { color: var(--accent-teal); } | |
| .text-purple { color: var(--accent-purple); } | |
| .text-blue { color: var(--accent-blue); } | |
| .text-amber { color: var(--accent-amber); } | |
| .text-rose { color: var(--accent-rose); } | |
| .text-green { color: var(--accent-green); } | |
| .glow-teal { text-shadow: 0 0 20px rgba(100,255,218,0.3), 0 0 60px rgba(100,255,218,0.1); } | |
| .glow-purple { text-shadow: 0 0 20px rgba(167,139,250,0.3), 0 0 60px rgba(167,139,250,0.1); } | |
| .glow-blue { text-shadow: 0 0 20px rgba(96,165,250,0.3), 0 0 60px rgba(96,165,250,0.1); } | |
| .glow-amber { text-shadow: 0 0 20px rgba(251,191,36,0.3), 0 0 60px rgba(251,191,36,0.1); } | |
| .glow-green { text-shadow: 0 0 20px rgba(52,211,153,0.3), 0 0 60px rgba(52,211,153,0.1); } | |
| /* ── Layout Utilities ── */ | |
| .slide-content { max-width: 1100px; width: 100%; } | |
| .flex-row { display: flex; gap: 2.5rem; align-items: flex-start; } | |
| .flex-col { display: flex; flex-direction: column; gap: 1rem; } | |
| .flex-1 { flex: 1; } | |
| .text-center { text-align: center; } | |
| .mb-1 { margin-bottom: 0.5rem; } .mb-2 { margin-bottom: 1rem; } .mb-3 { margin-bottom: 1.5rem; } .mb-4 { margin-bottom: 2rem; } .mb-6 { margin-bottom: 3rem; } | |
| .mt-2 { margin-top: 1rem; } .mt-4 { margin-top: 2rem; } | |
| /* ── Glow Divider ── */ | |
| .glow-divider { | |
| height: 2px; border: none; margin: 2rem 0; | |
| background: linear-gradient(90deg, transparent, var(--accent-teal), transparent); | |
| box-shadow: 0 0 15px rgba(100,255,218,0.2); | |
| } | |
| /* ── Cards ── */ | |
| .card { | |
| background: rgba(17,24,39,0.6); border: 1px solid var(--border-subtle); | |
| border-radius: 16px; padding: 2rem; backdrop-filter: blur(10px); | |
| transition: border-color 0.4s ease, box-shadow 0.4s ease; | |
| } | |
| .card:hover { border-color: var(--border-accent); box-shadow: 0 0 30px rgba(100,255,218,0.06); } | |
| /* ── Phase Badge ── */ | |
| .phase-badge { | |
| display: inline-flex; align-items: center; gap: 0.6rem; | |
| padding: 0.4rem 1rem; border-radius: 100px; font-size: 0.8rem; | |
| font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; | |
| border: 1px solid; margin-bottom: 1.5rem; | |
| } | |
| .phase-badge.teal { color: var(--accent-teal); border-color: rgba(100,255,218,0.3); background: rgba(100,255,218,0.06); } | |
| .phase-badge.purple { color: var(--accent-purple); border-color: rgba(167,139,250,0.3); background: rgba(167,139,250,0.06); } | |
| .phase-badge.blue { color: var(--accent-blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.06); } | |
| .phase-badge.amber { color: var(--accent-amber); border-color: rgba(251,191,36,0.3); background: rgba(251,191,36,0.06); } | |
| .phase-badge.green { color: var(--accent-green); border-color: rgba(52,211,153,0.3); background: rgba(52,211,153,0.06); } | |
| /* ── Bullet List ── */ | |
| .bullet-list { list-style: none; display: flex; flex-direction: column; gap: 0.9rem; } | |
| .bullet-list li { | |
| position: relative; padding-left: 1.6rem; line-height: 1.65; | |
| } | |
| .bullet-list li::before { | |
| content: ''; position: absolute; left: 0; top: 0.65em; | |
| width: 6px; height: 6px; border-radius: 50%; | |
| background: var(--accent-teal); | |
| box-shadow: 0 0 8px rgba(100,255,218,0.4); | |
| } | |
| .bullet-list.purple li::before { background: var(--accent-purple); box-shadow: 0 0 8px rgba(167,139,250,0.4); } | |
| .bullet-list.blue li::before { background: var(--accent-blue); box-shadow: 0 0 8px rgba(96,165,250,0.4); } | |
| .bullet-list.amber li::before { background: var(--accent-amber); box-shadow: 0 0 8px rgba(251,191,36,0.4); } | |
| .bullet-list.green li::before { background: var(--accent-green); box-shadow: 0 0 8px rgba(52,211,153,0.4); } | |
| /* ── Pipeline ── */ | |
| .pipeline { display: flex; align-items: center; gap: 0; justify-content: center; flex-wrap: wrap; } | |
| .pipeline-node { | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; | |
| width: 140px; height: 90px; border-radius: 14px; text-align: center; | |
| font-size: 0.8rem; font-weight: 600; letter-spacing: 0.03em; | |
| border: 1px solid; position: relative; | |
| } | |
| .pipeline-arrow { | |
| width: 40px; height: 2px; position: relative; flex-shrink: 0; | |
| } | |
| .pipeline-arrow::after { | |
| content: ''; position: absolute; right: -1px; top: 50%; transform: translateY(-50%); | |
| border: 5px solid transparent; border-left: 7px solid; | |
| } | |
| /* ── Table ── */ | |
| .data-table { width: 100%; border-collapse: collapse; font-size: 0.95rem; } | |
| .data-table th { | |
| text-align: left; padding: 0.8rem 1rem; font-weight: 600; font-size: 0.8rem; | |
| text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-tertiary); | |
| border-bottom: 1px solid var(--border-subtle); | |
| } | |
| .data-table td { | |
| padding: 0.75rem 1rem; color: var(--text-secondary); | |
| border-bottom: 1px solid rgba(148,163,184,0.04); | |
| } | |
| .data-table tr { transition: background 0.3s ease; } | |
| .data-table tbody tr:hover { background: rgba(100,255,218,0.02); } | |
| /* ── Big Number ── */ | |
| .big-number { | |
| font-family: 'Space Grotesk', sans-serif; font-size: 8rem; font-weight: 700; | |
| line-height: 1; letter-spacing: -0.05em; opacity: 0.1; | |
| position: absolute; right: 4rem; top: 3rem; | |
| } | |
| /* ── Flowchart SVG ── */ | |
| .flowchart { width: 100%; max-width: 900px; margin: 0 auto; } | |
| .flowchart svg { width: 100%; height: auto; } | |
| .flow-line { | |
| fill: none; stroke-width: 2; stroke-linecap: round; | |
| } | |
| .flow-node-rect { | |
| rx: 12; ry: 12; stroke-width: 1.5; | |
| } | |
| .flow-node-text { | |
| font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 500; | |
| fill: var(--text-primary); text-anchor: middle; dominant-baseline: central; | |
| } | |
| /* ── Stat Blocks ── */ | |
| .stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.2rem; } | |
| .stat-block { | |
| background: rgba(17,24,39,0.5); border: 1px solid var(--border-subtle); | |
| border-radius: 14px; padding: 1.5rem; text-align: center; | |
| } | |
| .stat-value { font-family: 'Space Grotesk', sans-serif; font-size: 2rem; font-weight: 700; } | |
| .stat-label { font-size: 0.8rem; color: var(--text-tertiary); margin-top: 0.3rem; } | |
| /* ── Title Chars ── */ | |
| .char-wrap { display: inline-block; overflow: hidden; } | |
| .char-inner { display: inline-block; } | |
| /* ── Markdown Render ── */ | |
| .md-render { font-size: 0.78rem; line-height: 1.75; color: var(--text-secondary); } | |
| .md-render h1 { font-size: 1.15rem; font-weight: 700; color: var(--text-primary); margin: 0 0 0.5rem 0; } | |
| .md-render h2 { font-size: 0.95rem; font-weight: 600; color: var(--accent-purple); margin: 1.2rem 0 0.5rem 0; } | |
| .md-render h3 { font-size: 0.82rem; font-weight: 600; color: var(--text-primary); margin: 1rem 0 0.4rem 0; } | |
| .md-render hr { border: none; border-top: 1px solid rgba(255,255,255,0.06); margin: 1rem 0; } | |
| .md-render p { margin: 0.4rem 0; } | |
| .md-render strong { color: var(--text-primary); font-weight: 600; } | |
| .md-render em { color: var(--text-tertiary); font-style: italic; } | |
| .md-render code { font-family: 'JetBrains Mono', monospace; font-size: 0.72rem; background: rgba(167,139,250,0.08); color: var(--accent-purple); padding: 0.15em 0.4em; border-radius: 4px; } | |
| .md-render pre { background: rgba(10,12,20,0.6); border: 1px solid rgba(255,255,255,0.06); border-radius: 8px; padding: 0.8rem 1rem; margin: 0.6rem 0; overflow-x: auto; } | |
| .md-render pre code { background: none; padding: 0; color: var(--accent-teal); font-size: 0.72rem; } | |
| .md-render blockquote { border-left: 3px solid var(--accent-purple); padding: 0.5rem 1rem; margin: 0.6rem 0; background: rgba(167,139,250,0.04); border-radius: 0 8px 8px 0; color: var(--text-tertiary); font-style: italic; } | |
| .md-render ul { padding-left: 1.2rem; margin: 0.4rem 0; } | |
| .md-render li { margin: 0.2rem 0; } | |
| .md-render li::marker { color: var(--accent-purple); } | |
| .md-render table { width: 100%; border-collapse: collapse; margin: 0.6rem 0; font-size: 0.74rem; } | |
| .md-render th { text-align: left; padding: 0.5rem 0.8rem; border-bottom: 1px solid rgba(255,255,255,0.1); color: var(--text-ghost); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.68rem; font-weight: 500; } | |
| .md-render td { padding: 0.45rem 0.8rem; border-bottom: 1px solid rgba(255,255,255,0.04); } | |
| /* ── File Tree ── */ | |
| .file-tree { | |
| font-family: 'JetBrains Mono', monospace; font-size: 0.82rem; line-height: 2; | |
| color: var(--text-tertiary); user-select: none; | |
| } | |
| .file-tree .tree-line { white-space: nowrap; display: flex; align-items: center; gap: 0.4rem; } | |
| .file-tree .tree-indent { color: var(--text-ghost); flex-shrink: 0; } | |
| .file-tree .tree-icon { flex-shrink: 0; font-size: 0.9rem; } | |
| .file-tree .tree-name { color: var(--text-secondary); } | |
| .file-tree .tree-name.folder { color: var(--text-tertiary); } | |
| .file-tree .tree-name.orchestrator { | |
| color: var(--accent-teal); font-weight: 600; | |
| text-shadow: 0 0 12px rgba(100,255,218,0.3); | |
| } | |
| .file-tree .tree-name.module { | |
| color: var(--accent-purple); | |
| } | |
| .file-tree .tree-tag { | |
| font-size: 0.6rem; padding: 0.1rem 0.45rem; border-radius: 4px; | |
| font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; | |
| margin-left: 0.5rem; vertical-align: middle; | |
| } | |
| .file-tree .tree-tag.orch { color: var(--accent-teal); background: rgba(100,255,218,0.08); border: 1px solid rgba(100,255,218,0.2); } | |
| .file-tree .tree-tag.ctx { color: var(--accent-purple); background: rgba(167,139,250,0.08); border: 1px solid rgba(167,139,250,0.2); } | |
| /* ── Orchestrator Hub ── */ | |
| .orch-hub { | |
| position: relative; display: flex; flex-direction: column; | |
| align-items: center; justify-content: center; gap: 0.6rem; | |
| } | |
| .orch-node { | |
| width: 160px; padding: 1rem; border-radius: 16px; text-align: center; | |
| border: 1.5px solid rgba(100,255,218,0.35); | |
| background: rgba(100,255,218,0.05); | |
| box-shadow: 0 0 30px rgba(100,255,218,0.08), inset 0 0 20px rgba(100,255,218,0.03); | |
| position: relative; z-index: 2; | |
| } | |
| .orch-node .orch-label { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--accent-teal); font-weight: 600; } | |
| .orch-node .orch-sub { font-size: 0.7rem; color: var(--text-tertiary); margin-top: 0.2rem; } | |
| .orch-pulse { | |
| position: absolute; inset: -8px; border-radius: 20px; z-index: 1; | |
| border: 1px solid rgba(100,255,218,0.15); | |
| animation: orchPulse 3s ease-in-out infinite; | |
| } | |
| @keyframes orchPulse { | |
| 0%, 100% { opacity: 0.3; transform: scale(1); } | |
| 50% { opacity: 0.8; transform: scale(1.06); } | |
| } | |
| /* ── Connection Lines (CSS animated) ── */ | |
| .conn-line { | |
| position: absolute; height: 1.5px; | |
| background: linear-gradient(90deg, rgba(167,139,250,0.4), rgba(100,255,218,0.5)); | |
| transform-origin: left center; | |
| z-index: 0; | |
| } | |
| .conn-line::after { | |
| content: ''; position: absolute; right: -2px; top: 50%; transform: translateY(-50%); | |
| width: 5px; height: 5px; border-radius: 50%; | |
| background: var(--accent-teal); | |
| box-shadow: 0 0 8px rgba(100,255,218,0.6); | |
| } | |
| .conn-dot-travel { | |
| position: absolute; width: 4px; height: 4px; border-radius: 50%; | |
| background: var(--accent-teal); | |
| box-shadow: 0 0 10px rgba(100,255,218,0.8), 0 0 20px rgba(100,255,218,0.3); | |
| z-index: 3; | |
| } | |
| /* ── Reveal Items ── */ | |
| .reveal-item { opacity: 0; transform: translateY(30px); } | |
| /* ── Responsive ── */ | |
| @media (max-width: 900px) { | |
| .slide { padding: 3rem 2rem; } | |
| .flex-row { flex-direction: column; } | |
| .stat-grid { grid-template-columns: repeat(2, 1fr); } | |
| .pipeline { flex-direction: column; } | |
| .pipeline-arrow { width: 2px; height: 30px; } | |
| .big-number { font-size: 5rem; right: 1.5rem; top: 1.5rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="ambient-bg"></div> | |
| <div class="grid-overlay"></div> | |
| <div class="radial-glow"></div> | |
| <div class="noise-overlay"></div> | |
| <div class="particles" id="particles"></div> | |
| <div class="progress-bar" id="progressBar"></div> | |
| <div class="slide-counter" id="slideCounter"></div> | |
| <div class="nav-hint" id="navHint">ARROW KEYS TO NAVIGATE</div> | |
| <div class="slides-container"> | |
| <!-- ═══════ SLIDE 0: Title ═══════ --> | |
| <div class="slide active" data-index="0"> | |
| <div class="slide-content text-center"> | |
| <p class="uppercase text-teal reveal-item mb-4" style="font-size:0.85rem;">Thoughtworks UBS Engineering Process</p> | |
| <h1 class="mb-3"> | |
| <span class="title-text">AI Assisted Development</span><br> | |
| <span class="title-text text-teal glow-teal">Workflow</span> | |
| </h1> | |
| <hr class="glow-divider reveal-item" style="max-width:200px;margin:1.5rem auto;"> | |
| <p class="reveal-item" style="max-width:600px;margin:0 auto;">A structured, five-phase delivery process that ensures consistent quality while maintaining velocity as the team scales.</p> | |
| <div class="mt-4 reveal-item"> | |
| <span class="mono text-ghost" style="font-size:0.85rem;">Daniel Euchar, Thoughtworks — 2026</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 1: Overview Pipeline ═══════ --> | |
| <div class="slide" data-index="1"> | |
| <div class="slide-content text-center"> | |
| <p class="uppercase text-teal reveal-item mb-3">End-to-End Overview</p> | |
| <h2 class="reveal-item mb-6">Five Phases, One Feedback Loop</h2> | |
| <div class="pipeline reveal-item"> | |
| <div class="pipeline-node" style="background:rgba(10,15,28,0.8);border-color:rgba(148,163,184,0.2);color:var(--text-secondary);"> | |
| <span style="font-size:1.4rem;margin-bottom:0.2rem;">0</span> | |
| <span style="font-size:0.7rem;opacity:0.7;">AI Bootstrap</span> | |
| <span style="font-size:0.6rem;color:var(--text-ghost);">ONE-TIME</span> | |
| </div> | |
| <div class="pipeline-arrow" style="background:var(--text-ghost);"> | |
| <span style="position:absolute;right:-1px;top:50%;transform:translateY(-50%);border:5px solid transparent;border-left:7px solid var(--text-ghost);"></span> | |
| </div> | |
| <div class="pipeline-node" style="background:rgba(99,102,241,0.1);border-color:rgba(99,102,241,0.3);color:var(--accent-purple);"> | |
| <span style="font-size:1.4rem;margin-bottom:0.2rem;">1</span> | |
| <span style="font-size:0.7rem;">Planning</span> | |
| </div> | |
| <div class="pipeline-arrow" style="background:var(--accent-purple);opacity:0.4;"> | |
| <span style="position:absolute;right:-1px;top:50%;transform:translateY(-50%);border:5px solid transparent;border-left:7px solid var(--accent-purple);opacity:0.4;"></span> | |
| </div> | |
| <div class="pipeline-node" style="background:rgba(251,191,36,0.08);border-color:rgba(251,191,36,0.3);color:var(--accent-amber);"> | |
| <span style="font-size:1.4rem;margin-bottom:0.2rem;">2</span> | |
| <span style="font-size:0.7rem;">Build & Test</span> | |
| </div> | |
| <div class="pipeline-arrow" style="background:var(--accent-amber);opacity:0.4;"> | |
| <span style="position:absolute;right:-1px;top:50%;transform:translateY(-50%);border:5px solid transparent;border-left:7px solid var(--accent-amber);opacity:0.4;"></span> | |
| </div> | |
| <div class="pipeline-node" style="background:rgba(96,165,250,0.08);border-color:rgba(96,165,250,0.3);color:var(--accent-blue);"> | |
| <span style="font-size:1.4rem;margin-bottom:0.2rem;">3</span> | |
| <span style="font-size:0.7rem;">Iteration</span> | |
| </div> | |
| <div class="pipeline-arrow" style="background:var(--accent-blue);opacity:0.4;"> | |
| <span style="position:absolute;right:-1px;top:50%;transform:translateY(-50%);border:5px solid transparent;border-left:7px solid var(--accent-blue);opacity:0.4;"></span> | |
| </div> | |
| <div class="pipeline-node" style="background:rgba(52,211,153,0.08);border-color:rgba(52,211,153,0.3);color:var(--accent-green);"> | |
| <span style="font-size:1.4rem;margin-bottom:0.2rem;">4</span> | |
| <span style="font-size:0.7rem;">Deploy &</span> | |
| <span style="font-size:0.7rem;">Validate</span> | |
| </div> | |
| </div> | |
| <div class="mt-4 reveal-item" style="display:flex;justify-content:center;align-items:center;gap:0.6rem;"> | |
| <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M15 10C15 12.7614 12.7614 15 10 15C7.23858 15 5 12.7614 5 10C5 7.23858 7.23858 5 10 5" stroke="var(--accent-teal)" stroke-width="1.5" stroke-linecap="round"/><path d="M10 2V5L12 3" stroke="var(--accent-teal)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> | |
| <span class="text-teal" style="font-size:0.9rem;">Learnings feeds back into documentation</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 2: Phase 0 ═══════ --> | |
| <div class="slide" data-index="2"> | |
| <div class="big-number text-ghost">0</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge teal">Phase 0 — One-Time Setup</div> | |
| <h2 class="reveal-item mb-2">AI Knowledge Bootstrap</h2> | |
| <p class="reveal-item mb-4" style="max-width:700px;">Give AI tools deep, structured understanding of the codebase so every future cycle starts with accurate context — not guesswork.</p> | |
| <div class="flex-row"> | |
| <div class="flex-1"> | |
| <h3 class="reveal-item text-teal mb-3" style="font-size:1.1rem;">What Happens</h3> | |
| <ul class="bullet-list"> | |
| <li class="reveal-item">AI scans entire codebase and generates structured module documentation</li> | |
| <li class="reveal-item">Developer domain experts review and enrich every doc — AI captures <em>what</em>, developers add the <em>why</em></li> | |
| <li class="reveal-item">Tool instruction files configured (<span class="mono text-teal" style="font-size:0.9em;">claude.md</span>, Copilot instructions)</li> | |
| <li class="reveal-item">Enforcement rules set — AI must consult docs and conventions before any plan</li> | |
| </ul> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="card reveal-item"> | |
| <h3 class="mb-2" style="font-size:0.95rem;color:var(--text-primary);">Module Doc Template</h3> | |
| <table class="data-table" style="font-size:0.85rem;"> | |
| <tbody> | |
| <tr><td class="text-teal" style="width:45%;">Overview & API</td><td class="text-ghost">AI-generated</td></tr> | |
| <tr><td class="text-teal">Dependencies</td><td class="text-ghost">AI-generated</td></tr> | |
| <tr><td class="text-purple">Architecture & Decisions</td><td class="text-ghost">Dev-authored</td></tr> | |
| <tr><td class="text-purple">Tribal Knowledge</td><td class="text-ghost">Dev-authored</td></tr> | |
| <tr><td class="text-amber">Constraints & Rules</td><td class="text-ghost">Both</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 3: Phase 0 Enforcement ═══════ --> | |
| <div class="slide" data-index="3"> | |
| <div class="big-number text-ghost">0</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge teal">Phase 0 — Orchestration</div> | |
| <h2 class="reveal-item mb-2">Instruction Files as Orchestrators</h2> | |
| <p class="reveal-item mb-3" style="max-width:750px;">Instruction files sit at the root and gather context from every module doc before the AI can plan or write code.</p> | |
| <!-- File Tree + Orchestrator Diagram --> | |
| <div class="flex-row reveal-item" style="align-items:stretch;gap:1.5rem;"> | |
| <!-- LEFT: File Tree --> | |
| <div class="card" style="flex:1.1;padding:1.5rem 1.8rem;"> | |
| <h3 style="font-size:0.8rem;color:var(--text-ghost);margin-bottom:0.8rem;letter-spacing:0.15em;text-transform:uppercase;">Project Structure</h3> | |
| <div class="file-tree" id="fileTree"> | |
| <div class="tree-line reveal-item"><span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">project/</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">├──</span> <span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">.github/</span></div> | |
| <div class="tree-line reveal-item" data-orch="2"><span class="tree-indent">│ └──</span> <span class="tree-icon">📜</span> <span class="tree-name orchestrator">copilot-instructions.md</span> <span class="tree-tag orch">orchestrator</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">├──</span> <span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">src/</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">│ ├──</span> <span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">auth/</span></div> | |
| <div class="tree-line reveal-item" data-module="1"><span class="tree-indent">│ │ └──</span> <span class="tree-icon">📄</span> <span class="tree-name module">module.md</span> <span class="tree-tag ctx">context</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">│ ├──</span> <span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">payments/</span></div> | |
| <div class="tree-line reveal-item" data-module="2"><span class="tree-indent">│ │ └──</span> <span class="tree-icon">📄</span> <span class="tree-name module">module.md</span> <span class="tree-tag ctx">context</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">│ ├──</span> <span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">api/</span></div> | |
| <div class="tree-line reveal-item" data-module="3"><span class="tree-indent">│ │ └──</span> <span class="tree-icon">📄</span> <span class="tree-name module">module.md</span> <span class="tree-tag ctx">context</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">│ └──</span> <span class="tree-icon" style="color:var(--accent-amber);">📁</span> <span class="tree-name folder">database/</span></div> | |
| <div class="tree-line reveal-item" data-module="4"><span class="tree-indent">│ └──</span> <span class="tree-icon">📄</span> <span class="tree-name module">module.md</span> <span class="tree-tag ctx">context</span></div> | |
| <div class="tree-line reveal-item"><span class="tree-indent">└──</span> <span class="tree-icon">📜</span> <span class="tree-name" style="color:var(--text-ghost);">CONVENTIONS.md</span></div> | |
| </div> | |
| </div> | |
| <!-- RIGHT: Orchestration Flow --> | |
| <div style="flex:0.9;display:flex;flex-direction:column;gap:1.2rem;"> | |
| <!-- Hub Diagram --> | |
| <div class="card reveal-item" style="flex:1;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;min-height:220px;"> | |
| <!-- SVG Connection Diagram --> | |
| <svg viewBox="0 0 520 260" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%;position:absolute;inset:0;padding:10px;"> | |
| <!-- Module nodes (left side) --> | |
| <rect class="flow-node-rect" x="10" y="12" width="160" height="42" rx="8" fill="rgba(167,139,250,0.06)" stroke="rgba(167,139,250,0.25)"/> | |
| <text class="flow-node-text" x="90" y="38" fill="var(--accent-purple)" font-size="9" text-anchor="middle" dominant-baseline="middle">auth/module.md</text> | |
| <rect class="flow-node-rect" x="10" y="66" width="160" height="42" rx="8" fill="rgba(167,139,250,0.06)" stroke="rgba(167,139,250,0.25)"/> | |
| <text class="flow-node-text" x="90" y="92" fill="var(--accent-purple)" font-size="9" text-anchor="middle" dominant-baseline="middle">payments/module.md</text> | |
| <rect class="flow-node-rect" x="10" y="120" width="160" height="42" rx="8" fill="rgba(167,139,250,0.06)" stroke="rgba(167,139,250,0.25)"/> | |
| <text class="flow-node-text" x="90" y="146" fill="var(--accent-purple)" font-size="9" text-anchor="middle" dominant-baseline="middle">api/module.md</text> | |
| <rect class="flow-node-rect" x="10" y="174" width="160" height="42" rx="8" fill="rgba(167,139,250,0.06)" stroke="rgba(167,139,250,0.25)"/> | |
| <text class="flow-node-text" x="90" y="200" fill="var(--accent-purple)" font-size="9" text-anchor="middle" dominant-baseline="middle">database/module.md</text> | |
| <!-- Connection lines from modules to orchestrator --> | |
| <line class="flow-line" x1="170" y1="33" x2="300" y2="100" stroke="rgba(167,139,250,0.2)"/> | |
| <line class="flow-line" x1="170" y1="87" x2="300" y2="110" stroke="rgba(167,139,250,0.2)"/> | |
| <line class="flow-line" x1="170" y1="141" x2="300" y2="122" stroke="rgba(167,139,250,0.2)"/> | |
| <line class="flow-line" x1="170" y1="195" x2="300" y2="134" stroke="rgba(167,139,250,0.2)"/> | |
| <!-- Central Orchestrator node --> | |
| <rect rx="14" ry="14" x="300" y="76" width="200" height="82" fill="rgba(100,255,218,0.06)" stroke="rgba(100,255,218,0.35)" stroke-width="1.5"/> | |
| <!-- Pulse ring --> | |
| <rect rx="18" ry="18" x="293" y="69" width="214" height="96" fill="none" stroke="rgba(100,255,218,0.1)" stroke-width="1" stroke-dasharray="4 4"> | |
| <animate attributeName="opacity" values="0.3;0.8;0.3" dur="3s" repeatCount="indefinite"/> | |
| </rect> | |
| <text class="flow-node-text" x="400" y="110" fill="var(--accent-teal)" font-size="13" font-weight="600" text-anchor="middle" dominant-baseline="middle">claude.md</text> | |
| <text class="flow-node-text" x="400" y="132" fill="var(--text-ghost)" font-size="9" text-anchor="middle" dominant-baseline="middle">gathers context → plans</text> | |
| <!-- Arrow from orchestrator to output --> | |
| <line class="flow-line" x1="400" y1="158" x2="400" y2="194" stroke="rgba(100,255,218,0.25)"/> | |
| <rect class="flow-node-rect" x="325" y="194" width="150" height="40" rx="8" fill="rgba(52,211,153,0.06)" stroke="rgba(52,211,153,0.25)"/> | |
| <text class="flow-node-text" x="400" y="218" fill="var(--accent-green)" font-size="9" text-anchor="middle" dominant-baseline="middle">Structured Plan</text> | |
| <!-- Animated dots traveling along lines --> | |
| <circle r="3" fill="var(--accent-teal)" opacity="0.8"> | |
| <animate attributeName="cx" values="170;300" dur="2s" repeatCount="indefinite"/> | |
| <animate attributeName="cy" values="33;100" dur="2s" repeatCount="indefinite"/> | |
| <animate attributeName="opacity" values="0;1;1;0" dur="2s" repeatCount="indefinite"/> | |
| </circle> | |
| <circle r="3" fill="var(--accent-teal)" opacity="0.8"> | |
| <animate attributeName="cx" values="170;300" dur="2.3s" repeatCount="indefinite" begin="0.4s"/> | |
| <animate attributeName="cy" values="87;110" dur="2.3s" repeatCount="indefinite" begin="0.4s"/> | |
| <animate attributeName="opacity" values="0;1;1;0" dur="2.3s" repeatCount="indefinite" begin="0.4s"/> | |
| </circle> | |
| <circle r="3" fill="var(--accent-teal)" opacity="0.8"> | |
| <animate attributeName="cx" values="170;300" dur="2.6s" repeatCount="indefinite" begin="0.8s"/> | |
| <animate attributeName="cy" values="141;122" dur="2.6s" repeatCount="indefinite" begin="0.8s"/> | |
| <animate attributeName="opacity" values="0;1;1;0" dur="2.6s" repeatCount="indefinite" begin="0.8s"/> | |
| </circle> | |
| <circle r="3" fill="var(--accent-teal)" opacity="0.8"> | |
| <animate attributeName="cx" values="170;300" dur="2.1s" repeatCount="indefinite" begin="1.2s"/> | |
| <animate attributeName="cy" values="195;134" dur="2.1s" repeatCount="indefinite" begin="1.2s"/> | |
| <animate attributeName="opacity" values="0;1;1;0" dur="2.1s" repeatCount="indefinite" begin="1.2s"/> | |
| </circle> | |
| <!-- Downward pulse dot --> | |
| <circle r="2.5" fill="var(--accent-green)" opacity="0.8"> | |
| <animate attributeName="cx" values="400;400" dur="1.5s" repeatCount="indefinite" begin="2s"/> | |
| <animate attributeName="cy" values="158;194" dur="1.5s" repeatCount="indefinite" begin="2s"/> | |
| <animate attributeName="opacity" values="0;1;1;0" dur="1.5s" repeatCount="indefinite" begin="2s"/> | |
| </circle> | |
| </svg> | |
| </div> | |
| <!-- How it works --> | |
| <div class="card reveal-item" style="padding:1.2rem 1.5rem;"> | |
| <h3 class="text-teal mb-2" style="font-size:0.85rem;">How the Orchestrator Works</h3> | |
| <ul class="bullet-list" style="font-size:0.82rem;gap:0.5rem;"> | |
| <li class="reveal-item">AI reads instruction files <em>first</em> — they define rules & conventions</li> | |
| <li class="reveal-item">Instruction files reference module docs — AI loads relevant context</li> | |
| <li class="reveal-item">AI outputs a <strong class="text-teal">structured plan</strong> following the architecture and constraints</li> | |
| <li class="reveal-item">Developer validates the Plan against their own knowledge</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 4: Phase 1 ═══════ --> | |
| <div class="slide" data-index="4"> | |
| <div class="big-number" style="color:rgba(167,139,250,0.08);">1</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge purple">Phase 1</div> | |
| <h2 class="reveal-item mb-2">Planning</h2> | |
| <p class="reveal-item mb-4" style="max-width:700px;">Align on scope, break work into safe chunks, and confirm AI tools have context — before writing a single line of code.</p> | |
| <div class="flex-row"> | |
| <div class="flex-1"> | |
| <h3 class="reveal-item text-purple mb-3" style="font-size:1.1rem;">Key Activities</h3> | |
| <ul class="bullet-list purple"> | |
| <li class="reveal-item"><strong>AI consults module docs first</strong> — surfaces constraints before developer plan</li> | |
| <li class="reveal-item"><strong>Time-boxed spikes</strong> for unknowns — prototyping the riskiest part</li> | |
| <li class="reveal-item"><strong>Decompose into sub-tasks</strong> — each having logical boundaries and clear success criteria</li> | |
| <li class="reveal-item"><strong>Estimate with ranges</strong> — "1-4 days, most likely 2"</li> | |
| </ul> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="card reveal-item"> | |
| <h3 class="mb-3" style="font-size:0.95rem;color:var(--text-primary);">Planning Discipline</h3> | |
| <table class="data-table" style="font-size:0.85rem;"> | |
| <thead><tr><th>Rule</th><th>Why</th></tr></thead> | |
| <tbody> | |
| <tr><td>Plan only first 2-3 tasks in detail</td><td class="text-ghost">Refine as understanding grows</td></tr> | |
| <tr><td>Re-evaluate after each sub-task</td><td class="text-ghost">Built into loop, not optional</td></tr> | |
| <tr><td>Feature flags for user-facing changes</td><td class="text-ghost">Decouple deploy from release</td></tr> | |
| <tr><td>Short-lived feature branches</td><td class="text-ghost">Merge within days, not weeks</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 4b: Sample PRD ═══════ --> | |
| <div class="slide" data-index="5"> | |
| <div class="big-number" style="color:rgba(167,139,250,0.08);">1</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge purple">Phase 1 — Example</div> | |
| <h2 class="reveal-item mb-3">Sample Planning PRD</h2> | |
| <!-- Markdown rendered in a contained doc box --> | |
| <div class="reveal-item" style="max-width:960px;max-height:72vh;overflow-y:auto;margin:0 auto;background:rgba(15,18,28,0.7);border:1px solid rgba(167,139,250,0.15);border-radius:14px;padding:2.2rem 2.8rem;box-shadow:0 4px 30px rgba(0,0,0,0.3);backdrop-filter:blur(8px);scrollbar-width:thin;scrollbar-color:rgba(167,139,250,0.25) transparent;"> | |
| <!-- Title bar --> | |
| <div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:1.5rem;padding-bottom:1rem;border-bottom:1px solid rgba(255,255,255,0.06);"> | |
| <div style="display:flex;gap:6px;"> | |
| <span style="width:10px;height:10px;border-radius:50%;background:#ff5f57;"></span> | |
| <span style="width:10px;height:10px;border-radius:50%;background:#febc2e;"></span> | |
| <span style="width:10px;height:10px;border-radius:50%;background:#28c840;"></span> | |
| </div> | |
| <span style="font-family:'JetBrains Mono',monospace;font-size:0.75rem;color:var(--text-ghost);margin-left:0.5rem;">samplePrd.md</span> | |
| </div> | |
| <!-- Rendered markdown content --> | |
| <div class="md-render"> | |
| <h1>Lineage Radius Feature — Product Requirements Document</h1> | |
| <hr> | |
| <h2>Overview</h2> | |
| <p>Add a <strong>Radius control</strong> to the data product lineage canvas that allows users to dynamically adjust the depth of lineage visualization by re-fetching data with different radius values.</p> | |
| <hr> | |
| <h2>Scope</h2> | |
| <table> | |
| <tbody> | |
| <tr><td><strong>In Scope</strong></td><td>Data product lineage (port-level view) in <code>FlowCanvas.tsx</code></td></tr> | |
| <tr><td><strong>Out of Scope</strong></td><td>Column-level lineage in <code>ColumnLineageCanvas.tsx</code></td></tr> | |
| </tbody> | |
| </table> | |
| <hr> | |
| <h2>User Story</h2> | |
| <blockquote>As a data product user, I want to control the depth of lineage visualization so that I can explore relationships at different levels without being overwhelmed by too much information.</blockquote> | |
| <hr> | |
| <h2>Acceptance Criteria</h2> | |
| <ul> | |
| <li>☐ Radius control appears in the ReactFlow Controls panel</li> | |
| <li>☐ Clicking control allows user to select radius (1–5)</li> | |
| <li>☐ Changing radius triggers an API call with the new radius parameter</li> | |
| <li>☐ Graph updates with new nodes and recalculated edges</li> | |
| <li>☐ Loading state displays during re-fetch</li> | |
| <li>☐ Control is only visible for data product lineage (not column lineage)</li> | |
| <li>☐ Default radius is <code>2</code></li> | |
| <li>☐ All existing tests pass</li> | |
| <li>☐ New unit tests cover radius functionality</li> | |
| </ul> | |
| <hr> | |
| <h2>Implementation Tasks</h2> | |
| <h3>1. API Enhancement</h3> | |
| <p><strong>File:</strong> <code>src/features/lineage/api/index.ts</code></p> | |
| <p>Update the <code>getLineage</code> endpoint to support an optional <code>radius</code> query parameter.</p> | |
| <ul> | |
| <li>Add <code>radius?: number</code> to the <code>QueryParams</code> type</li> | |
| <li>Append <code>radius</code> to the URL query string when provided</li> | |
| <li>Default behavior remains unchanged if <code>radius</code> is omitted</li> | |
| </ul> | |
| <h3>2. Radius Control Component</h3> | |
| <p><strong>File:</strong> <code>src/features/lineage/components/RadiusControlButton.tsx</code> <em>(new)</em></p> | |
| <ul> | |
| <li>Extend ReactFlow’s <code>ControlButton</code></li> | |
| <li>Display the current radius value</li> | |
| <li>Support radius options: <code>1, 2, 3, 4, 5</code></li> | |
| <li>Use a dropdown or increment/decrement UI pattern</li> | |
| </ul> | |
| <h3>3. Integrate Control into Canvas</h3> | |
| <p><strong>File:</strong> <code>src/features/lineage/components/CustomReactFlow.tsx</code></p> | |
| <ul> | |
| <li>Accept <code>radius</code> and <code>onRadiusChange</code> as props</li> | |
| <li>Render control inside existing <code><Controls></code> component</li> | |
| <li>Only render when lineage data is available</li> | |
| </ul> | |
| <h3>4. State Management in FlowCanvas</h3> | |
| <p><strong>File:</strong> <code>src/features/lineage/components/FlowCanvas.tsx</code></p> | |
| <ul> | |
| <li>Add <code>radius</code> state with default value of <code>2</code></li> | |
| <li>Add <code>onRadiusChange</code> callback prop</li> | |
| <li>Trigger the callback when radius changes</li> | |
| </ul> | |
| <h3>5. Parent Component Integration</h3> | |
| <p><strong>File:</strong> <code>src/features/lineage/components/Lineage.tsx</code></p> | |
| <ul> | |
| <li>Add <code>radius</code> state variable</li> | |
| <li>Pass <code>radius</code> to <code>useGetLineageQuery</code> hook</li> | |
| <li>Implement <code>handleRadiusChange</code> callback</li> | |
| </ul> | |
| <h3>6. Constants & Configuration</h3> | |
| <p><strong>File:</strong> <code>src/features/lineage/constants.ts</code></p> | |
| <pre><code>DEFAULT_RADIUS = 2 | |
| RADIUS_OPTIONS = [1, 2, 3, 4, 5] | |
| MIN_RADIUS = 1 | |
| MAX_RADIUS = 5</code></pre> | |
| <h3>7. Testing</h3> | |
| <table> | |
| <thead><tr><th>File</th><th>Type</th><th>Details</th></tr></thead> | |
| <tbody> | |
| <tr><td><code>RadiusControlButton.test.tsx</code></td><td>New</td><td>Radius display, change interactions, callback</td></tr> | |
| <tr><td><code>FlowCanvas.test.tsx</code></td><td>Update</td><td>Radius prop propagation, change handler</td></tr> | |
| <tr><td><code>Lineage.test.tsx</code></td><td>Update</td><td>Radius state management, API re-fetch</td></tr> | |
| </tbody> | |
| </table> | |
| <hr> | |
| <h2>Technical Considerations</h2> | |
| <ul> | |
| <li><code>radius</code> parameter controls how many hops/levels to fetch</li> | |
| <li>Show a loading indicator during re-fetch</li> | |
| <li>Optionally debounce rapid radius changes</li> | |
| <li>Cache previous radius results via RTK Query</li> | |
| </ul> | |
| <hr> | |
| <h2>Dependencies</h2> | |
| <table> | |
| <thead><tr><th>Dependency</th><th>Status</th></tr></thead> | |
| <tbody> | |
| <tr><td><code>@xyflow/react</code> (ReactFlow)</td><td>Existing</td></tr> | |
| <tr><td>RTK Query</td><td>Existing</td></tr> | |
| <tr><td>Lineage API with <code>radius</code> support</td><td style="color:var(--accent-amber);">Required from backend</td></tr> | |
| </tbody> | |
| </table> | |
| <hr> | |
| <h2>Estimated Complexity</h2> | |
| <p><strong>Medium</strong> — Requires an API update, a new component, state coordination across multiple layers, and test coverage.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 5: Phase 2 ═══════ --> | |
| <div class="slide" data-index="6"> | |
| <div class="big-number" style="color:rgba(251,191,36,0.08);">2</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge amber">Phase 2</div> | |
| <h2 class="reveal-item mb-2">Build & Test Loop</h2> | |
| <p class="reveal-item mb-4" style="max-width:700px;">Each sub-task follows a tight cycle. Automated gates run in parallel. AI assists but developer makes the final call.</p> | |
| <div class="flowchart reveal-item mb-4"> | |
| <svg viewBox="0 0 900 180" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <!-- Implement --> | |
| <rect class="flow-node-rect" x="0" y="55" width="120" height="60" fill="rgba(251,191,36,0.1)" stroke="rgba(251,191,36,0.35)"/> | |
| <text class="flow-node-text" x="60" y="85" fill="var(--accent-amber)" font-size="12">Implement</text> | |
| <line class="flow-line" x1="120" y1="85" x2="170" y2="85" stroke="rgba(251,191,36,0.25)"/> | |
| <!-- Auto Gates Group --> | |
| <rect rx="14" ry="14" x="170" y="5" width="380" height="170" fill="rgba(20,184,166,0.03)" stroke="rgba(20,184,166,0.15)" stroke-dasharray="6 4"/> | |
| <text x="185" y="25" fill="var(--text-ghost)" font-size="10" font-family="Inter">AUTOMATED GATES (PARALLEL)</text> | |
| <rect class="flow-node-rect" x="185" y="40" width="100" height="44" fill="rgba(20,184,166,0.08)" stroke="rgba(20,184,166,0.25)"/> | |
| <text class="flow-node-text" x="235" y="62" fill="var(--accent-green)" font-size="10">Linting</text> | |
| <rect class="flow-node-rect" x="295" y="40" width="100" height="44" fill="rgba(20,184,166,0.08)" stroke="rgba(20,184,166,0.25)"/> | |
| <text class="flow-node-text" x="345" y="62" fill="var(--accent-green)" font-size="10">Unit Tests</text> | |
| <rect class="flow-node-rect" x="185" y="95" width="100" height="44" fill="rgba(20,184,166,0.08)" stroke="rgba(20,184,166,0.25)"/> | |
| <text class="flow-node-text" x="235" y="117" fill="var(--accent-green)" font-size="10">SAST Scan</text> | |
| <rect class="flow-node-rect" x="295" y="95" width="100" height="44" fill="rgba(20,184,166,0.08)" stroke="rgba(20,184,166,0.25)"/> | |
| <text class="flow-node-text" x="345" y="117" fill="var(--accent-green)" font-size="10">Perf Check</text> | |
| <!-- Arrow connecting group to next --> | |
| <line class="flow-line" x1="415" y1="85" x2="550" y2="85" stroke="rgba(20,184,166,0.25)"/> | |
| <!-- AI Review --> | |
| <rect class="flow-node-rect" x="560" y="55" width="120" height="60" fill="rgba(167,139,250,0.1)" stroke="rgba(167,139,250,0.35)"/> | |
| <text class="flow-node-text" x="620" y="80" fill="var(--accent-purple)" font-size="11">AI Review</text> | |
| <text class="flow-node-text" x="620" y="95" fill="var(--text-ghost)" font-size="9">assistant, not gate</text> | |
| <line class="flow-line" x1="680" y1="85" x2="720" y2="85" stroke="rgba(167,139,250,0.25)"/> | |
| <!-- Human Review --> | |
| <rect class="flow-node-rect" x="720" y="55" width="130" height="60" fill="rgba(236,72,153,0.08)" stroke="rgba(236,72,153,0.3)"/> | |
| <text class="flow-node-text" x="785" y="80" fill="var(--accent-rose)" font-size="11">Human Review</text> | |
| <text class="flow-node-text" x="785" y="95" fill="var(--text-ghost)" font-size="9">final authority</text> | |
| </svg> | |
| </div> | |
| <div class="flex-row"> | |
| <div class="flex-1 card reveal-item" style="text-align:center;"> | |
| <span class="text-amber" style="font-size:2rem;font-weight:700;">SLA</span> | |
| <p style="font-size:0.85rem;margin-top:0.4rem;">Standard: <strong class="text-teal">2 hrs</strong><br>Hotfix: <strong class="text-amber">30 min</strong></p> | |
| </div> | |
| <div class="flex-1 card reveal-item" style="text-align:center;"> | |
| <span class="text-purple" style="font-size:2rem;font-weight:700;">Async</span> | |
| <p style="font-size:0.85rem;margin-top:0.4rem;">Devs move to next task<br>while review proceeds</p> | |
| </div> | |
| <div class="flex-1 card reveal-item" style="text-align:center;"> | |
| <span class="text-green" style="font-size:2rem;font-weight:700;">Risk-Tiered</span> | |
| <p style="font-size:0.85rem;margin-top:0.4rem;">Docs fix ≠ payment code<br>Different gates per risk</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 6: Phase 2 Quality Gates ═══════ --> | |
| <div class="slide" data-index="7"> | |
| <div class="big-number" style="color:rgba(251,191,36,0.08);">2</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge amber">Phase 2 — Quality Gates</div> | |
| <h2 class="reveal-item mb-4">Risk-Based Review Tiers</h2> | |
| <div class="card reveal-item"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr><th>Change Type</th><th>Required Gates</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr class="reveal-item"><td><strong>Documentation / config</strong></td><td>Linting + 1 reviewer</td></tr> | |
| <tr class="reveal-item"><td><strong>Standard feature work</strong></td><td>All automated gates + AI review + 1 reviewer</td></tr> | |
| <tr class="reveal-item"><td><strong class="text-rose">Security-sensitive paths</strong> <span class="text-ghost" style="font-size:0.8em;">(auth, payments, PII)</span></td><td>All gates + security reviewer + DAST on staging</td></tr> | |
| <tr class="reveal-item"><td><strong class="text-amber">Database migrations</strong></td><td>All gates + DBA review + backward-compat check</td></tr> | |
| <tr class="reveal-item"><td><strong class="text-purple">New external dependencies</strong></td><td>All gates + architecture review + license check</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="mt-4 card reveal-item" style="border-color:rgba(167,139,250,0.15);"> | |
| <div class="flex-row" style="align-items:center;"> | |
| <div style="font-size:2rem;">🤖</div> | |
| <div> | |
| <h3 style="font-size:0.95rem;">AI Review: Assistant, Not Authority</h3> | |
| <p style="font-size:0.85rem;">AI flags issues and reduces human review scope. AI approval alone is <strong class="text-rose">never sufficient</strong>. Human reviewer must independently verify security, business logic, and architecture fit.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 7: Phase 3 ═══════ --> | |
| <div class="slide" data-index="8"> | |
| <div class="big-number" style="color:rgba(96,165,250,0.08);">3</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge blue">Phase 3</div> | |
| <h2 class="reveal-item mb-2">Iteration & Completion Control</h2> | |
| <p class="reveal-item mb-4" style="max-width:700px;">Each checkpoint is an opportunity to adjust course. Work loops through chunks and sub-tasks until the feature is complete.</p> | |
| <div class="flex-row"> | |
| <div class="flex-1"> | |
| <h3 class="reveal-item text-blue mb-3" style="font-size:1.1rem;">Iteration Rules</h3> | |
| <ul class="bullet-list blue"> | |
| <li class="reveal-item"><strong>Atomic commits</strong> — each one can be understood and reverted independently</li> | |
| <li class="reveal-item"><strong>1-2 day sub-tasks</strong> — taking longer is a signal to re-decompose</li> | |
| <li class="reveal-item"><strong>Mandatory re-evaluation</strong> after each sub-task completion</li> | |
| <li class="reveal-item"><strong>Daily rebase</strong> against main — catch drift early</li> | |
| <li class="reveal-item"><strong>Branch age limit</strong> — 3 days = warning, 5 days = escalation</li> | |
| </ul> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="card reveal-item" style="border-color:rgba(96,165,250,0.15);"> | |
| <h3 class="text-blue mb-3" style="font-size:0.95rem;">After Each Sub-Task, Ask:</h3> | |
| <ul class="bullet-list blue" style="font-size:0.9rem;"> | |
| <li class="reveal-item">Has anything changed the original plan?</li> | |
| <li class="reveal-item">Are remaining estimates still valid?</li> | |
| <li class="reveal-item">Should priorities shift based on learnings?</li> | |
| </ul> | |
| <hr class="glow-divider" style="margin:1rem 0;background:linear-gradient(90deg,transparent,var(--accent-blue),transparent);box-shadow:0 0 12px rgba(96,165,250,0.2);"> | |
| <p class="reveal-item" style="font-size:0.85rem;color:var(--text-tertiary);">On feature completion → AI refreshes module docs → human reviews diff</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 8: Phase 4 ═══════ --> | |
| <div class="slide" data-index="9"> | |
| <div class="big-number" style="color:rgba(52,211,153,0.08);">4</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge green">Phase 4</div> | |
| <h2 class="reveal-item mb-2">Deploy, Validate & Learn</h2> | |
| <p class="reveal-item mb-4" style="max-width:700px;">Ship safely with progressive delivery. Deployment is the <strong class="text-green">beginning</strong> of validation, not the end.</p> | |
| <!-- Pipeline flowchart --> | |
| <div class="flowchart reveal-item mb-4"> | |
| <svg viewBox="0 0 760 100" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <rect class="flow-node-rect" x="0" y="20" width="110" height="60" rx="6" fill="rgba(96,165,250,0.08)" stroke="rgba(96,165,250,0.3)"/> | |
| <text class="flow-node-text" x="55" y="44" fill="var(--accent-blue)" font-size="10" text-anchor="middle">Merge to</text> | |
| <text class="flow-node-text" x="55" y="58" fill="var(--accent-blue)" font-size="10" text-anchor="middle">main</text> | |
| <line class="flow-line" x1="110" y1="50" x2="145" y2="50" stroke="rgba(96,165,250,0.25)"/> | |
| <rect class="flow-node-rect" x="145" y="20" width="110" height="60" rx="6" fill="rgba(6,182,212,0.08)" stroke="rgba(6,182,212,0.3)"/> | |
| <text class="flow-node-text" x="200" y="53" fill="#06b6d4" font-size="10" text-anchor="middle">CI/CD</text> | |
| <line class="flow-line" x1="255" y1="50" x2="290" y2="50" stroke="rgba(6,182,212,0.25)"/> | |
| <rect class="flow-node-rect" x="290" y="20" width="110" height="60" rx="6" fill="rgba(6,182,212,0.08)" stroke="rgba(6,182,212,0.3)"/> | |
| <text class="flow-node-text" x="345" y="44" fill="#06b6d4" font-size="10" text-anchor="middle">Integration</text> | |
| <text class="flow-node-text" x="345" y="58" fill="#06b6d4" font-size="10" text-anchor="middle">Tests</text> | |
| <line class="flow-line" x1="400" y1="50" x2="435" y2="50" stroke="rgba(167,139,250,0.25)"/> | |
| <rect class="flow-node-rect" x="435" y="20" width="110" height="60" rx="6" fill="rgba(167,139,250,0.08)" stroke="rgba(167,139,250,0.3)"/> | |
| <text class="flow-node-text" x="490" y="44" fill="var(--accent-purple)" font-size="10" text-anchor="middle">E2E</text> | |
| <text class="flow-node-text" x="490" y="58" fill="var(--accent-purple)" font-size="10" text-anchor="middle">Automation</text> | |
| <line class="flow-line" x1="545" y1="50" x2="580" y2="50" stroke="rgba(52,211,153,0.25)"/> | |
| <rect class="flow-node-rect" x="580" y="20" width="110" height="60" rx="6" fill="rgba(52,211,153,0.08)" stroke="rgba(52,211,153,0.3)"/> | |
| <text class="flow-node-text" x="635" y="44" fill="var(--accent-green)" font-size="10" text-anchor="middle">Full</text> | |
| <text class="flow-node-text" x="635" y="58" fill="var(--accent-green)" font-size="10" text-anchor="middle">Rollout</text> | |
| <line class="flow-line" x1="690" y1="50" x2="710" y2="50" stroke="rgba(52,211,153,0.25)"/> | |
| <rect class="flow-node-rect" x="710" y="20" width="50" height="60" rx="6" fill="rgba(52,211,153,0.12)" stroke="rgba(52,211,153,0.4)"/> | |
| <text class="flow-node-text" x="735" y="53" fill="var(--accent-green)" font-size="10" text-anchor="middle">✓</text> | |
| </svg> | |
| </div> | |
| <!-- Three column detail cards --> | |
| <div class="flex-row" style="gap:1.2rem;"> | |
| <div class="flex-1 card reveal-item"> | |
| <h3 class="text-amber mb-2" style="font-size:0.95rem;">Auto-Rollback</h3> | |
| <p style="font-size:0.85rem;margin-bottom:0.8rem;">If error rate or latency spikes post-deploy, <strong class="text-rose">automatic rollback</strong> triggers.</p> | |
| <ul style="font-size:0.8rem;list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:0.3rem;"> | |
| <li style="color:var(--text-tertiary);"><span style="color:var(--accent-amber);">●</span> Error rate threshold exceeded</li> | |
| <li style="color:var(--text-tertiary);"><span style="color:var(--accent-amber);">●</span> P99 latency spike detected</li> | |
| <li style="color:var(--text-tertiary);"><span style="color:var(--accent-amber);">●</span> Team alerted via PagerDuty</li> | |
| </ul> | |
| </div> | |
| <div class="flex-1 card reveal-item"> | |
| <h3 class="text-teal mb-2" style="font-size:0.95rem;">Post-Deploy Validation</h3> | |
| <p style="font-size:0.85rem;margin-bottom:0.8rem;">Production observation period confirms the release is healthy.</p> | |
| <ul style="font-size:0.8rem;list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:0.3rem;"> | |
| <li style="color:var(--text-tertiary);"><span style="color:var(--accent-teal);">●</span> SLO compliance checked</li> | |
| <li style="color:var(--text-tertiary);"><span style="color:var(--accent-teal);">●</span> Dashboards reviewed 24-48h</li> | |
| <li style="color:var(--text-tertiary);"><span style="color:var(--accent-teal);">●</span> Learnings feed next cycle</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 9: Rollback Strategy ═══════ --> | |
| <div class="slide" data-index="10"> | |
| <div class="big-number" style="color:rgba(52,211,153,0.08);">4</div> | |
| <div class="slide-content"> | |
| <div class="phase-badge green">Phase 4 — Safety Net</div> | |
| <h2 class="reveal-item mb-4">Always Have a Way Back</h2> | |
| <div class="card reveal-item mb-4"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr><th>Rollback Type</th><th>When</th><th>Time Target</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr class="reveal-item"><td><strong class="text-teal">Feature flag disable</strong></td><td>User-facing issues, business logic errors</td><td class="mono text-green">Seconds</td></tr> | |
| <tr class="reveal-item"><td><strong class="text-amber">Deployment rollback</strong></td><td>Infrastructure issues, perf degradation</td><td class="mono text-amber">< 15 min</td></tr> | |
| <tr class="reveal-item"><td><strong class="text-purple">Database rollback</strong></td><td>Schema migration caused data issues</td><td class="mono text-purple">Per runbook</td></tr> | |
| <tr class="reveal-item"><td><strong class="text-rose">Hotfix</strong></td><td>Bug requires new code, not just reverting</td><td class="mono text-rose">Hours</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="card reveal-item" style="border-color:rgba(100,255,218,0.15);"> | |
| <h3 class="text-teal mb-2" style="font-size:0.95rem;">The Feedback Loop</h3> | |
| <ul class="bullet-list" style="font-size:0.9rem;"> | |
| <li class="reveal-item"><strong>Post-deploy review within one week</strong> — what performed as expected? what surprised us?</li> | |
| <li class="reveal-item"><strong>Production telemetry feeds planning</strong> — real data informs next cycle's priorities</li> | |
| <li class="reveal-item"><strong>Team that ships owns production health</strong> — direct incentive for reliable code</li> | |
| <li class="reveal-item"><strong>SLO breaches auto-prioritize reliability</strong> — health degrades = features pause</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════ SLIDE 11: Closing ═══════ --> | |
| <div class="slide" data-index="12"> | |
| <div class="slide-content text-center"> | |
| <p class="uppercase text-teal reveal-item mb-4">Summary</p> | |
| <h2 class="reveal-item mb-3">Not a Pipeline.<br><span class="text-teal glow-teal">A Feedback System.</span></h2> | |
| <hr class="glow-divider reveal-item" style="max-width:200px;margin:1.5rem auto;"> | |
| <div class="reveal-item" style="max-width:650px;margin:0 auto;"> | |
| <p class="mb-4">Development insights feed back into planning. Every deployment teaches us something. The process evolves through retrospectives.</p> | |
| </div> | |
| <div class="reveal-item" style="max-width:800px;margin:0 auto;"> | |
| <div class="card" style="border-color:rgba(100,255,218,0.2);padding:1.5rem 2rem;"> | |
| <div style="display:flex;align-items:center;gap:1rem;margin-bottom:1rem;"> | |
| <div style="width:40px;height:40px;border-radius:10px;background:rgba(100,255,218,0.08);border:1px solid rgba(100,255,218,0.2);display:flex;align-items:center;justify-content:center;"> | |
| <span style="font-size:1.2rem;">⚙</span> | |
| </div> | |
| <div> | |
| <h3 class="text-teal" style="font-size:1.05rem;margin:0;">Prompt-Ops Framework</h3> | |
| <p style="font-size:0.75rem;color:var(--text-ghost);margin:0;">Centralized prompt distribution across teams</p> | |
| </div> | |
| </div> | |
| <p style="font-size:0.85rem;margin-bottom:1rem;">Reusable, version-controlled prompts packaged as a shared framework — so every team leverages battle-tested AI workflows without reinventing them.</p> | |
| <div style="display:flex;gap:1rem;"> | |
| <div style="flex:1;padding:0.8rem;border-radius:8px;background:rgba(100,255,218,0.04);border:1px solid rgba(100,255,218,0.1);"> | |
| <div style="font-size:0.7rem;color:var(--accent-teal);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.4rem;">Distribute</div> | |
| <p style="font-size:0.78rem;color:var(--text-tertiary);margin:0;">Spec templates, planning prompts, review checklists — shared across all product teams</p> | |
| </div> | |
| <div style="flex:1;padding:0.8rem;border-radius:8px;background:rgba(167,139,250,0.04);border:1px solid rgba(167,139,250,0.1);"> | |
| <div style="font-size:0.7rem;color:var(--accent-purple);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.4rem;">Standardize</div> | |
| <p style="font-size:0.78rem;color:var(--text-tertiary);margin:0;">Consistent AI interactions — same quality gates, same constraint summaries, same output format</p> | |
| </div> | |
| <div style="flex:1;padding:0.8rem;border-radius:8px;background:rgba(251,191,36,0.04);border:1px solid rgba(251,191,36,0.1);"> | |
| <div style="font-size:0.7rem;color:var(--accent-amber);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.4rem;">Evolve</div> | |
| <p style="font-size:0.78rem;color:var(--text-tertiary);margin:0;">Version-controlled prompts improve over time — teams contribute learnings back to the framework</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4 reveal-item"> | |
| <span class="mono text-ghost" style="font-size:0.8rem;">UBS — Feature Development Workflow</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/animejs/dist/bundles/anime.umd.min.js"></script> | |
| <script> | |
| (function() { | |
| 'use strict'; | |
| // ── anime.js v4 imports ── | |
| const { animate, createTimeline, stagger } = anime; | |
| // ── State ── | |
| const slides = document.querySelectorAll('.slide'); | |
| const totalSlides = slides.length; | |
| let current = 0; | |
| let isAnimating = false; | |
| // ── Particles ── | |
| function initParticles() { | |
| const container = document.getElementById('particles'); | |
| for (let i = 0; i < 25; i++) { | |
| const p = document.createElement('div'); | |
| p.className = 'particle'; | |
| p.style.left = Math.random() * 100 + '%'; | |
| p.style.animationDuration = (10 + Math.random() * 15) + 's'; | |
| p.style.animationDelay = (Math.random() * 12) + 's'; | |
| const size = 2 + Math.random() * 4; | |
| p.style.width = size + 'px'; | |
| p.style.height = size + 'px'; | |
| container.appendChild(p); | |
| } | |
| } | |
| initParticles(); | |
| // ── Progress & Counter ── | |
| function updateUI() { | |
| const pct = totalSlides > 1 ? (current / (totalSlides - 1)) * 100 : 0; | |
| document.getElementById('progressBar').style.width = pct + '%'; | |
| document.getElementById('slideCounter').textContent = | |
| String(current + 1).padStart(2, '0') + ' / ' + String(totalSlides).padStart(2, '0'); | |
| // hide nav hint after first navigation | |
| if (current > 0) document.getElementById('navHint').style.display = 'none'; | |
| } | |
| updateUI(); | |
| // ── Animate Slide In ── | |
| function animateSlideIn(slide) { | |
| // Reset all reveal items | |
| const items = slide.querySelectorAll('.reveal-item'); | |
| items.forEach(el => { el.style.opacity = '0'; el.style.transform = 'translateY(30px)'; }); | |
| // Reveal items staggered | |
| if (items.length > 0) { | |
| animate(items, { | |
| opacity: [0, 1], | |
| translateY: [30, 0], | |
| duration: 700, | |
| ease: 'outExpo', | |
| delay: stagger(90, { start: 250 }), | |
| }); | |
| } | |
| // Animate SVG flow lines (draw effect) | |
| const flowLines = slide.querySelectorAll('.flow-line'); | |
| flowLines.forEach((line, i) => { | |
| let length; | |
| try { length = line.getTotalLength(); } catch(e) { length = 200; } | |
| line.style.strokeDasharray = length; | |
| line.style.strokeDashoffset = length; | |
| animate(line, { | |
| strokeDashoffset: [length, 0], | |
| duration: 700, | |
| ease: 'inOutCubic', | |
| delay: 500 + i * 150, | |
| }); | |
| }); | |
| // Animate SVG rects (scale in) | |
| const flowRects = slide.querySelectorAll('.flow-node-rect'); | |
| if (flowRects.length > 0) { | |
| flowRects.forEach(r => { r.style.opacity = '0'; }); | |
| animate(flowRects, { | |
| opacity: [0, 1], | |
| duration: 500, | |
| ease: 'outCubic', | |
| delay: stagger(80, { start: 300 }), | |
| }); | |
| } | |
| // Animate SVG text | |
| const flowTexts = slide.querySelectorAll('.flow-node-text'); | |
| if (flowTexts.length > 0) { | |
| flowTexts.forEach(t => { t.style.opacity = '0'; }); | |
| animate(flowTexts, { | |
| opacity: [0, 1], | |
| duration: 400, | |
| ease: 'outCubic', | |
| delay: stagger(60, { start: 500 }), | |
| }); | |
| } | |
| // Animate pipeline nodes | |
| const pipelineNodes = slide.querySelectorAll('.pipeline-node'); | |
| if (pipelineNodes.length > 0) { | |
| pipelineNodes.forEach(n => { n.style.opacity = '0'; n.style.transform = 'scale(0.8)'; }); | |
| animate(pipelineNodes, { | |
| opacity: [0, 1], | |
| scale: [0.8, 1], | |
| duration: 600, | |
| ease: 'outBack', | |
| delay: stagger(120, { start: 400 }), | |
| }); | |
| } | |
| // Animate pipeline arrows | |
| const pipeArrows = slide.querySelectorAll('.pipeline-arrow'); | |
| if (pipeArrows.length > 0) { | |
| pipeArrows.forEach(a => { a.style.opacity = '0'; a.style.transform = 'scaleX(0)'; }); | |
| animate(pipeArrows, { | |
| opacity: [0, 1], | |
| scaleX: [0, 1], | |
| duration: 400, | |
| ease: 'outCubic', | |
| delay: stagger(120, { start: 600 }), | |
| }); | |
| } | |
| // Animate stat blocks | |
| const statBlocks = slide.querySelectorAll('.stat-block'); | |
| if (statBlocks.length > 0) { | |
| statBlocks.forEach(s => { s.style.opacity = '0'; s.style.transform = 'translateY(20px) scale(0.95)'; }); | |
| animate(statBlocks, { | |
| opacity: [0, 1], | |
| translateY: [20, 0], | |
| scale: [0.95, 1], | |
| duration: 600, | |
| ease: 'outBack', | |
| delay: stagger(80, { start: 400 }), | |
| }); | |
| } | |
| // Animate table rows | |
| const tableRows = slide.querySelectorAll('.data-table tbody tr.reveal-item'); | |
| if (tableRows.length > 0) { | |
| tableRows.forEach(r => { r.style.opacity = '0'; r.style.transform = 'translateX(-20px)'; }); | |
| animate(tableRows, { | |
| opacity: [0, 1], | |
| translateX: [-20, 0], | |
| duration: 500, | |
| ease: 'outCubic', | |
| delay: stagger(100, { start: 500 }), | |
| }); | |
| } | |
| } | |
| // ── Transition Between Slides ── | |
| async function goTo(index) { | |
| if (index < 0 || index >= totalSlides || index === current || isAnimating) return; | |
| isAnimating = true; | |
| const direction = index > current ? 1 : -1; | |
| const outSlide = slides[current]; | |
| const inSlide = slides[index]; | |
| // Prep incoming slide: hide reveal items BEFORE making it visible | |
| const revealItems = inSlide.querySelectorAll('.reveal-item'); | |
| revealItems.forEach(el => { el.style.opacity = '0'; el.style.transform = 'translateY(30px)'; }); | |
| const flowRects = inSlide.querySelectorAll('.flow-node-rect'); | |
| flowRects.forEach(r => { r.style.opacity = '0'; }); | |
| const flowTexts = inSlide.querySelectorAll('.flow-node-text'); | |
| flowTexts.forEach(t => { t.style.opacity = '0'; }); | |
| const pipelineNodes = inSlide.querySelectorAll('.pipeline-node'); | |
| pipelineNodes.forEach(n => { n.style.opacity = '0'; n.style.transform = 'scale(0.8)'; }); | |
| const pipeArrows = inSlide.querySelectorAll('.pipeline-arrow'); | |
| pipeArrows.forEach(a => { a.style.opacity = '0'; a.style.transform = 'scaleX(0)'; }); | |
| const statBlocks = inSlide.querySelectorAll('.stat-block'); | |
| statBlocks.forEach(s => { s.style.opacity = '0'; s.style.transform = 'translateY(20px) scale(0.95)'; }); | |
| const tableRows = inSlide.querySelectorAll('.data-table tbody tr.reveal-item'); | |
| tableRows.forEach(r => { r.style.opacity = '0'; r.style.transform = 'translateX(-20px)'; }); | |
| // Set incoming slide initial state | |
| const clipFrom = direction > 0 | |
| ? 'polygon(100% 0, 100% 0, 100% 100%, 100% 100%)' | |
| : 'polygon(0 0, 0 0, 0 100%, 0 100%)'; | |
| const clipTo = 'polygon(0 0, 100% 0, 100% 100%, 0 100%)'; | |
| inSlide.classList.add('active'); | |
| inSlide.style.opacity = '0'; | |
| inSlide.style.clipPath = clipFrom; | |
| // Animate outgoing slide | |
| const outAnim = animate(outSlide, { | |
| opacity: [1, 0], | |
| filter: ['blur(0px)', 'blur(6px)'], | |
| scale: [1, 0.97], | |
| duration: 500, | |
| ease: 'inCubic', | |
| }); | |
| // Small delay, then animate incoming | |
| await new Promise(r => setTimeout(r, 200)); | |
| inSlide.style.filter = 'blur(4px)'; | |
| const inAnim = animate(inSlide, { | |
| opacity: [0, 1], | |
| clipPath: [clipFrom, clipTo], | |
| filter: ['blur(4px)', 'blur(0px)'], | |
| scale: [1.02, 1], | |
| duration: 700, | |
| ease: 'outExpo', | |
| }); | |
| // After outgoing finishes | |
| await new Promise(r => setTimeout(r, 400)); | |
| outSlide.classList.remove('active'); | |
| outSlide.style.opacity = '0'; | |
| outSlide.style.filter = ''; | |
| outSlide.style.transform = ''; | |
| outSlide.style.clipPath = ''; | |
| // Update state | |
| current = index; | |
| updateUI(); | |
| // Animate content of new slide (items already prepped above) | |
| animateSlideIn(inSlide); | |
| // Wait for animations to settle | |
| await new Promise(r => setTimeout(r, 600)); | |
| inSlide.style.clipPath = ''; | |
| inSlide.style.filter = ''; | |
| inSlide.style.transform = ''; | |
| isAnimating = false; | |
| } | |
| function next() { goTo(current + 1); } | |
| function prev() { goTo(current - 1); } | |
| // ── Keyboard Navigation ── | |
| document.addEventListener('keydown', function(e) { | |
| if (isAnimating) return; | |
| switch(e.key) { | |
| case 'ArrowRight': case 'ArrowDown': case ' ': case 'PageDown': | |
| e.preventDefault(); next(); break; | |
| case 'ArrowLeft': case 'ArrowUp': case 'PageUp': | |
| e.preventDefault(); prev(); break; | |
| case 'Home': e.preventDefault(); goTo(0); break; | |
| case 'End': e.preventDefault(); goTo(totalSlides - 1); break; | |
| } | |
| if (e.key >= '1' && e.key <= '9') { | |
| const idx = parseInt(e.key) - 1; | |
| if (idx < totalSlides) goTo(idx); | |
| } | |
| }); | |
| // ── Touch / Swipe ── | |
| let touchStartX = 0, touchStartY = 0; | |
| document.addEventListener('touchstart', function(e) { | |
| touchStartX = e.touches[0].clientX; | |
| touchStartY = e.touches[0].clientY; | |
| }, { passive: true }); | |
| document.addEventListener('touchend', function(e) { | |
| const dx = e.changedTouches[0].clientX - touchStartX; | |
| const dy = e.changedTouches[0].clientY - touchStartY; | |
| if (Math.abs(dx) < 50 && Math.abs(dy) < 50) return; | |
| if (Math.abs(dx) > Math.abs(dy)) { | |
| if (dx < 0) next(); else prev(); | |
| } else { | |
| if (dy < 0) next(); else prev(); | |
| } | |
| }, { passive: true }); | |
| // ── Initial animation for first slide ── | |
| requestAnimationFrame(function() { | |
| animateSlideIn(slides[0]); | |
| }); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment