.taskItem { background: #f2f2f2; margin: 5px; padding: 10px; border-radius: 5px; }
---------------------------------------------------------
<!-- ❄ Snow effect: classic flakes + NUKE every click + controls -->
<style>
/* Snow overlay canvas – fixed on top, no layout impact, clicks pass through */
#snow-canvas {
position: fixed;
inset: 0; /* top:0; right:0; bottom:0; left:0 */
width: 100%;
height: 100%;
pointer-events: none; /* so your site remains fully clickable */
z-index: 9999; /* visually above everything */
}
/* --- Controls (upper right) --- */
#snow-controls-toggle {
position: fixed;
right: 16px;
top: 16px;
z-index: 10002;
width: 40px;
height: 40px;
border-radius: 999px;
border: 1px solid rgba(255,255,255,0.65);
background: rgba(0,0,0,0.4);
color: #fff;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
backdrop-filter: blur(3px);
}
#snow-controls-toggle:hover {
background: rgba(0,0,0,0.6);
}
#snow-controls {
position: fixed;
right: 16px;
top: 16px;
z-index: 10001;
width: 260px;
padding: 12px;
border-radius: 12px;
background: rgba(0,0,0,0.45);
color: #fff;
font: 14px/1.3 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
border: 1px solid rgba(255,255,255,0.6);
backdrop-filter: blur(4px);
box-sizing: border-box;
opacity: 0;
transform: translateY(-8px) scale(0.98);
pointer-events: none;
transition: opacity 0.18s ease, transform 0.18s ease;
}
#snow-controls.open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
#snow-controls .row {
margin-bottom: 8px;
}
#snow-controls label {
display: flex;
justify-content: space-between;
gap: 8px;
font-size: 13px;
margin-bottom: 4px;
}
#snow-controls input[type="range"] {
width: 100%;
}
#snow-toggle-visibility {
width: 100%;
margin-bottom: 6px;
padding: 7px 10px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.7);
background: rgba(255,255,255,0.12);
color: #fff;
cursor: pointer;
}
#snow-toggle-visibility:hover {
background: rgba(255,255,255,0.22);
}
#snow-tip {
font-size: 11px;
opacity: 0.85;
margin-top: 4px;
text-align: center;
}
@media (max-width: 560px) {
#snow-controls {
width: calc(100vw - 32px);
right: 16px;
}
}
</style>
<!-- Controls + Canvas -->
<button id="snow-controls-toggle"
aria-expanded="false"
aria-controls="snow-controls"
title="Show snow controls">❄</button>
<div id="snow-controls" aria-hidden="true">
<div class="row">
<button id="snow-toggle-visibility">Hide Snow</button>
</div>
<div class="row">
<label for="snow-count">
<span>Snow amount</span>
<span id="snow-count-label">450</span>
</label>
<input id="snow-count" type="range" min="50" max="800" value="450" />
</div>
<div id="snow-tip">
Click anywhere to NUKE the snow
</div>
</div>
<canvas id="snow-canvas"></canvas>
<script>
(function () {
const canvas = document.getElementById("snow-canvas");
const ctx = canvas.getContext("2d");
// Controls
const controlsToggle = document.getElementById("snow-controls-toggle");
const controlsPanel = document.getElementById("snow-controls");
const toggleSnowBtn = document.getElementById("snow-toggle-visibility");
const snowCountInput = document.getElementById("snow-count");
const snowCountLabel = document.getElementById("snow-count-label");
let width, height;
let docHeight = 0;
let flakes = [];
let animStarted = false;
let snowEnabled = true;
// Snow counts (default 450)
let targetFlakeCount = parseInt(snowCountInput.value, 10) || 450;
// NUKE only (no small burst)
const NUKE_RADIUS = 450;
const NUKE_STRENGTH = 14; // overall push strength
const RING_DURATION = 650; // ms
const rings = []; // shockwave visuals: {x,y,start}
function updateDocHeight() {
const d = document.documentElement;
const b = document.body;
docHeight = Math.max(
d.scrollHeight, d.offsetHeight, d.clientHeight,
b ? b.scrollHeight : 0,
b ? b.offsetHeight : 0
);
}
function resizeCanvas() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
updateDocHeight();
}
window.addEventListener("resize", resizeCanvas);
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function createFlake(initialYRandom = true) {
const scrollY = window.scrollY || document.documentElement.scrollTop || 0;
return {
x: Math.random() * width,
// Y in page-space (full document), not just viewport
y: initialYRandom
? Math.random() * docHeight // spread everywhere
: scrollY - rand(20, height), // respawn above view
radius: rand(3, 7), // base size (arms extend from this)
speedY: rand(0.4, 1.1),
speedX: rand(-0.3, 0.3),
opacity: rand(0.4, 0.95),
swing: Math.random() * Math.PI * 2,
swingSpeed: rand(0.001, 0.003),
type: Math.floor(Math.random() * 3), // three classic-ish variants
rotation: Math.random() * Math.PI * 2,
rotationSpeed: rand(-0.0015, 0.0015),
vx: 0, // velocity from NUKE
vy: 0
};
}
function initFlakes() {
flakes = [];
for (let i = 0; i < targetFlakeCount; i++) {
flakes.push(createFlake(true));
}
snowCountLabel.textContent = String(targetFlakeCount);
}
function syncFlakeCount() {
const current = flakes.length;
if (targetFlakeCount > current) {
const toAdd = targetFlakeCount - current;
for (let i = 0; i < toAdd; i++) {
flakes.push(createFlake(true));
}
} else if (targetFlakeCount < current) {
flakes.splice(targetFlakeCount);
}
snowCountLabel.textContent = String(targetFlakeCount);
}
function drawClassicFlake(flake, scrollY) {
ctx.save();
ctx.globalAlpha = flake.opacity;
const screenY = flake.y - scrollY; // convert world Y → screen Y
if (screenY < -50 || screenY > height + 50) {
ctx.restore();
return;
}
ctx.translate(flake.x, screenY);
ctx.rotate(flake.rotation);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 1;
const r = flake.radius;
const longArm = r * 1.6;
const midArm = r * 1.3;
ctx.beginPath();
if (flake.type === 0) {
// Simple six-armed crystal
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI) / 3;
const x = Math.cos(angle) * longArm;
const y = Math.sin(angle) * longArm;
ctx.moveTo(0, 0);
ctx.lineTo(x, y);
}
} else if (flake.type === 1) {
// Six-armed with side branches
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI) / 3;
const xMain = Math.cos(angle) * longArm;
const yMain = Math.sin(angle) * longArm;
ctx.moveTo(0, 0);
ctx.lineTo(xMain, yMain);
for (let t of [0.35, 0.7]) {
const baseX = Math.cos(angle) * longArm * t;
const baseY = Math.sin(angle) * longArm * t;
const branchAngle1 = angle + Math.PI / 6;
const branchAngle2 = angle - Math.PI / 6;
const branchLen = r * 0.6;
ctx.moveTo(baseX, baseY);
ctx.lineTo(
baseX + Math.cos(branchAngle1) * branchLen,
baseY + Math.sin(branchAngle1) * branchLen
);
ctx.moveTo(baseX, baseY);
ctx.lineTo(
baseX + Math.cos(branchAngle2) * branchLen,
baseY + Math.sin(branchAngle2) * branchLen
);
}
}
} else {
// Starry flake with 12 arms
for (let i = 0; i < 12; i++) {
const angle = (i * Math.PI) / 6;
const isLong = i % 2 === 0;
const len = isLong ? longArm : midArm;
const x = Math.cos(angle) * len;
const y = Math.sin(angle) * len;
ctx.moveTo(0, 0);
ctx.lineTo(x, y);
}
}
ctx.stroke();
// Little bright core
ctx.beginPath();
ctx.arc(0, 0, r * 0.3, 0, Math.PI * 2);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.restore();
}
function updateFlake(flake) {
flake.swing += flake.swingSpeed;
// Page-space movement
flake.x += flake.speedX + Math.cos(flake.swing) * 0.4 + flake.vx;
flake.y += flake.speedY + flake.vy;
flake.rotation += flake.rotationSpeed;
// Dampen NUKE velocity + a little extra gravity feel
flake.vx *= 0.96;
flake.vy = flake.vy * 0.96 + 0.01;
// Horizontal wrap
if (flake.x < -30) flake.x = width + 30;
else if (flake.x > width + 30) flake.x = -30;
// Respawn above page when below full document
if (flake.y > docHeight + 30) {
const nf = createFlake(false);
Object.assign(flake, nf);
}
}
function drawRings(now, scrollY) {
for (let i = rings.length - 1; i >= 0; i--) {
const ring = rings[i];
const age = now - ring.start;
if (age > RING_DURATION) {
rings.splice(i, 1);
continue;
}
const t = age / RING_DURATION;
const radius = 40 + t * (NUKE_RADIUS * 0.9);
const alpha = 0.35 * (1 - t);
ctx.save();
ctx.globalAlpha = alpha;
ctx.beginPath();
ctx.lineWidth = 2 + 6 * (1 - t);
ctx.strokeStyle = "#ffffff";
ctx.arc(ring.x, ring.y - scrollY, radius, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
}
function render(now) {
const scrollY = window.scrollY || document.documentElement.scrollTop || 0;
ctx.clearRect(0, 0, width, height);
if (snowEnabled) {
drawRings(now, scrollY);
for (const flake of flakes) {
updateFlake(flake);
drawClassicFlake(flake, scrollY);
}
}
requestAnimationFrame(render);
}
function initSnow() {
resizeCanvas();
targetFlakeCount = parseInt(snowCountInput.value, 10) || 450;
initFlakes();
// Controls initial label
snowCountLabel.textContent = String(targetFlakeCount);
if (!animStarted) {
animStarted = true;
requestAnimationFrame(render);
}
}
// --- Controls behaviour ---
function setControlsOpen(open) {
controlsPanel.classList.toggle("open", open);
controlsToggle.setAttribute("aria-expanded", open ? "true" : "false");
controlsPanel.setAttribute("aria-hidden", open ? "false" : "true");
controlsToggle.title = open ? "Hide snow controls" : "Show snow controls";
}
controlsToggle.addEventListener("click", () => {
const open = !controlsPanel.classList.contains("open");
setControlsOpen(open);
});
toggleSnowBtn.addEventListener("click", () => {
snowEnabled = !snowEnabled;
toggleSnowBtn.textContent = snowEnabled ? "Hide Snow" : "Show Snow";
if (!snowEnabled) {
ctx.clearRect(0, 0, width, height);
}
});
snowCountInput.addEventListener("input", (e) => {
targetFlakeCount = parseInt(e.target.value, 10) || 450;
syncFlakeCount();
});
// --- NUKE on every page click (except when clicking the controls) ---
window.addEventListener("click", (event) => {
if (!snowEnabled) return;
// Do not trigger when interacting with the controls UI
if (event.target.closest("#snow-controls, #snow-controls-toggle")) return;
const scrollY = window.scrollY || document.documentElement.scrollTop || 0;
const scrollX = window.scrollX || document.documentElement.scrollLeft || 0;
const clickX = scrollX + event.clientX;
const clickY = scrollY + event.clientY;
// Add shockwave ring visual
rings.push({ x: clickX, y: clickY, start: performance.now() });
// NUKE blast: push all nearby flakes strongly outward
for (const flake of flakes) {
const dx = flake.x - clickX;
const dy = flake.y - clickY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < NUKE_RADIUS && dist > 0.0001) {
const falloff = (NUKE_RADIUS - dist) / NUKE_RADIUS; // 0..1
const power = NUKE_STRENGTH * falloff;
const nx = dx / dist;
const ny = dy / dist;
flake.vx += nx * power;
flake.vy += ny * power * 0.7 - 2 * falloff; // slight upward lift
}
}
});
// Start after DOM is ready / page is shown
if (document.readyState === "complete" || document.readyState === "interactive") {
initSnow();
} else {
window.addEventListener("DOMContentLoaded", initSnow, { once: true });
}
window.addEventListener("pageshow", initSnow);
})();
</script>
<!-- ❄ end snow effect block -->