909 lines
23 KiB
HTML
909 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>TDD Workshop — 80 Minutes</title>
|
|
<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=JetBrains+Mono:wght@400;700;800&family=Archivo+Black&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--red: #FF3B30;
|
|
--green: #34C759;
|
|
--refactor: #007AFF;
|
|
--bg-dark: #0A0A0A;
|
|
--bg-light: #F5F5F0;
|
|
--text-dark: #1A1A1A;
|
|
--text-light: #FAFAFA;
|
|
--accent: #FFD60A;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
background: var(--bg-dark);
|
|
color: var(--text-light);
|
|
overflow: hidden;
|
|
cursor: none;
|
|
}
|
|
|
|
/* Custom Cursor */
|
|
#cursor {
|
|
position: fixed;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--accent);
|
|
border-radius: 50%;
|
|
pointer-events: none;
|
|
z-index: 10000;
|
|
transition: transform 0.15s ease, border-color 0.2s;
|
|
mix-blend-mode: difference;
|
|
}
|
|
|
|
#cursor.click {
|
|
transform: scale(0.7);
|
|
border-color: var(--green);
|
|
}
|
|
|
|
/* Slide Container */
|
|
.slides {
|
|
position: relative;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.slide {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 4rem;
|
|
opacity: 0;
|
|
transform: translateX(100%);
|
|
transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
|
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.slide.active {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
z-index: 10;
|
|
}
|
|
|
|
.slide.prev {
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
/* Slide Backgrounds */
|
|
.slide-title { background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1a2e 100%); }
|
|
.slide-red { background: linear-gradient(135deg, #2D0A0A 0%, var(--bg-dark) 100%); }
|
|
.slide-green { background: linear-gradient(135deg, #0A2D0F 0%, var(--bg-dark) 100%); }
|
|
.slide-refactor { background: linear-gradient(135deg, #0A1A2D 0%, var(--bg-dark) 100%); }
|
|
.slide-neutral { background: var(--bg-dark); }
|
|
|
|
/* Typography */
|
|
h1 {
|
|
font-family: 'Archivo Black', sans-serif;
|
|
font-size: clamp(3rem, 8vw, 7rem);
|
|
line-height: 0.95;
|
|
letter-spacing: -0.03em;
|
|
text-transform: uppercase;
|
|
margin-bottom: 2rem;
|
|
position: relative;
|
|
}
|
|
|
|
h1 .highlight {
|
|
display: inline-block;
|
|
padding: 0.1em 0.3em;
|
|
background: var(--accent);
|
|
color: var(--bg-dark);
|
|
transform: skewX(-5deg);
|
|
}
|
|
|
|
h2 {
|
|
font-size: clamp(2rem, 5vw, 4rem);
|
|
font-weight: 800;
|
|
margin-bottom: 1.5rem;
|
|
letter-spacing: -0.02em;
|
|
line-height: 1.6;
|
|
padding: 0.5em 0.2em;
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
h3 {
|
|
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
|
font-weight: 700;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
p, li {
|
|
font-size: clamp(1rem, 2vw, 1.5rem);
|
|
line-height: 1.6;
|
|
opacity: 0.85;
|
|
max-width: 50ch;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: clamp(1.2rem, 2.5vw, 2rem);
|
|
opacity: 0.7;
|
|
font-weight: 400;
|
|
}
|
|
|
|
/* Cycle Visualization */
|
|
.cycle {
|
|
display: flex;
|
|
gap: 2rem;
|
|
margin: 3rem 0;
|
|
align-items: center;
|
|
}
|
|
|
|
.cycle-step {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
animation: fadeInUp 0.6s ease forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
.cycle-step:nth-child(1) { animation-delay: 0.2s; }
|
|
.cycle-step:nth-child(2) { animation-delay: 0.4s; }
|
|
.cycle-step:nth-child(3) { animation-delay: 0.6s; }
|
|
.cycle-step:nth-child(4) { animation-delay: 0.8s; }
|
|
|
|
.cycle-icon {
|
|
width: 140px;
|
|
height: 140px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 3.5rem;
|
|
font-weight: 800;
|
|
border: 4px solid;
|
|
position: relative;
|
|
overflow: visible;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.cycle-icon::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
transform: skewX(-20deg);
|
|
animation: shine 3s infinite;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.red-step .cycle-icon {
|
|
background: var(--red);
|
|
border-color: var(--red);
|
|
color: white;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.green-step .cycle-icon {
|
|
background: var(--green);
|
|
border-color: var(--green);
|
|
color: white;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.refactor-step .cycle-icon {
|
|
background: var(--refactor);
|
|
border-color: var(--refactor);
|
|
color: white;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.repeat-step .cycle-icon {
|
|
background: transparent;
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.cycle-label {
|
|
font-size: 1.8rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
|
|
.cycle-desc {
|
|
font-size: 1.2rem;
|
|
opacity: 0.7;
|
|
text-align: center;
|
|
max-width: 20ch;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.arrow {
|
|
font-size: 2rem;
|
|
opacity: 0.4;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
/* Timeline */
|
|
.timeline {
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
margin: 2rem 0;
|
|
width: 100%;
|
|
max-width: 900px;
|
|
}
|
|
|
|
.timeline-item {
|
|
display: grid;
|
|
grid-template-columns: 100px 1fr;
|
|
gap: 2rem;
|
|
padding: 1.5rem;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-left: 4px solid var(--accent);
|
|
animation: slideInLeft 0.5s ease forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
.timeline-item:nth-child(1) { animation-delay: 0.1s; }
|
|
.timeline-item:nth-child(2) { animation-delay: 0.2s; }
|
|
.timeline-item:nth-child(3) { animation-delay: 0.3s; }
|
|
.timeline-item:nth-child(4) { animation-delay: 0.4s; }
|
|
.timeline-item:nth-child(5) { animation-delay: 0.5s; }
|
|
|
|
.timeline-time {
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.timeline-content h3 {
|
|
font-size: 1.3rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.timeline-content p {
|
|
font-size: 1rem;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* Ping Pong Diagram */
|
|
.ping-pong {
|
|
display: flex;
|
|
gap: 3rem;
|
|
margin: 3rem 0;
|
|
align-items: center;
|
|
}
|
|
|
|
.dev-role {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
animation: fadeInUp 0.6s ease forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
.dev-role:nth-child(1) { animation-delay: 0.2s; }
|
|
.dev-role:nth-child(2) { animation-delay: 0.5s; }
|
|
.dev-role:nth-child(3) { animation-delay: 0.8s; }
|
|
|
|
.dev-avatar {
|
|
width: 100px;
|
|
height: 100px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 2.5rem;
|
|
font-weight: 800;
|
|
border: 4px solid;
|
|
}
|
|
|
|
.dev-role:nth-child(1) .dev-avatar {
|
|
background: var(--red);
|
|
border-color: var(--red);
|
|
color: white;
|
|
}
|
|
|
|
.dev-role:nth-child(2) .dev-avatar {
|
|
background: var(--green);
|
|
border-color: var(--green);
|
|
color: white;
|
|
}
|
|
|
|
.dev-role:nth-child(3) .dev-avatar {
|
|
background: var(--refactor);
|
|
border-color: var(--refactor);
|
|
color: white;
|
|
}
|
|
|
|
.dev-name {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.dev-task {
|
|
font-size: 1rem;
|
|
opacity: 0.7;
|
|
text-align: center;
|
|
max-width: 20ch;
|
|
}
|
|
|
|
.flow-arrow {
|
|
font-size: 3rem;
|
|
opacity: 0.5;
|
|
animation: slideRight 1.5s infinite;
|
|
}
|
|
|
|
/* Lists */
|
|
ul {
|
|
list-style: none;
|
|
display: grid;
|
|
gap: 1rem;
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
li {
|
|
padding-left: 2rem;
|
|
position: relative;
|
|
animation: fadeInUp 0.5s ease forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
li:nth-child(1) { animation-delay: 0.1s; }
|
|
li:nth-child(2) { animation-delay: 0.2s; }
|
|
li:nth-child(3) { animation-delay: 0.3s; }
|
|
li:nth-child(4) { animation-delay: 0.4s; }
|
|
li:nth-child(5) { animation-delay: 0.5s; }
|
|
|
|
li::before {
|
|
content: '▸';
|
|
position: absolute;
|
|
left: 0;
|
|
color: var(--accent);
|
|
font-weight: 800;
|
|
}
|
|
|
|
/* Progress Bar */
|
|
.progress {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 4px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 100%;
|
|
background: var(--accent);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
/* Slide Counter */
|
|
.counter {
|
|
position: fixed;
|
|
bottom: 2rem;
|
|
right: 2rem;
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
opacity: 0.5;
|
|
z-index: 1000;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Navigation Hint */
|
|
.nav-hint {
|
|
position: fixed;
|
|
bottom: 2rem;
|
|
left: 2rem;
|
|
font-size: 0.9rem;
|
|
opacity: 0.4;
|
|
z-index: 1000;
|
|
animation: fadeIn 1s ease 2s forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
/* Animations */
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes slideInLeft {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(-30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
to { opacity: 0.4; }
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 0.4; }
|
|
50% { opacity: 0.8; }
|
|
}
|
|
|
|
@keyframes slideRight {
|
|
0%, 100% { transform: translateX(0); }
|
|
50% { transform: translateX(10px); }
|
|
}
|
|
|
|
@keyframes shine {
|
|
to { left: 100%; }
|
|
}
|
|
|
|
/* Code Block */
|
|
.code-block {
|
|
background: rgba(0, 0, 0, 0.5);
|
|
border: 2px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 2rem;
|
|
margin: 2rem 0;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 1.1rem;
|
|
line-height: 1.8;
|
|
max-width: 600px;
|
|
animation: fadeInUp 0.6s ease 0.3s forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
.code-comment {
|
|
color: #6A9955;
|
|
}
|
|
|
|
.code-keyword {
|
|
color: #569CD6;
|
|
}
|
|
|
|
.code-string {
|
|
color: #CE9178;
|
|
}
|
|
|
|
/* Big Number */
|
|
.big-number {
|
|
font-size: clamp(8rem, 20vw, 15rem);
|
|
font-weight: 800;
|
|
line-height: 1;
|
|
color: var(--accent);
|
|
opacity: 0.2;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
z-index: 0;
|
|
}
|
|
|
|
.content-over {
|
|
position: relative;
|
|
z-index: 1;
|
|
max-width: 90%;
|
|
padding: 1rem 0;
|
|
}
|
|
|
|
/* Phase indicator circles */
|
|
.phase-indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 1em;
|
|
height: 1em;
|
|
border-radius: 50%;
|
|
margin-right: 0.3em;
|
|
vertical-align: middle;
|
|
position: relative;
|
|
top: -0.05em;
|
|
}
|
|
|
|
.phase-red {
|
|
background: var(--red);
|
|
box-shadow: 0 0 20px rgba(255, 59, 48, 0.5);
|
|
}
|
|
|
|
.phase-green {
|
|
background: var(--green);
|
|
box-shadow: 0 0 20px rgba(52, 199, 89, 0.5);
|
|
}
|
|
|
|
.phase-blue {
|
|
background: var(--refactor);
|
|
box-shadow: 0 0 20px rgba(0, 122, 255, 0.5);
|
|
}
|
|
@media (max-width: 768px) {
|
|
.cycle {
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.ping-pong {
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.arrow, .flow-arrow {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.timeline-item {
|
|
grid-template-columns: 1fr;
|
|
gap: 0.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="cursor"></div>
|
|
|
|
<div class="slides">
|
|
<!-- Slide 1: Title -->
|
|
<div class="slide active slide-title">
|
|
<div class="content-over">
|
|
<h1>
|
|
<span class="highlight">TDD</span><br>
|
|
WORKSHOP
|
|
</h1>
|
|
<p class="subtitle">80 minutes · 3-Way Ping-Pong · Hands-on Practice</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 2: The Cycle -->
|
|
<div class="slide slide-neutral">
|
|
<div class="content-over">
|
|
<h2>The TDD Rhythm</h2>
|
|
<div class="cycle">
|
|
<div class="cycle-step red-step">
|
|
<div class="cycle-icon">1</div>
|
|
<div class="cycle-label">Red</div>
|
|
<div class="cycle-desc">Write failing test</div>
|
|
</div>
|
|
<div class="arrow">→</div>
|
|
<div class="cycle-step green-step">
|
|
<div class="cycle-icon">2</div>
|
|
<div class="cycle-label">Green</div>
|
|
<div class="cycle-desc">Make it pass</div>
|
|
</div>
|
|
<div class="arrow">→</div>
|
|
<div class="cycle-step refactor-step">
|
|
<div class="cycle-icon">3</div>
|
|
<div class="cycle-label">Refactor</div>
|
|
<div class="cycle-desc">Clean up code</div>
|
|
</div>
|
|
<div class="arrow">→</div>
|
|
<div class="cycle-step repeat-step">
|
|
<div class="cycle-icon">↻</div>
|
|
<div class="cycle-label">Repeat</div>
|
|
<div class="cycle-desc">Next test</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 3: Schedule -->
|
|
<div class="slide slide-neutral">
|
|
<div class="big-number">80</div>
|
|
<div class="content-over">
|
|
<h2>Today's Schedule</h2>
|
|
<div class="timeline">
|
|
<div class="timeline-item">
|
|
<div class="timeline-time">0:00</div>
|
|
<div class="timeline-content">
|
|
<h3>Welcome & Setup</h3>
|
|
<p>5 minutes · Verify environment</p>
|
|
</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-time">0:05</div>
|
|
<div class="timeline-content">
|
|
<h3>Live Demo: FizzBuzz</h3>
|
|
<p>15 minutes · Watch TDD in action</p>
|
|
</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-time">0:20</div>
|
|
<div class="timeline-content">
|
|
<h3>Exercise Intro</h3>
|
|
<p>5 minutes · 3-Way Ping-Pong explained</p>
|
|
</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-time">0:25</div>
|
|
<div class="timeline-content">
|
|
<h3>Hands-on Practice</h3>
|
|
<p>40 minutes · Mob programming</p>
|
|
</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-time">1:05</div>
|
|
<div class="timeline-content">
|
|
<h3>Retrospective</h3>
|
|
<p>15 minutes · Reflect & discuss</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 4: 3-Way Ping-Pong -->
|
|
<div class="slide slide-neutral">
|
|
<div class="content-over">
|
|
<h2>3-Way Ping-Pong</h2>
|
|
<div class="ping-pong">
|
|
<div class="dev-role">
|
|
<div class="dev-avatar">A</div>
|
|
<div class="dev-name">Red</div>
|
|
<div class="dev-task">Uncomment test<br>Watch it fail</div>
|
|
</div>
|
|
<div class="flow-arrow">→</div>
|
|
<div class="dev-role">
|
|
<div class="dev-avatar">B</div>
|
|
<div class="dev-name">Green</div>
|
|
<div class="dev-task">Write minimal code<br>Make it pass</div>
|
|
</div>
|
|
<div class="flow-arrow">→</div>
|
|
<div class="dev-role">
|
|
<div class="dev-avatar">C</div>
|
|
<div class="dev-name">Refactor</div>
|
|
<div class="dev-task">Clean up<br>Next test</div>
|
|
</div>
|
|
</div>
|
|
<p style="margin-top: 3rem; text-align: center; opacity: 0.7;">
|
|
Rotate roles with each cycle · One shared screen
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 5: RED Phase -->
|
|
<div class="slide slide-red">
|
|
<div class="content-over">
|
|
<h2 style="color: var(--red);"><span class="phase-indicator phase-red"></span>RED Phase</h2>
|
|
<h3>Write a failing test</h3>
|
|
<ul>
|
|
<li>Uncomment ONE test</li>
|
|
<li>Run <code>dart test</code></li>
|
|
<li>Watch it fail (proves it can fail)</li>
|
|
<li>Pass the keyboard</li>
|
|
</ul>
|
|
<div class="code-block">
|
|
<span class="code-comment">// Uncomment this test</span><br>
|
|
<span class="code-keyword">test</span>(<span class="code-string">'returns 1 for input 1'</span>, () {<br>
|
|
<span class="code-keyword">expect</span>(fizzbuzz(<span class="code-string">1</span>), <span class="code-string">'1'</span>);<br>
|
|
});
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 6: GREEN Phase -->
|
|
<div class="slide slide-green">
|
|
<div class="content-over">
|
|
<h2 style="color: var(--green);"><span class="phase-indicator phase-green"></span>GREEN Phase</h2>
|
|
<h3>Make it pass (minimal code)</h3>
|
|
<ul>
|
|
<li>Write the simplest code that works</li>
|
|
<li>Don't worry about perfection</li>
|
|
<li>Run tests to confirm GREEN</li>
|
|
<li>Pass the keyboard</li>
|
|
</ul>
|
|
<div class="code-block">
|
|
<span class="code-keyword">String</span> fizzbuzz(<span class="code-keyword">int</span> n) {<br>
|
|
<span class="code-keyword">return</span> <span class="code-string">'1'</span>; <span class="code-comment">// Hardcode is OK!</span><br>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 7: REFACTOR Phase -->
|
|
<div class="slide slide-refactor">
|
|
<div class="content-over">
|
|
<h2 style="color: var(--refactor);"><span class="phase-indicator phase-blue"></span>REFACTOR Phase</h2>
|
|
<h3>Clean up the code</h3>
|
|
<ul>
|
|
<li>Remove duplication</li>
|
|
<li>Improve names</li>
|
|
<li>Tests stay GREEN</li>
|
|
<li>Uncomment next test (back to RED)</li>
|
|
<li>Pass the keyboard</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 8: Kata Choices -->
|
|
<div class="slide slide-neutral">
|
|
<div class="content-over">
|
|
<h2>Choose Your Kata</h2>
|
|
<div style="display: grid; gap: 2rem; margin-top: 2rem;">
|
|
<div style="padding: 2rem; background: rgba(255, 255, 255, 0.05); border-left: 4px solid var(--accent);">
|
|
<h3>Password Validator</h3>
|
|
<p>Rules-based validation · Refactoring challenge · Open-Closed Principle</p>
|
|
</div>
|
|
<div style="padding: 2rem; background: rgba(255, 255, 255, 0.05); border-left: 4px solid var(--green);">
|
|
<h3>Shopping Cart</h3>
|
|
<p>Stateful domain object · Data structures · Value Objects</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 9: Key Principles -->
|
|
<div class="slide slide-neutral">
|
|
<div class="content-over">
|
|
<h2>TDD Principles</h2>
|
|
<ul>
|
|
<li><strong>Tests first</strong> — Even for "obvious" code</li>
|
|
<li><strong>Small steps</strong> — One test at a time</li>
|
|
<li><strong>See RED</strong> — Proves the test can fail</li>
|
|
<li><strong>Minimal GREEN</strong> — Hardcoding is OK initially</li>
|
|
<li><strong>Refactor fearlessly</strong> — Tests protect you</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 10: Questions -->
|
|
<div class="slide slide-title">
|
|
<div class="content-over">
|
|
<h1>
|
|
Ready to<br>
|
|
<span class="highlight">Practice?</span>
|
|
</h1>
|
|
<p class="subtitle">Let's build muscle memory for TDD</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="progress">
|
|
<div class="progress-bar"></div>
|
|
</div>
|
|
|
|
<!-- Slide Counter -->
|
|
<div class="counter">
|
|
<span id="current">1</span> / <span id="total">10</span>
|
|
</div>
|
|
|
|
<!-- Navigation Hint -->
|
|
<div class="nav-hint">
|
|
← → arrows or click to navigate
|
|
</div>
|
|
|
|
<script>
|
|
// Custom cursor
|
|
const cursor = document.getElementById('cursor');
|
|
document.addEventListener('mousemove', (e) => {
|
|
cursor.style.left = e.clientX + 'px';
|
|
cursor.style.top = e.clientY + 'px';
|
|
});
|
|
|
|
document.addEventListener('mousedown', () => {
|
|
cursor.classList.add('click');
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
cursor.classList.remove('click');
|
|
});
|
|
|
|
// Slide navigation
|
|
const slides = document.querySelectorAll('.slide');
|
|
const progressBar = document.querySelector('.progress-bar');
|
|
const currentCounter = document.getElementById('current');
|
|
const totalCounter = document.getElementById('total');
|
|
|
|
let currentSlide = 0;
|
|
const totalSlides = slides.length;
|
|
|
|
totalCounter.textContent = totalSlides;
|
|
|
|
function updateSlide() {
|
|
slides.forEach((slide, index) => {
|
|
slide.classList.remove('active', 'prev');
|
|
if (index === currentSlide) {
|
|
slide.classList.add('active');
|
|
} else if (index < currentSlide) {
|
|
slide.classList.add('prev');
|
|
}
|
|
});
|
|
|
|
// Update progress bar
|
|
const progress = ((currentSlide + 1) / totalSlides) * 100;
|
|
progressBar.style.width = progress + '%';
|
|
|
|
// Update counter
|
|
currentCounter.textContent = currentSlide + 1;
|
|
}
|
|
|
|
function nextSlide() {
|
|
if (currentSlide < totalSlides - 1) {
|
|
currentSlide++;
|
|
updateSlide();
|
|
}
|
|
}
|
|
|
|
function prevSlide() {
|
|
if (currentSlide > 0) {
|
|
currentSlide--;
|
|
updateSlide();
|
|
}
|
|
}
|
|
|
|
// Keyboard navigation
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'ArrowRight' || e.key === ' ') {
|
|
e.preventDefault();
|
|
nextSlide();
|
|
} else if (e.key === 'ArrowLeft') {
|
|
e.preventDefault();
|
|
prevSlide();
|
|
} else if (e.key === 'Home') {
|
|
currentSlide = 0;
|
|
updateSlide();
|
|
} else if (e.key === 'End') {
|
|
currentSlide = totalSlides - 1;
|
|
updateSlide();
|
|
}
|
|
});
|
|
|
|
// Click navigation
|
|
document.addEventListener('click', (e) => {
|
|
if (e.clientX > window.innerWidth / 2) {
|
|
nextSlide();
|
|
} else {
|
|
prevSlide();
|
|
}
|
|
});
|
|
|
|
// Touch navigation
|
|
let touchStartX = 0;
|
|
document.addEventListener('touchstart', (e) => {
|
|
touchStartX = e.touches[0].clientX;
|
|
});
|
|
|
|
document.addEventListener('touchend', (e) => {
|
|
const touchEndX = e.changedTouches[0].clientX;
|
|
const diff = touchStartX - touchEndX;
|
|
|
|
if (Math.abs(diff) > 50) {
|
|
if (diff > 0) {
|
|
nextSlide();
|
|
} else {
|
|
prevSlide();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize
|
|
updateSlide();
|
|
</script>
|
|
</body>
|
|
</html>
|