188 lines
7.3 KiB
JavaScript
188 lines
7.3 KiB
JavaScript
// Generated, do not edit. sha256:8ea45396bfd27f4dbf4c416aa596846fa428d7394799796a19ce526c30b7ada7
|
|
import { CONFIG } from '../../shared/config.js';
|
|
|
|
const BLOBS = Object.freeze([
|
|
Object.freeze({ cls: 'blob-a', x: [0, 42], y: [0, 26], firstMs: 4000 }),
|
|
Object.freeze({ cls: 'blob-b', x: [-52, 0], y: [0, 32], firstMs: 14000 }),
|
|
Object.freeze({ cls: 'blob-c', x: [-38, 38], y: [-38, -4], firstMs: 26000, sink: 0.3 }),
|
|
]);
|
|
const SINK_VH = 80;
|
|
|
|
export const BAND_LAYERS = Object.freeze([
|
|
Object.freeze({ w: 1400, h: 1800, count: 4, len: [700, 1300], width: [40, 70], alpha: [0.2, 0.4], blur: 12, dur: 21, opacity: 0.75, tilt: 10, seed: 19 }),
|
|
Object.freeze({ w: 1800, h: 2100, count: 3, len: [900, 1600], width: [110, 180], alpha: [0.12, 0.26], blur: 28, dur: 17, opacity: 0.9, tilt: 12, seed: 29 }),
|
|
Object.freeze({ w: 2200, h: 2400, count: 2, len: [1200, 1900], width: [300, 450], alpha: [0.08, 0.16], blur: 64, dur: 14, opacity: 1, tilt: 14, seed: 43 }),
|
|
]);
|
|
const FADE_MS = 5200;
|
|
|
|
const between = (rnd, a, b) => a + rnd() * (b - a);
|
|
|
|
export function tempoOf(key) {
|
|
return CONFIG.GLOW_TEMPO[key] || CONFIG.GLOW_TEMPO.calm;
|
|
}
|
|
|
|
export function motionMode({ profile, glow, motion, reducedMotion = false, hidden = false } = {}) {
|
|
if (profile !== 'glass' || glow === 'off' || reducedMotion || hidden) return 'off';
|
|
return motion === 'step' || motion === 'always' ? motion : 'off';
|
|
}
|
|
|
|
export function nextDelayMs(tempo, rnd = Math.random) {
|
|
return Math.round((tempo.MOVE_S + between(rnd, tempo.PAUSE_MIN_S, tempo.PAUSE_MAX_S)) * 1000);
|
|
}
|
|
|
|
export function driftTransform(area, rnd = Math.random) {
|
|
const sinks = area.sink ? rnd() < area.sink : false;
|
|
const x = between(rnd, area.x[0], area.x[1]).toFixed(1);
|
|
const y = sinks ? SINK_VH.toFixed(1) : between(rnd, area.y[0], area.y[1]).toFixed(1);
|
|
const turn = between(rnd, -16, 16).toFixed(1);
|
|
const sx = between(rnd, 0.85, 1.2).toFixed(3);
|
|
const sy = between(rnd, 0.85, 1.2).toFixed(3);
|
|
return `translate(${x}vw,${y}vh) rotate(${turn}deg) scale(${sx},${sy})`;
|
|
}
|
|
|
|
export function nextCore(cores, i, rnd = Math.random, chance = CONFIG.GLOW_CORE_CHANCE) {
|
|
if (cores[i]) return false;
|
|
if (cores.some((c, n) => c && n !== i)) return false;
|
|
return rnd() < chance;
|
|
}
|
|
|
|
export function showerMs(tempo, on, rnd = Math.random) {
|
|
return Math.round((on ? tempo.MOVE_S * 2 : between(rnd, tempo.PAUSE_MIN_S, tempo.PAUSE_MAX_S)) * 1000);
|
|
}
|
|
|
|
function seeded(a) {
|
|
return () => {
|
|
a |= 0; a = a + 0x6D2B79F5 | 0;
|
|
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
};
|
|
}
|
|
|
|
export function bandTile({ w, h, count, len, width, alpha, blur, seed }) {
|
|
const rnd = seeded(seed);
|
|
const halo = blur * 3, pad = 2 + halo;
|
|
let shapes = '';
|
|
for (let i = 0; i < count; i++) {
|
|
const sw = between(rnd, width[0], width[1]);
|
|
const x = between(rnd, pad, w - pad - sw), y = between(rnd, 0, h);
|
|
const L = between(rnd, len[0], len[1]), a = between(rnd, alpha[0], alpha[1]);
|
|
for (const dy of [0, -h, h]) {
|
|
if (dy < 0 && y + L + halo <= h) continue;
|
|
if (dy > 0 && y - halo >= 0) continue;
|
|
shapes += `<rect x="${x.toFixed(1)}" y="${(y + dy).toFixed(1)}" width="${sw.toFixed(1)}" height="${L.toFixed(1)}"`
|
|
+ ` rx="${(sw / 2).toFixed(1)}" fill="url(#g)" opacity="${a.toFixed(2)}"/>`;
|
|
}
|
|
}
|
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"><defs>`
|
|
+ '<linearGradient id="g" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff" stop-opacity="0"/>'
|
|
+ '<stop offset="0.35" stop-color="#fff"/><stop offset="0.65" stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient>'
|
|
+ `<filter id="f" filterUnits="userSpaceOnUse" x="0" y="0" width="${w}" height="${h}"><feGaussianBlur stdDeviation="${blur}"/></filter>`
|
|
+ `</defs><g filter="url(#f)">${shapes}</g></svg>`;
|
|
return `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`;
|
|
}
|
|
|
|
const timers = [];
|
|
let shapeTimer = null;
|
|
let showerTimer = null;
|
|
let fadeTimer = null;
|
|
let bandsDrawn = false;
|
|
let wired = false;
|
|
let last = {};
|
|
|
|
function drawBands(box) {
|
|
if (bandsDrawn) return;
|
|
bandsDrawn = true;
|
|
box.querySelectorAll('.band').forEach((el, i) => {
|
|
const spec = BAND_LAYERS[i];
|
|
if (!spec) return;
|
|
el.style.maskImage = el.style.webkitMaskImage = bandTile(spec);
|
|
el.style.maskSize = el.style.webkitMaskSize = `${spec.w}px ${spec.h}px`;
|
|
el.style.setProperty('--tile-h', `${spec.h}px`);
|
|
el.style.setProperty('--band-dur', `${spec.dur}s`);
|
|
el.style.setProperty('--tilt', `${spec.tilt}deg`);
|
|
el.style.opacity = String(spec.opacity);
|
|
});
|
|
}
|
|
|
|
function shower(box, tempo, on) {
|
|
clearTimeout(showerTimer); clearTimeout(fadeTimer);
|
|
box.classList.add('is-falling');
|
|
box.classList.toggle('is-shown', on);
|
|
if (!on) fadeTimer = setTimeout(() => box.classList.remove('is-falling'), FADE_MS);
|
|
showerTimer = setTimeout(() => shower(box, tempo, !on), showerMs(tempo, on));
|
|
}
|
|
|
|
function applyBands(box, bands, mode, tempo) {
|
|
clearTimeout(showerTimer); clearTimeout(fadeTimer);
|
|
box.classList.remove('is-falling');
|
|
if (bands) drawBands(box);
|
|
box.classList.toggle('is-shown', bands);
|
|
if (!bands) return;
|
|
if (mode === 'always') box.classList.add('is-falling');
|
|
else if (mode === 'step') shower(box, tempo, true);
|
|
}
|
|
|
|
function elements() {
|
|
return BLOBS.map(b => document.querySelector(`.aurora .${b.cls}`));
|
|
}
|
|
|
|
function coresOf(els) { return els.map(el => !!el?.classList.contains('is-core')); }
|
|
|
|
function reshape(els, i) {
|
|
const el = els[i];
|
|
if (el) el.classList.toggle('is-core', nextCore(coresOf(els), i));
|
|
}
|
|
|
|
function stopAll(els, keepTransform) {
|
|
timers.forEach(t => clearTimeout(t));
|
|
timers.length = 0;
|
|
clearTimeout(shapeTimer);
|
|
for (const el of els) {
|
|
if (!el) continue;
|
|
el.classList.remove('is-core');
|
|
if (!keepTransform) el.style.transform = '';
|
|
}
|
|
}
|
|
|
|
export function applyAurora(settings = last) {
|
|
last = settings;
|
|
const root = document.documentElement;
|
|
const els = elements();
|
|
if (!els.some(Boolean)) return;
|
|
const tempo = tempoOf(settings.glow_tempo);
|
|
root.style.setProperty('--drift-dur', `${tempo.MOVE_S}s`);
|
|
root.style.setProperty('--drift-mult', String(tempo.ALWAYS_MULT));
|
|
const mode = motionMode({
|
|
profile: settings.profile, glow: settings.glow, motion: settings.motion,
|
|
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
hidden: document.hidden,
|
|
});
|
|
const bands = settings.glowshape === 'bands';
|
|
const box = document.querySelector('.aurora .bands');
|
|
if (box) applyBands(box, bands, mode, tempo);
|
|
const blobMode = bands ? 'off' : mode;
|
|
stopAll(els, blobMode === 'step');
|
|
if (blobMode === 'step') {
|
|
BLOBS.forEach((b, i) => {
|
|
const tick = () => {
|
|
if (els[i]) els[i].style.transform = driftTransform(b);
|
|
reshape(els, i);
|
|
timers[i] = setTimeout(tick, nextDelayMs(tempo));
|
|
};
|
|
timers[i] = setTimeout(tick, b.firstMs);
|
|
});
|
|
} else if (blobMode === 'always') {
|
|
const tick = () => {
|
|
reshape(els, Math.floor(Math.random() * els.length));
|
|
shapeTimer = setTimeout(tick, nextDelayMs(tempo) / els.length);
|
|
};
|
|
shapeTimer = setTimeout(tick, 20000);
|
|
}
|
|
if (!wired) {
|
|
wired = true;
|
|
document.addEventListener('visibilitychange', () => applyAurora());
|
|
matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', () => applyAurora());
|
|
}
|
|
}
|