live-20 / script.js
Samfredoly's picture
Deploy Pookie virtual hug Gradio app
759fd36 verified
Raw History Blame Contribute Delete
5.36 kB
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();
});