-
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy paththeme.js
More file actions
131 lines (115 loc) · 4.31 KB
/
Copy paththeme.js
File metadata and controls
131 lines (115 loc) · 4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* theme.js
* Theme init + parallax (mousemove + scroll) with reduced-motion support
*/
/* Tailwind theme (brand colours, Inter, soft shadow) now lives in tailwind.config.js
and is compiled into /dist/tailwind.css by `npm run build:css`. */
/* -----------------------------
Theme: dark mode bootstrap
-------------------------------- */
(() => {
try {
const stored = localStorage.getItem('theme');
const systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = stored || (systemDark ? 'dark' : 'light');
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} catch {
// no-op if storage blocked
}
})();
/* -----------------------------
DOM-ready helper
-------------------------------- */
function onReady(fn) {
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn);
else fn();
}
/* -----------------------------
Reduced motion detection
-------------------------------- */
const prefersReducedMotion = window.matchMedia
? window.matchMedia('(prefers-reduced-motion: reduce)')
: { matches: false, addEventListener: () => {} };
/* Utility to pause CSS gradient animations when reduced motion.
The animation lives on .animated-gradient::before, which inline styles
cannot reach, so toggle a class the stylesheet keys off instead. */
function updateAnimatedGradients() {
const paused = prefersReducedMotion.matches;
document.querySelectorAll('.animated-gradient').forEach(el => {
el.classList.toggle('motion-paused', paused);
});
}
prefersReducedMotion.addEventListener?.('change', updateAnimatedGradients);
onReady(updateAnimatedGradients);
/* -----------------------------
Parallax: mousemove (desktop)
- Uses requestAnimationFrame to throttle
- GPU-friendly translate3d
- Controlled by [data-parallax] (number)
- Layers are collected once; promotion comes from .parallax-will-change
in the stylesheet rather than a style write on every frame
-------------------------------- */
(() => {
if (prefersReducedMotion.matches) return;
// only run on devices that have a mouse/touchpad pointer
if (!(window.matchMedia && window.matchMedia('(pointer: fine)').matches)) return;
let layers = [];
let rafId = null;
let lastCx = 0, lastCy = 0;
function onMouseMove(e) {
lastCx = e.clientX / window.innerWidth - 0.5; // -0.5..0.5
lastCy = e.clientY / window.innerHeight - 0.5; // -0.5..0.5
if (rafId === null) rafId = requestAnimationFrame(applyMouseParallax);
}
function applyMouseParallax() {
rafId = null;
for (const { el, speed } of layers) {
// tune multiplier for subtle motion
el.style.transform = `translate3d(${-lastCx * speed * 10}px, ${-lastCy * speed * 10}px, 0)`;
}
}
onReady(() => {
layers = Array.from(document.querySelectorAll('[data-parallax]'), el => ({
el,
speed: parseFloat(el.getAttribute('data-parallax')) || 1
}));
if (layers.length) window.addEventListener('mousemove', onMouseMove, { passive: true });
});
})();
/* -----------------------------
Parallax: scroll (mobile/touch fallback)
- Elements with [data-parallax-scroll]
- speed attribute: number (e.g., 2, 4, 8)
- Effect: translateY based on scroll position
-------------------------------- */
(() => {
if (prefersReducedMotion.matches) return;
let layers = [];
let ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(applyScrollParallax);
}
function applyScrollParallax() {
ticking = false;
const scrollY = window.scrollY || window.pageYOffset || 0;
for (const { el, speed } of layers) {
// smaller multiplier keeps it subtle; tweak to taste
el.style.transform = `translate3d(0, ${scrollY * speed * 0.05}px, 0)`;
}
}
onReady(() => {
layers = Array.from(document.querySelectorAll('[data-parallax-scroll]'), el => ({
el,
speed: parseFloat(el.getAttribute('data-parallax-scroll')) || 1
}));
if (!layers.length) return; // skip if not used
for (const { el } of layers) el.style.willChange = 'transform';
window.addEventListener('scroll', onScroll, { passive: true });
applyScrollParallax(); // initial position
});
})();