Spaces:
Sleeping
Sleeping
File size: 5,363 Bytes
759fd36 | 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 | const introScreen = document.querySelector('#introScreen');
const holdScreen = document.querySelector('#holdScreen');
const completeScreen = document.querySelector('#completeScreen');
const openButton = document.querySelector('#openButton');
const replayButton = document.querySelector('#replayButton');
const sendBackButton = document.querySelector('#sendBackButton');
const sendBackNote = document.querySelector('#sendBackNote');
const letterButton = document.querySelector('#letterButton');
const letterPanel = document.querySelector('#letterPanel');
const letterClose = document.querySelector('#letterClose');
const heartHold = document.querySelector('#heartHold');
const holdMeterFill = document.querySelector('#holdMeterFill');
const holdStatusText = document.querySelector('#holdStatusText');
const holdInstruction = document.querySelector('#holdInstruction');
const topbarNote = document.querySelector('#topbarNote');
const HOLD_DURATION = 3200;
let holdStartedAt = 0;
let holdFrame = null;
let isHolding = false;
let hasCompleted = false;
const holdLines = [
'I’m right here',
'A little closer…',
'That’s it, Pookie',
'You are so, so loved',
'I’ve got you'
];
function showScreen(screen) {
[introScreen, holdScreen, completeScreen].forEach((item) => {
const active = item === screen;
item.classList.toggle('is-active', active);
item.setAttribute('aria-hidden', String(!active));
});
}
function resetHold() {
cancelAnimationFrame(holdFrame);
isHolding = false;
holdStartedAt = 0;
heartHold.classList.remove('is-holding');
heartHold.style.setProperty('--progress', '0%');
holdMeterFill.style.width = '0%';
holdStatusText.textContent = 'I’m right here';
holdInstruction.textContent = 'Press and hold the heart to feel how much I love you.';
}
function beginExperience() {
resetHold();
showScreen(holdScreen);
topbarNote.textContent = 'hold close';
window.setTimeout(() => heartHold.focus(), 60);
}
function updateHold(now) {
if (!isHolding) return;
const elapsed = now - holdStartedAt;
const progress = Math.min(elapsed / HOLD_DURATION, 1);
const percent = `${Math.round(progress * 100)}%`;
heartHold.style.setProperty('--progress', percent);
holdMeterFill.style.width = percent;
const lineIndex = Math.min(Math.floor(progress * holdLines.length), holdLines.length - 1);
holdStatusText.textContent = holdLines[lineIndex];
if (progress >= 1) {
completeHug();
return;
}
holdFrame = requestAnimationFrame(updateHold);
}
function startHolding(event) {
if (hasCompleted || isHolding) return;
if (event?.type === 'keydown' && event.key !== ' ' && event.key !== 'Enter') return;
if (event?.cancelable) event.preventDefault();
isHolding = true;
holdStartedAt = performance.now();
heartHold.classList.add('is-holding');
holdInstruction.textContent = 'Keep holding… let the warmth find you.';
holdFrame = requestAnimationFrame(updateHold);
}
function stopHolding(event) {
if (event?.type === 'keyup' && event.key !== ' ' && event.key !== 'Enter') return;
if (event?.cancelable) event.preventDefault();
if (!isHolding || hasCompleted) return;
cancelAnimationFrame(holdFrame);
isHolding = false;
heartHold.classList.remove('is-holding');
holdStatusText.textContent = 'Almost… stay with me';
holdInstruction.textContent = 'You can hold a little longer for the full hug.';
}
function completeHug() {
cancelAnimationFrame(holdFrame);
isHolding = false;
hasCompleted = true;
heartHold.classList.remove('is-holding');
heartHold.style.setProperty('--progress', '100%');
holdMeterFill.style.width = '100%';
holdStatusText.textContent = 'Hug complete';
topbarNote.textContent = 'made of warmth';
window.setTimeout(() => {
showScreen(completeScreen);
window.setTimeout(() => replayButton.focus(), 80);
}, 420);
}
function replayHug() {
hasCompleted = false;
sendBackNote.textContent = '';
letterPanel.hidden = true;
beginExperience();
}
function openLetter() {
letterPanel.hidden = false;
letterButton.setAttribute('aria-expanded', 'true');
window.setTimeout(() => letterClose.focus(), 60);
}
function closeLetter() {
letterPanel.hidden = true;
letterButton.setAttribute('aria-expanded', 'false');
letterButton.focus();
}
function sendHugBack() {
sendBackNote.textContent = 'I felt that, Pookie. Keeping it close.';
sendBackButton.innerHTML = '<span aria-hidden="true">♥</span> Hug sent';
window.setTimeout(() => {
sendBackButton.innerHTML = '<span aria-hidden="true">♡</span> Send another hug back';
}, 2600);
}
openButton.addEventListener('click', beginExperience);
replayButton.addEventListener('click', replayHug);
sendBackButton.addEventListener('click', sendHugBack);
letterButton.addEventListener('click', openLetter);
letterClose.addEventListener('click', closeLetter);
heartHold.addEventListener('pointerdown', startHolding);
heartHold.addEventListener('pointerup', stopHolding);
heartHold.addEventListener('pointercancel', stopHolding);
heartHold.addEventListener('pointerleave', stopHolding);
heartHold.addEventListener('keydown', startHolding);
heartHold.addEventListener('keyup', stopHolding);
heartHold.addEventListener('blur', stopHolding);
window.addEventListener('contextmenu', (event) => {
if (event.target === heartHold) event.preventDefault();
});
|