-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
444 lines (361 loc) · 14.2 KB
/
script.js
File metadata and controls
444 lines (361 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
const button = document.getElementById('anxiousBtn');
const buttonyes = document.getElementById('yesBtn');
const buttonText = button.querySelector('.button-text');
const panicMessage = document.getElementById('panicMessage');
const buttonZone = document.querySelector('.button-zone');
let currentAnxietyLevel = 'calm';
let hasBeenClicked = false;
let isRunningAway = false;
// --- Background music with exposed low-pass filter controls ---
// Note: browsers require a user gesture to start audio; we start on first pointer/key interaction.
// Tweakables:
// - Default slider values live in index.html.
// - These constants control the "Yes" transition + filter opening behavior.
const SHOW_AUDIO_CONTROLS = false;
const YES_REVEAL_FLASH_MS = 800; // dip-to-white duration before the image fades in
const FILTER_OPEN_CUTOFF_HZ = 20000; // effectively "no low-pass"
const FILTER_OPEN_TIME_CONSTANT = 1.1; // larger = slower smoothing
const bgmEl = document.getElementById('bgm');
const audioControlsEl = document.querySelector('.audio-controls');
const audioStatusEl = document.getElementById('audioStatus');
const lpCutoffEl = document.getElementById('lpCutoff');
const lpCutoffValEl = document.getElementById('lpCutoffVal');
const lpQEl = document.getElementById('lpQ');
const lpQValEl = document.getElementById('lpQVal');
const bgmVolEl = document.getElementById('bgmVol');
const bgmVolValEl = document.getElementById('bgmVolVal');
let audioCtx = null;
let lpFilter = null;
let bgmGain = null;
let bgmSource = null;
let audioStarted = false;
if (audioControlsEl) {
audioControlsEl.classList.toggle('is-hidden', !SHOW_AUDIO_CONTROLS);
}
function clamp(n, min, max) {
return Math.max(min, Math.min(max, n));
}
function updateAudioUI() {
if (lpCutoffEl && lpCutoffValEl) lpCutoffValEl.textContent = String(lpCutoffEl.value);
if (lpQEl && lpQValEl) lpQValEl.textContent = String(lpQEl.value);
if (bgmVolEl && bgmVolValEl) bgmVolValEl.textContent = String(bgmVolEl.value);
}
function applyFilterSettings() {
if (!audioCtx || !lpFilter) return;
const now = audioCtx.currentTime;
const cutoff = clamp(parseFloat(lpCutoffEl?.value ?? '1200'), 40, 20000);
const q = clamp(parseFloat(lpQEl?.value ?? '0.8'), 0.0001, 30);
lpFilter.frequency.cancelScheduledValues(now);
lpFilter.frequency.setTargetAtTime(cutoff, now, 0.03);
lpFilter.Q.cancelScheduledValues(now);
lpFilter.Q.setTargetAtTime(q, now, 0.03);
}
function applyVolume() {
if (!audioCtx || !bgmGain) return;
const now = audioCtx.currentTime;
const vol = clamp(parseFloat(bgmVolEl?.value ?? '0.35'), 0, 1);
bgmGain.gain.cancelScheduledValues(now);
bgmGain.gain.setTargetAtTime(vol, now, 0.03);
}
async function ensureAudioStarted() {
if (!bgmEl) return;
if (audioStarted) return;
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) {
if (audioStatusEl) audioStatusEl.textContent = 'AudioContext not supported';
return;
}
audioCtx = new AC();
lpFilter = audioCtx.createBiquadFilter();
lpFilter.type = 'lowpass';
bgmGain = audioCtx.createGain();
// Media element sources can only be created once per audio element.
bgmSource = audioCtx.createMediaElementSource(bgmEl);
bgmSource.connect(lpFilter);
lpFilter.connect(bgmGain);
bgmGain.connect(audioCtx.destination);
updateAudioUI();
applyFilterSettings();
applyVolume();
try {
await audioCtx.resume();
await bgmEl.play();
audioStarted = true;
if (audioStatusEl) audioStatusEl.textContent = 'Playing';
} catch (err) {
if (audioStatusEl) audioStatusEl.textContent = 'Click to start (autoplay blocked)';
// Swallow: user can click again, or provide a valid audio file at audio/bgm.mp3.
}
}
function removeLowPassFilterSmooth() {
if (!audioCtx || !lpFilter) return;
const now = audioCtx.currentTime;
// Smoothly "remove" the low-pass effect by opening the cutoff all the way up.
lpFilter.frequency.cancelScheduledValues(now);
lpFilter.frequency.setTargetAtTime(FILTER_OPEN_CUTOFF_HZ, now, FILTER_OPEN_TIME_CONSTANT);
// Lower resonance to avoid a noticeable peak while the filter opens.
lpFilter.Q.cancelScheduledValues(now);
lpFilter.Q.setTargetAtTime(0.0001, now, FILTER_OPEN_TIME_CONSTANT);
if (audioStatusEl) audioStatusEl.textContent = 'Filter opened';
}
// Start audio on first interaction anywhere on the page.
document.addEventListener('pointerdown', () => { ensureAudioStarted(); }, { once: true });
document.addEventListener('keydown', () => { ensureAudioStarted(); }, { once: true });
// Live tweak controls.
if (lpCutoffEl) lpCutoffEl.addEventListener('input', () => { updateAudioUI(); applyFilterSettings(); });
if (lpQEl) lpQEl.addEventListener('input', () => { updateAudioUI(); applyFilterSettings(); });
if (bgmVolEl) bgmVolEl.addEventListener('input', () => { updateAudioUI(); applyVolume(); });
updateAudioUI();
// Panic messages for different anxiety levels
const panicMessages = {
'slightly-nervous': [
"Um... hi there?",
"Oh, you're getting close...",
"I see you...",
"Please be gentle..."
],
'nervous': [
"Wait, what are you doing?",
"I'm not ready for this!",
"Can we talk about this?",
"Please don't!",
"I'm getting nervous..."
],
'very-nervous': [
"No no no no no!",
"PLEASE STOP!",
"I can't handle this!",
"Why are you doing this?!",
"I'm literally shaking!",
"BACK AWAY!"
],
'panicking': [
"I'M FREAKING OUT!!!",
"NOOOOOO!!!",
"SOMEBODY HELP!!!",
"I CAN'T BREATHE!",
"THIS IS TOO MUCH!",
"LEAVE ME ALONE!!!",
"I'M OUTTA HERE!"
]
};
// Success messages
const successMessages = [
"You did it! 🎉",
"I'm okay! 💚",
"That wasn't so bad!",
"We're friends now! 😊",
"Phew! I survived!"
];
let messageTimeout;
// Calculate distance between cursor and button
function getDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
// Get button center position
function getButtonCenter() {
const rect = button.getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
}
// Update anxiety level based on distance
function updateAnxietyLevel(distance) {
let newLevel;
if (distance > 200) {
newLevel = 'calm';
} else if (distance > 150) {
newLevel = 'slightly-nervous';
} else if (distance > 100) {
newLevel = 'nervous';
} else if (distance > 60) {
newLevel = 'very-nervous';
} else {
newLevel = 'panicking';
}
if (newLevel !== currentAnxietyLevel) {
currentAnxietyLevel = newLevel;
updateButtonState();
}
}
// Update button appearance and show messages
function updateButtonState() {
// Remove all anxiety classes
button.classList.remove('calm', 'slightly-nervous', 'nervous', 'very-nervous', 'panicking');
document.body.classList.remove('level-calm', 'level-slightly-nervous', 'level-nervous', 'level-very-nervous', 'level-panicking');
// Add current anxiety class
button.classList.add(currentAnxietyLevel);
document.body.classList.add('level-' + currentAnxietyLevel);
// Show panic message for nervous states
if (currentAnxietyLevel !== 'calm' && !hasBeenClicked) {
showPanicMessage();
} else if (currentAnxietyLevel === 'calm') {
hidePanicMessage();
}
}
// Show a random panic message
function showPanicMessage() {
if (panicMessages[currentAnxietyLevel]) {
const messages = panicMessages[currentAnxietyLevel];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
panicMessage.textContent = randomMessage;
panicMessage.classList.add('show');
// Clear previous timeout
clearTimeout(messageTimeout);
// Hide message after 2 seconds if level doesn't change
messageTimeout = setTimeout(() => {
if (currentAnxietyLevel !== 'panicking') {
hidePanicMessage();
}
}, 2000);
}
}
// Hide panic message
function hidePanicMessage() {
panicMessage.classList.remove('show');
}
// Move button away from cursor
function moveButtonAway(cursorX, cursorY) {
if (hasBeenClicked || isRunningAway) return;
const buttonCenter = getButtonCenter();
const distance = getDistance(cursorX, cursorY, buttonCenter.x, buttonCenter.y);
// Only run away when panicking
if (currentAnxietyLevel === 'panicking' && distance < 80) {
isRunningAway = true;
button.classList.add('running');
// Calculate direction away from cursor
const angle = Math.atan2(buttonCenter.y - cursorY, buttonCenter.x - cursorX);
const moveDistance = 150;
const newX = Math.cos(angle) * moveDistance;
const newY = Math.sin(angle) * moveDistance;
// Get button zone boundaries
const zoneRect = buttonZone.getBoundingClientRect();
const buttonRect = button.getBoundingClientRect();
// Calculate new position relative to button zone
let finalX = buttonCenter.x - zoneRect.left + newX - buttonRect.width / 2;
let finalY = buttonCenter.y - zoneRect.top + newY - buttonRect.height / 2;
// Keep button within boundaries
const margin = 50;
finalX = Math.max(margin, Math.min(zoneRect.width - buttonRect.width - margin, finalX));
finalY = Math.max(margin, Math.min(zoneRect.height - buttonRect.height - margin, finalY));
button.style.left = finalX + 'px';
button.style.top = finalY + 'px';
// Reset running state
setTimeout(() => {
button.classList.remove('running');
isRunningAway = false;
}, 200);
}
}
// Track mouse movement
document.addEventListener('mousemove', (e) => {
if (hasBeenClicked) return;
const buttonCenter = getButtonCenter();
const distance = getDistance(e.clientX, e.clientY, buttonCenter.x, buttonCenter.y);
// Add micro-jitter for realism when nervous
if (currentAnxietyLevel !== 'calm' && !isRunningAway) {
const jitterX = (Math.random() - 0.5) * 4;
const jitterY = (Math.random() - 0.5) * 4;
button.style.transform = `translate(${jitterX}px, ${jitterY}px)`;
} else if (!isRunningAway) {
button.style.transform = '';
}
updateAnxietyLevel(distance);
moveButtonAway(e.clientX, e.clientY);
});
// Handle button click
button.addEventListener('click', (e) => {
e.preventDefault();
if (!hasBeenClicked) {
hasBeenClicked = true;
// Final ultimate prank: BSOD after a short delay
setTimeout(() => {
const bsod = document.getElementById('bsod');
bsod.classList.add('active');
// Hide the container to sell the effect
document.querySelector('.container').style.opacity = '0';
document.body.style.background = '#0078d7';
}, 800);
// Remove all anxiety classes
button.classList.remove('calm', 'slightly-nervous', 'nervous', 'very-nervous', 'panicking', 'running');
// Add clicked state
button.classList.add('clicked');
// Show success message
const randomSuccess = successMessages[Math.floor(Math.random() * successMessages.length)];
buttonText.textContent = randomSuccess;
// Hide panic message
hidePanicMessage();
// Create confetti
createConfetti();
}
});
// Create confetti effect
function createConfetti() {
const colors = ['#FFE66D', '#FF6B6B', '#4ECDC4', '#95E1D3', '#A8E6CF'];
const buttonRect = button.getBoundingClientRect();
const centerX = buttonRect.left + buttonRect.width / 2;
const centerY = buttonRect.top + buttonRect.height / 2;
for (let i = 0; i < 50; i++) {
setTimeout(() => {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confetti.style.left = centerX + (Math.random() - 0.5) * 100 + 'px';
confetti.style.top = centerY + 'px';
confetti.style.width = Math.random() * 10 + 5 + 'px';
confetti.style.height = confetti.style.width;
confetti.style.animationDuration = Math.random() * 2 + 2 + 's';
confetti.style.animationDelay = Math.random() * 0.5 + 's';
document.body.appendChild(confetti);
setTimeout(() => {
confetti.remove();
}, 4000);
}, i * 20);
}
}
// Reset button to initial state
function resetButton() {
hasBeenClicked = false;
currentAnxietyLevel = 'calm';
button.classList.remove('clicked');
button.classList.add('calm');
buttonText.textContent = 'Click Me?';
// Reset position to center
button.style.left = '';
button.style.top = '';
}
// Initialize button state
button.classList.add('calm');
// Handle yes button click
buttonyes.addEventListener('click', (e) => {
e.preventDefault();
if (!hasBeenClicked) {
hasBeenClicked = true;
// Ensure audio is running (user gesture = this click), then open the filter during the transition.
ensureAudioStarted().finally(() => {
removeLowPassFilterSmooth();
});
// Fullscreen reveal with a dip-to-white + fade in to image.
const reveal = document.getElementById('yesReveal');
const flash = reveal?.querySelector('.yes-reveal__flash');
const img = document.getElementById('yesRevealImg');
if (reveal && flash && img) {
reveal.classList.add('active');
reveal.setAttribute('aria-hidden', 'false');
flash.classList.add('on');
img.classList.remove('show');
// Quick flash, then fade in the image.
setTimeout(() => {
flash.classList.remove('on');
img.classList.add('show');
}, YES_REVEAL_FLASH_MS);
}
// Add clicked state
buttonyes.classList.add('clicked');
// Hide panic message
hidePanicMessage();
// Create confetti
createConfetti();
}
});