Add a colour wash animation
The frame parks in the middle and the colour changes instead: a gradient creeps round the colour wheel behind the poster, and the frame's glow travels with it. Every other animation moves the frame, so the still one reads as a rest between them rather than as a dead spot. The wash layer blends with mix-blend-mode: color, which keeps the poster's luminance and replaces only its hue. soft-light was the first try and barely shifted the colour at all against a poster already knocked back to 45% brightness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tg15tLAuT6NrCE1ZxKVgDB
This commit is contained in:
@@ -19,6 +19,9 @@ import {
|
||||
TRAIL_SPACING,
|
||||
TRAIL_LENGTH,
|
||||
TRAIL_FADE_SECONDS,
|
||||
WASH_CYCLE_SECONDS,
|
||||
WASH_SPREAD,
|
||||
WASH_STRENGTH,
|
||||
AUTO_ANIMATION,
|
||||
} from '/config.js';
|
||||
|
||||
@@ -903,6 +906,78 @@ export function startDragResize({ element }) {
|
||||
};
|
||||
}
|
||||
|
||||
// --- colour wash --------------------------------------------------------
|
||||
|
||||
// The frame parks in the middle and the room changes colour around it: a wash
|
||||
// creeps round the colour wheel behind the poster, and the frame's glow travels
|
||||
// with it, so the whole screen shifts as one. It is the quiet one — everything
|
||||
// else in the registry moves the frame, and after a few of those a still frame
|
||||
// with a slow colour behind it is a relief rather than a dead spot.
|
||||
|
||||
/**
|
||||
* The two ends of the wash gradient at `elapsed` seconds, as hues in degrees.
|
||||
* One trip round the wheel takes WASH_CYCLE_SECONDS, and the second hue trails
|
||||
* the first by WASH_SPREAD so the gradient always has somewhere to go. Pure, so
|
||||
* the cycle can be exercised without a browser.
|
||||
*/
|
||||
export function washHues(elapsed) {
|
||||
const turn = ((elapsed / WASH_CYCLE_SECONDS) * 360) % 360;
|
||||
return [turn, (turn + WASH_SPREAD) % 360];
|
||||
}
|
||||
|
||||
/** A wash stop as a colour: saturated and mid-bright, before the blend mode. */
|
||||
function washStop(hue, alpha = 1) {
|
||||
return `hsl(${hue.toFixed(1)} 85% 55% / ${alpha})`;
|
||||
}
|
||||
|
||||
// The frame's glow is the wash colour held back to the same weight the fixed
|
||||
// pink had, so a hue with more punch to it does not brighten the whole frame.
|
||||
const GLOW_ALPHA = 0.55;
|
||||
|
||||
export function startWash({ element }) {
|
||||
const wash = document.createElement('div');
|
||||
wash.id = 'wash';
|
||||
wash.style.opacity = String(WASH_STRENGTH);
|
||||
// Behind the stills and the frame, in front of the poster it tints.
|
||||
document.body.appendChild(wash);
|
||||
element.classList.add('washing');
|
||||
|
||||
function centre() {
|
||||
const size = applyFrameSize(element);
|
||||
place(element, (window.innerWidth - size.width) / 2, (window.innerHeight - size.height) / 2);
|
||||
}
|
||||
|
||||
centre();
|
||||
window.addEventListener('resize', centre);
|
||||
|
||||
const started = performance.now();
|
||||
let handle = 0;
|
||||
let stopped = false;
|
||||
|
||||
function frame(now) {
|
||||
if (stopped) return;
|
||||
|
||||
const [from, to] = washHues((now - started) / 1000);
|
||||
wash.style.background = `linear-gradient(135deg, ${washStop(from)}, ${washStop(to)})`;
|
||||
// The frame's glow rides the leading hue, so it belongs to the wash instead
|
||||
// of sitting on top of it in a fixed pink.
|
||||
element.style.setProperty('--wash', washStop(from, GLOW_ALPHA));
|
||||
|
||||
handle = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
handle = requestAnimationFrame(frame);
|
||||
|
||||
return function stop() {
|
||||
stopped = true;
|
||||
cancelAnimationFrame(handle);
|
||||
window.removeEventListener('resize', centre);
|
||||
wash.remove();
|
||||
element.classList.remove('washing');
|
||||
element.style.removeProperty('--wash');
|
||||
};
|
||||
}
|
||||
|
||||
// --- registry -----------------------------------------------------------
|
||||
|
||||
// Keyed by the names in config.js, which is what travels over the socket.
|
||||
@@ -911,6 +986,7 @@ export const ANIMATIONS = {
|
||||
corners: startCorners,
|
||||
drag: startDragResize,
|
||||
trail: startTrail,
|
||||
wash: startWash,
|
||||
};
|
||||
|
||||
const NAMES = Object.keys(ANIMATIONS);
|
||||
|
||||
Reference in New Issue
Block a user