Skip to content

Commit abb7ddf

Browse files
authored
big red button added
1 parent dacc58a commit abb7ddf

4 files changed

Lines changed: 359 additions & 77 deletions

File tree

‎hero-animation.js‎

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ window.startHeroAnimation = () => {
33
if (!canvas) return;
44
const ctx = canvas.getContext('2d');
55

6+
// --- NEW: PREVENT DUPLICATE ANIMATIONS ON RESET ---
7+
if (window.heroAnimationFrame) {
8+
cancelAnimationFrame(window.heroAnimationFrame);
9+
}
10+
611
const name = "DANIEL HAN";
712
const initialCountdown = (Math.random() * 1.5 + 3.5).toFixed(2);
813
let countdown = parseFloat(initialCountdown);
@@ -16,6 +21,12 @@ window.startHeroAnimation = () => {
1621
let crawlStartTime = 0;
1722
let isWaving = false;
1823

24+
// --- NEW STATES FOR THE BUTTON TRIGGER ---
25+
window.isCountingDown = false;
26+
window.triggerDetonation = () => {
27+
window.isCountingDown = true;
28+
};
29+
1930
let glitchState = 0;
2031
let bodyOffsetX = 0;
2132
let bodyOffsetY = 0;
@@ -183,13 +194,8 @@ window.startHeroAnimation = () => {
183194
this.y += this.vy;
184195
this.rotation += this.spin;
185196

186-
if (this.x < 50) {
187-
this.x = 50;
188-
this.vx = Math.abs(this.vx) * 0.7;
189-
} else if (this.x > canvas.width - 50) {
190-
this.x = canvas.width - 50;
191-
this.vx = -Math.abs(this.vx) * 0.7;
192-
}
197+
if (this.x < 50) { this.x = 50; this.vx *= -0.7; }
198+
else if (this.x > canvas.width - 50) { this.x = canvas.width - 50; this.vx *= -0.7; }
193199

194200
const floorY = canvas.height / 2 + 150;
195201
if (this.y > floorY) {
@@ -219,14 +225,12 @@ window.startHeroAnimation = () => {
219225
cTargetX += (Math.random() - 0.5) * 8;
220226
cTargetRot += (Math.random() - 0.5) * 0.15;
221227
}
222-
// Right arm swings while walking instead of the left
223228
if (this.id === 'r_arm_1' || this.id === 'r_arm_2') {
224229
cTargetX += Math.sin(walkCycle + Math.PI) * 20;
225230
cTargetRot += Math.sin(walkCycle + Math.PI) * 0.3;
226231
}
227232
}
228233

229-
// PHASE 4: LEFT ARM REACHES DOWN
230234
if (glitchState === 4 && (this.id === 'l_arm_1' || this.id === 'l_arm_2')) {
231235
const headParticle = particles.find(p => p.id === 'head');
232236
if (this.id === 'l_arm_2') {
@@ -240,15 +244,13 @@ window.startHeroAnimation = () => {
240244
}
241245
}
242246

243-
// PHASE 5: LEFT HAND HOLDS HEAD
244247
if (this.id === 'head' && glitchState === 5) {
245248
const lHand = particles.find(p => p.id === 'l_arm_2');
246249
this.x += (lHand.x - this.x) * 0.4;
247250
this.y += (lHand.y - 15 - this.y) * 0.4;
248251
this.rotation += (0 - this.rotation) * 0.1;
249252
}
250253

251-
// PHASE 5: LEFT ARM MOVES HEAD BACK TO NECK
252254
if (glitchState === 5 && (this.id === 'l_arm_1' || this.id === 'l_arm_2')) {
253255
const neckX = canvas.width / 2 + bodyOffsetX;
254256
const neckY = canvas.height / 2 - 110 + bodyOffsetY;
@@ -278,7 +280,6 @@ window.startHeroAnimation = () => {
278280
}
279281

280282
} else {
281-
// PHASE 0: RIGHT ARM WAVING ('L')
282283
if (this.id === 'r_arm_2') {
283284
const waveAngle = -0.85 + Math.sin(Date.now() * 0.003) * 0.6;
284285
const pivotX = canvas.width / 2 + 45;
@@ -365,17 +366,16 @@ window.startHeroAnimation = () => {
365366
const cx = canvas.width / 2;
366367
const cy = canvas.height / 2 + 30;
367368

368-
// PERFECTED NEW TARGET MAPPING
369369
const targets = [
370-
{ x: cx, y: cy - 110, rot: (Math.random()-0.5)*0.3, id: 'head' }, // D
371-
{ x: cx - 85, y: cy - 5, rot: 0.8, id: 'l_arm_2' }, // A (Left Hand)
372-
{ x: cx - 45, y: cy - 40, rot: 0.8, id: 'l_arm_1' }, // N (Left Arm)
373-
{ x: cx, y: cy - 40, rot: 0, id: 'torso_1' }, // I (Spine)
374-
{ x: cx + 45, y: cy - 60, rot: -0.4, id: 'r_arm_1' }, // E (Right Arm, Raised)
375-
{ x: cx + 80, y: cy - 100, rot: -0.85, id: 'r_arm_2' }, // L (Right Hand, Waving Hook)
376-
{ x: cx - 35, y: cy + 80, rot: 0, id: 'l_leg' }, // H (Left Leg)
377-
{ x: cx, y: cy + 10, rot: 0, id: 'torso_2' }, // A (Pelvis)
378-
{ x: cx + 35, y: cy + 80, rot: 0, id: 'r_leg' } // N (Right Leg)
370+
{ x: cx, y: cy - 110, rot: (Math.random()-0.5)*0.3, id: 'head' },
371+
{ x: cx - 85, y: cy - 5, rot: 0.8, id: 'l_arm_2' },
372+
{ x: cx - 45, y: cy - 40, rot: 0.8, id: 'l_arm_1' },
373+
{ x: cx, y: cy - 40, rot: 0, id: 'torso_1' },
374+
{ x: cx + 45, y: cy - 60, rot: -0.4, id: 'r_arm_1' },
375+
{ x: cx + 80, y: cy - 100, rot: -0.85, id: 'r_arm_2' },
376+
{ x: cx - 35, y: cy + 80, rot: 0, id: 'l_leg' },
377+
{ x: cx, y: cy + 10, rot: 0, id: 'torso_2' },
378+
{ x: cx + 35, y: cy + 80, rot: 0, id: 'r_leg' }
379379
];
380380

381381
particles.forEach((p, index) => {
@@ -389,6 +389,20 @@ window.startHeroAnimation = () => {
389389
});
390390
}
391391

392+
function drawIdleName() {
393+
ctx.save();
394+
ctx.translate(canvas.width / 2, canvas.height / 2);
395+
ctx.font = 'bold 80px "JetBrains Mono"';
396+
ctx.fillStyle = "#00ff41";
397+
ctx.shadowColor = "#00ff41";
398+
// Very slow, calm breathing effect before the button is clicked
399+
ctx.shadowBlur = 10 + Math.sin(Date.now() / 400) * 5;
400+
ctx.textAlign = 'center';
401+
ctx.textBaseline = 'middle';
402+
ctx.fillText(name, 0, 0);
403+
ctx.restore();
404+
}
405+
392406
function drawPulsingName() {
393407
const progress = 1 - (countdown / initialCountdown);
394408
const intensity = Math.pow(progress, 4);
@@ -404,24 +418,36 @@ window.startHeroAnimation = () => {
404418
ctx.textAlign = 'center';
405419
ctx.textBaseline = 'middle';
406420
ctx.fillText(name, 0, 0);
421+
422+
if (countdown < 0.6 && Math.random() > 0.4) {
423+
ctx.fillStyle = "rgba(255, 0, 60, 0.7)";
424+
ctx.fillText(name, (Math.random()-0.5)*20, (Math.random()-0.5)*20);
425+
ctx.fillStyle = "rgba(0, 212, 255, 0.7)";
426+
ctx.fillText(name, (Math.random()-0.5)*20, (Math.random()-0.5)*20);
427+
}
407428
ctx.restore();
408429
}
409430

410431
function animate() {
411-
const cycleTime = Date.now() % 16000;
432+
// --- MASTER TIMELINE UPDATED TO 17 SECONDS ---
433+
const cycleTime = Date.now() % 17000;
412434
let newPhase = 0;
413435

414-
if (cycleTime > 5000 && cycleTime < 5400) newPhase = 1;
415-
else if (cycleTime >= 5400 && cycleTime < 6800) newPhase = 2;
416-
else if (cycleTime >= 6800 && cycleTime < 10000) newPhase = 3;
417-
else if (cycleTime >= 10000 && cycleTime < 11200) newPhase = 4;
418-
else if (cycleTime >= 11200 && cycleTime < 14500) newPhase = 5;
419-
else if (cycleTime >= 14500 && cycleTime < 16000) newPhase = 6;
436+
// 1-SECOND GREEN PAUSE INSERTED IN THE MIDDLE OF THE SHAKE
437+
if (cycleTime > 5000 && cycleTime < 5200) newPhase = 1; // 1st half of violent red shake
438+
else if (cycleTime >= 5200 && cycleTime < 5900) newPhase = 0; // 1-second calm green pause
439+
else if (cycleTime >= 5900 && cycleTime < 6400) newPhase = 1; // 2nd half of violent red shake
440+
441+
// The rest of the Frankenstein walk cycle (Shifted +1000ms)
442+
else if (cycleTime >= 6400 && cycleTime < 7800) newPhase = 2;
443+
else if (cycleTime >= 7800 && cycleTime < 11000) newPhase = 3;
444+
else if (cycleTime >= 11000 && cycleTime < 12200) newPhase = 4;
445+
else if (cycleTime >= 12200 && cycleTime < 15500) newPhase = 5;
446+
else if (cycleTime >= 15500 && cycleTime < 17000) newPhase = 6;
420447

421448
if (newPhase === 3) {
422449
const headParticle = particles.find(p => p.id === 'head');
423450
if (headParticle) {
424-
// Because head bounces left, body walks to the right of the head
425451
let targetOffsetX = headParticle.x - (canvas.width / 2) + 35;
426452
let dist = targetOffsetX - bodyOffsetX;
427453
if (Math.abs(dist) > 30) {
@@ -458,7 +484,6 @@ window.startHeroAnimation = () => {
458484
const head = particles.find(p => p.id === 'head');
459485
if (head) {
460486
head.vy = -12 - Math.random() * 4;
461-
// Force bounce LEFT so the left arm can grab it
462487
head.vx = -Math.abs(Math.random() * 5 + 4);
463488
head.spin = (Math.random() - 0.5) * 0.8;
464489

@@ -470,9 +495,34 @@ window.startHeroAnimation = () => {
470495
glitchState = newPhase;
471496

472497
const allLettersStopped = exploded && particles.length > 0 && particles.every(p => p.stopped);
498+
499+
// --- TWITCHING HEAD WAKE-UP SEQUENCE ---
473500
if (allLettersStopped && !isCrawling && !isWaving) {
474501
if (timeSinceStop === 0) timeSinceStop = Date.now();
475-
if (Date.now() - timeSinceStop > 2500) {
502+
503+
let elapsedIdle = Date.now() - timeSinceStop;
504+
505+
// Between 1.5s and 1.7s, the "D" (head) violently twitches
506+
if (elapsedIdle > 1500 && elapsedIdle < 1700) {
507+
508+
// INCREASED FREQUENCY: Now twitches on 90% of frames instead of 50%
509+
if (Math.random() > 0.1) {
510+
// INCREASED MAGNITUDE: Jumps up to 14 pixels instead of 6
511+
particles[0].x += (Math.random() - 0.5) * 14;
512+
particles[0].y += (Math.random() - 0.5) * 14;
513+
514+
// Thrashes its rotation twice as hard
515+
particles[0].rotation += (Math.random() - 0.5) * 0.8;
516+
517+
// Bleeds more often while thrashing
518+
if (Math.random() > 0.6) {
519+
splatters.push(new BloodSplatter(particles[0].x, particles[0].y, (Math.random()-0.5)*6, (Math.random()-0.5)*6, true));
520+
}
521+
}
522+
}
523+
524+
// After 2.7 seconds, start crawling
525+
if (elapsedIdle > 2700) {
476526
isCrawling = true;
477527
crawlStartTime = Date.now();
478528
assignStickFigureTargets();
@@ -486,7 +536,12 @@ window.startHeroAnimation = () => {
486536

487537
ctx.clearRect(0, 0, canvas.width, canvas.height);
488538

489-
if (!exploded) {
539+
// --- NEW RENDER LOGIC FOR THE IDLE/COUNTDOWN STATE ---
540+
if (!isCountingDown) {
541+
// STATE 1: Waiting for the user to click the button
542+
drawIdleName();
543+
} else if (!exploded) {
544+
// STATE 2: Button clicked! Start the countdown
490545
countdown = (parseFloat(countdown) - 0.016).toFixed(2);
491546
if (countdown <= 0) {
492547
countdown = 0;
@@ -495,11 +550,8 @@ window.startHeroAnimation = () => {
495550
document.body.style.animation = "glitch 0.3s 2";
496551
}
497552
drawPulsingName();
498-
ctx.fillStyle = "#ff003c";
499-
ctx.font = 'bold 24px "JetBrains Mono"';
500-
ctx.textAlign = "center";
501-
ctx.fillText(`DETONATION IN: ${countdown}s`, canvas.width / 2, canvas.height / 2 + 120);
502553
} else {
554+
// STATE 3: Exploded, run the Frankenstein logic
503555
splatters = splatters.filter(s => s.alpha > 0);
504556
splatters.forEach(s => {
505557
s.update(allLettersStopped);
@@ -511,7 +563,7 @@ window.startHeroAnimation = () => {
511563
p.draw();
512564
});
513565
}
514-
animationFrame = requestAnimationFrame(animate);
566+
window.heroAnimationFrame = requestAnimationFrame(animate);
515567
}
516568

517569
resize();

‎index.html‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,45 @@
4141
<canvas id="hero-canvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; pointer-events: none;"></canvas> <div id="target-text" style="display:none;">DANIEL HAN</div>
4242
</section>
4343

44+
<div id="detonation-container" style="display: none; opacity: 0;">
45+
46+
<div class="arrow-cluster">
47+
<div class="arrow-wrap" style="--angle: -85deg;">
48+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
49+
</div>
50+
<div class="arrow-wrap" style="--angle: -55deg;">
51+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
52+
</div>
53+
<div class="arrow-wrap" style="--angle: -25deg;">
54+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
55+
</div>
56+
<div class="arrow-wrap" style="--angle: 0deg;">
57+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
58+
</div>
59+
<div class="arrow-wrap" style="--angle: 25deg;">
60+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
61+
</div>
62+
<div class="arrow-wrap" style="--angle: 55deg;">
63+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
64+
</div>
65+
<div class="arrow-wrap" style="--angle: 85deg;">
66+
<svg width="35" height="50" viewBox="0 0 40 60"><path d="M20 60 L40 30 L28 30 L28 0 L12 0 L12 30 L0 30 Z" fill="#cc0000" /></svg>
67+
</div>
68+
</div>
69+
70+
<div class="button-scene">
71+
<div class="button-3d-wrapper">
72+
<div class="button-base"></div>
73+
<button id="detonate-btn" class="button-top">
74+
<span>DO NOT<br>PRESS</span>
75+
</button>
76+
</div>
77+
</div>
78+
79+
</div>
80+
81+
82+
4483
<a href="#about" class="scroll-indicator">
4584
<span class="scroll-text">Scroll Down</span>
4685
<svg class="scroll-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">

‎script.js‎

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,27 +163,42 @@ document.addEventListener('DOMContentLoaded', () => {
163163
document.body.classList.remove('loading');
164164
if (matrixInterval) clearInterval(matrixInterval);
165165

166-
// --- NEW: Trigger the name explosion here ---
167-
if (window.startHeroAnimation) window.startHeroAnimation();
166+
// 1. START THE IDLE CANVAS IMMEDIATELY
167+
if (window.startHeroAnimation) {
168+
window.startHeroAnimation();
169+
}
170+
171+
// 2. SHOW THE BUTTON
172+
const actionZone = document.getElementById('detonation-container');
173+
const detonateBtn = document.getElementById('detonate-btn');
174+
175+
if (actionZone && detonateBtn) {
176+
actionZone.style.display = 'flex';
177+
setTimeout(() => {
178+
actionZone.style.opacity = '1';
179+
}, 50);
180+
181+
// 3. TRIGGER COUNTDOWN ON CLICK
182+
detonateBtn.onclick = () => {
183+
actionZone.style.opacity = '0';
184+
setTimeout(() => {
185+
actionZone.style.display = 'none';
186+
// Fire the countdown trigger!
187+
if (window.triggerDetonation) {
188+
window.triggerDetonation();
189+
}
190+
}, 400);
191+
};
192+
}
168193
}
169194
});
170195

171196
const terminalWindow = document.querySelector('.terminal-window');
172-
173197
if (terminalWindow) {
174-
tl.to(terminalWindow, {
175-
opacity: 0,
176-
duration: 0.8,
177-
ease: "power2.inOut"
178-
});
198+
tl.to(terminalWindow, { opacity: 0, duration: 0.8, ease: "power2.inOut" });
179199
}
180-
181200
if (overlay) {
182-
tl.to(overlay, {
183-
opacity: 0,
184-
duration: 0.8,
185-
ease: "power2.inOut"
186-
}, "<");
201+
tl.to(overlay, { opacity: 0, duration: 0.8, ease: "power2.inOut" }, "<");
187202
}
188203
}
189204

@@ -192,6 +207,32 @@ document.addEventListener('DOMContentLoaded', () => {
192207
} else {
193208
document.body.classList.remove('loading');
194209
if (overlay) overlay.style.display = 'none';
210+
if (matrixInterval) clearInterval(matrixInterval);
211+
212+
// --- THE MISSING FIX: Force load the canvas and button! ---
213+
if (window.startHeroAnimation) {
214+
window.startHeroAnimation();
215+
}
216+
217+
const actionZone = document.getElementById('detonation-container');
218+
const detonateBtn = document.getElementById('detonate-btn');
219+
220+
if (actionZone && detonateBtn) {
221+
actionZone.style.display = 'flex';
222+
actionZone.style.opacity = '1'; // Instantly visible since we skipped the intro
223+
224+
// Attach the click event so the button actually works
225+
detonateBtn.onclick = () => {
226+
actionZone.style.opacity = '0';
227+
setTimeout(() => {
228+
actionZone.style.display = 'none';
229+
if (window.triggerDetonation) {
230+
window.triggerDetonation();
231+
}
232+
}, 400);
233+
};
234+
}
235+
// -----------------------------------------------------------
195236

196237
setTimeout(() => {
197238
const target = document.querySelector(window.location.hash);
@@ -299,4 +340,5 @@ document.addEventListener('DOMContentLoaded', () => {
299340
});
300341

301342
resetInactivityTimer();
343+
302344
});

0 commit comments

Comments
 (0)