This repository has no description
1.9 kB
60 lines
1const styles = `
2 [data-punchcard] {
3 .bg-green-100, .dark\\:bg-green-100 { --w-size: 20%; }
4 .bg-green-200, .dark\\:bg-green-200 { --w-size: 30%; }
5 .bg-green-300, .dark\\:bg-green-300 { --w-size: 40%; }
6 .bg-green-400, .dark\\:bg-green-400 { --w-size: 50%; }
7 .bg-green-500, .dark\\:bg-green-500 { --w-size: 60%; }
8 .bg-green-600, .dark\\:bg-green-600 { --w-size: 70%; }
9 .bg-green-700, .dark\\:bg-green-700 { --w-size: 80%; }
10 .bg-green-800, .dark\\:bg-green-800 { --w-size: 90%; }
11 .bg-green-900, .dark\\:bg-green-900 { --w-size: 100%; }
12
13 > div > div {
14 background-color: color-mix(in srgb, #2160ec var(--w-size), light-dark(white, black));
15 transition: transform 0.2s ease-out;
16 will-change: transform;
17 }
18 }
19`;
20
21const sheet = new CSSStyleSheet();
22sheet.replaceSync(styles);
23document.adoptedStyleSheets = [sheet];
24
25const RADIUS = 60;
26const MAX_SCALE = 2.5;
27
28const grid = document.querySelector('[data-punchcard]');
29const dots = Array.from(grid.querySelectorAll(':scope > div > div'));
30
31function mousemove(e) {
32 for (const dot of dots) {
33 const rect = dot.getBoundingClientRect();
34 const cx = rect.left + rect.width / 2;
35 const cy = rect.top + rect.height / 2;
36 const dist = Math.hypot(e.clientX - cx, e.clientY - cy);
37 const scale = 1 + (MAX_SCALE - 1) * Math.max(0, 1 - dist / RADIUS);
38 dot.style.transform = `scale(${scale})`;
39 }
40}
41
42function mouseleave() {
43 for (const dot of dots) {
44 dot.style.transform = 'scale(1)';
45 }
46}
47
48function toggleAnimation(enabled) {
49 if (enabled) {
50 grid.addEventListener('mousemove', mousemove);
51 grid.addEventListener('mouseleave', mouseleave);
52 } else {
53 grid.removeEventListener('mousemove', mousemove);
54 grid.removeEventListener('mouseleave', mouseleave);
55 }
56}
57
58const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
59toggleAnimation(!motion.matches);
60motion.addEventListener('change', (event) => toggleAnimation(!event.matches));