mirror of
https://github.com/by-jp/www.byjp.me.git
synced 2025-08-09 22:16:07 +01:00
Because of a missing variable, and combining numbers as strings instead of integers, the number of claps was displaying extremely incorrectly! This is now fixed.
214 lines
No EOL
6.1 KiB
JavaScript
214 lines
No EOL
6.1 KiB
JavaScript
/* Accent colour */
|
|
|
|
function updateAccentColor() {
|
|
const now = new Date()
|
|
let start = new Date(now)
|
|
const hue = (now - start.setHours(0,0,0,0)) / 240000
|
|
document.documentElement.style.setProperty('--accent', `lch(60% 120 ${hue})`);
|
|
document.documentElement.style.setProperty('--accentAlt', `lch(60% 120 ${(hue + 120) % 360})`);
|
|
document.documentElement.style.setProperty('--accentHue', hue);
|
|
}
|
|
setInterval(updateAccentColor, 15000)
|
|
updateAccentColor()
|
|
|
|
/* Clap counter */
|
|
|
|
const performClap = (e) => {
|
|
e.preventDefault();
|
|
const btn = e.currentTarget;
|
|
btn.disabled = true;
|
|
fetch(btn.parentElement.action, {
|
|
method: 'POST',
|
|
headers: new Headers({ 'Accept': 'application/json' }),
|
|
signal: AbortSignal.timeout(2000),
|
|
}).then(res => res.json().then((data) => ([res, data])))
|
|
.then(([res, data]) => {
|
|
if (res.status !== 200) {
|
|
const errorDesc = data.error || JSON.stringify(data)
|
|
throw new Error(`Received HTTP ${res.status} from clap counter: ${errorDesc}`)
|
|
}
|
|
if (typeof data.claps !== 'number') {
|
|
throw new Error(`Clap count response isn't a number: ${JSON.stringify(data)}`)
|
|
}
|
|
return data;
|
|
})
|
|
.then(data => {
|
|
const clapCount = addInteractions(btn, data.claps);
|
|
localStorage.setItem(clapKey(btn.parentElement.action), clapCount);
|
|
forEveryClapButton((btn) => {
|
|
setClapCount(btn, clapCount);
|
|
}, btn.parentElement.action)
|
|
btn.parentElement.classList.add('clapped')
|
|
btn.disabled = false;
|
|
})
|
|
.catch(e => {
|
|
if (e.name === 'AbortError') {
|
|
e = new Error("Fetch exceeded timeout")
|
|
}
|
|
|
|
console.error(`Failed to automate the clap: ${e.message}`)
|
|
alert("Sorry friend! It looks like my clap-o-meter has broken on us.\n\n"
|
|
+ "Instead, you could reach out to me via the sites listed on my homepage — to share appreciation for this post, or to tell me about this break!\n\n"
|
|
+ "(And if you're technically inclined,the console will list the error's details!)")
|
|
})
|
|
}
|
|
|
|
const clapKey = (action) => `clap:${(new URL(action)).pathname}`;
|
|
|
|
const addInteractions = (btn, claps) => (claps + (Number(btn.dataset.interactions) || 0))
|
|
|
|
const setClapCount = (btn, clapCount) => {
|
|
if (clapCount === 0) {
|
|
return
|
|
}
|
|
|
|
const count = clapCountEl(btn);
|
|
if (count) {
|
|
count.textContent = Math.max(count.textContent, clapCount)
|
|
} else {
|
|
const newCount = document.createElement('span');
|
|
newCount.textContent = clapCount;
|
|
btn.appendChild(newCount);
|
|
}
|
|
}
|
|
|
|
const clapCountEl = (btn) => btn.querySelector('span')
|
|
|
|
const forEveryClapButton = (fn, sameAction = '') => {
|
|
for (const btn of document.querySelectorAll(`form.claps${sameAction ? `[action="${sameAction}"]` : ''} button`)) {
|
|
fn(btn)
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
let toCheck = []
|
|
forEveryClapButton((btn) => {
|
|
btn.addEventListener("click", performClap)
|
|
|
|
const action = btn.parentElement.action
|
|
const lastClappedTo = localStorage.getItem(clapKey(action));
|
|
if (lastClappedTo) {
|
|
btn.parentElement.classList.add('clapped')
|
|
setClapCount(btn, lastClappedTo)
|
|
}
|
|
toCheck.push([action, btn])
|
|
})
|
|
|
|
toCheck.forEach(([action, btn]) => {
|
|
fetch(action, { method: 'GET', headers: new Headers({ 'Accept': 'application/json' }) })
|
|
.then(res => {
|
|
if (res.status !== 200) {
|
|
throw new Error(`Got HTTP ${res.status} while trying to retrieve claps`)
|
|
}
|
|
return res.json()
|
|
})
|
|
.then(data => {
|
|
const clapCount = addInteractions(btn, data.claps);
|
|
forEveryClapButton((btn) => {
|
|
setClapCount(btn, clapCount);
|
|
}, action)
|
|
})
|
|
.catch(console.error)
|
|
})
|
|
})
|
|
|
|
/* drops */
|
|
|
|
const dropsContainer = document.getElementById('drops');
|
|
let drops = [];
|
|
const dropLength = 0.5; // s
|
|
const fps = 60;
|
|
const dropIncrement = 1/(fps*dropLength);
|
|
function createDrop(posX, posY) {
|
|
startDropCounter();
|
|
|
|
const el = document.createElement('div');
|
|
let size = 0;
|
|
|
|
el.style.setProperty('--dropSize', size);
|
|
el.style.setProperty('top', posY);
|
|
el.style.setProperty('left', posX);
|
|
drops.push({ el, size });
|
|
dropsContainer.appendChild(el);
|
|
playDroplet();
|
|
}
|
|
|
|
let dropCounterInterval;
|
|
function startDropCounter() {
|
|
if (dropCounterInterval) {
|
|
return;
|
|
}
|
|
dropCounterInterval = setInterval(updateDropCounter, 1000/fps);
|
|
}
|
|
|
|
function updateDropCounter() {
|
|
for (const [i, drop] of drops.entries()) {
|
|
if (drop.size > 1) {
|
|
dropsContainer.removeChild(drop.el);
|
|
drops.splice(i, 1);
|
|
continue;
|
|
}
|
|
|
|
drop.size += dropIncrement;
|
|
drop.el.style.setProperty('--dropSize', `${drop.size}`);
|
|
}
|
|
|
|
if (drops.length == 0) {
|
|
clearInterval(dropCounterInterval);
|
|
dropCounterInterval = false;
|
|
}
|
|
}
|
|
|
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
function playDroplet() {
|
|
const playLength = dropLength * 2 / 3;
|
|
const startFreq = 98 + gRand(12);
|
|
const endFreq = 220 + gRand(12);
|
|
|
|
const oscillator = audioContext.createOscillator();
|
|
oscillator.type = 'sine'
|
|
oscillator.frequency.setValueAtTime(startFreq, audioContext.currentTime);
|
|
oscillator.frequency.exponentialRampToValueAtTime(endFreq, audioContext.currentTime + playLength);
|
|
|
|
const gainNode = audioContext.createGain();
|
|
gainNode.gain.setValueAtTime(0.2, audioContext.currentTime);
|
|
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + playLength);
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioContext.destination);
|
|
|
|
oscillator.start();
|
|
oscillator.stop(audioContext.currentTime + playLength);
|
|
}
|
|
|
|
let raining = false;
|
|
document.body.addEventListener('click', (e) => {
|
|
raining = false;
|
|
switch(e.target.tagName) {
|
|
case 'A':
|
|
case 'BUTTON':
|
|
return true;
|
|
default:
|
|
}
|
|
|
|
createDrop(`${e.pageX}px`, `${e.pageY}px`);
|
|
return true;
|
|
});
|
|
|
|
function gRand(scale = 1) {
|
|
return scale * Math.sqrt(-2 * Math.log(1 - Math.random())) * Math.cos(2 * Math.PI * Math.random())
|
|
}
|
|
|
|
function doRain() {
|
|
if (!raining) {
|
|
return
|
|
}
|
|
|
|
createDrop(`${Math.random() * document.body.scrollWidth}px`, `${Math.random() * document.body.scrollHeight}px`);
|
|
setTimeout(doRain, 250+gRand(250));
|
|
}
|
|
|
|
function rain() {
|
|
raining = true;
|
|
doRain();
|
|
} |