mirror of
https://github.com/Extroonie/extroonie.com.git
synced 2026-10-12 17:48:06 -07:00
88 lines
2.8 KiB
JavaScript
88 lines
2.8 KiB
JavaScript
(() => {
|
|
const root = document.documentElement;
|
|
const toggle = document.querySelector('.theme-toggle');
|
|
const system = matchMedia('(prefers-color-scheme: dark)');
|
|
let manual = ['light', 'dark'].includes(root.dataset.themeChoice);
|
|
function setTheme(theme) {
|
|
root.dataset.theme = theme;
|
|
toggle?.setAttribute('aria-checked', String(theme === 'dark'));
|
|
}
|
|
if (toggle) {
|
|
toggle.hidden = false;
|
|
setTheme(root.dataset.theme || (system.matches ? 'dark' : 'light'));
|
|
toggle.addEventListener('click', () => {
|
|
const theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
manual = true;
|
|
root.dataset.themeChoice = theme;
|
|
setTheme(theme);
|
|
try {
|
|
localStorage.setItem('theme', theme);
|
|
} catch {}
|
|
});
|
|
system.addEventListener('change', () => {
|
|
if (!manual) setTheme(system.matches ? 'dark' : 'light');
|
|
});
|
|
window.addEventListener('storage', (event) => {
|
|
if (event.key !== 'theme') return;
|
|
manual = ['light', 'dark'].includes(event.newValue);
|
|
root.dataset.themeChoice = manual ? event.newValue : 'system';
|
|
setTheme(manual ? event.newValue : system.matches ? 'dark' : 'light');
|
|
});
|
|
}
|
|
const biography = document.querySelector('#about');
|
|
function fragmentTarget(hash = location.hash) {
|
|
let id;
|
|
try {
|
|
id = decodeURIComponent(hash.slice(1));
|
|
} catch {
|
|
return;
|
|
}
|
|
return id ? document.getElementById(id) : null;
|
|
}
|
|
function revealFragment() {
|
|
const target = fragmentTarget();
|
|
if (!target) return;
|
|
if (biography?.contains(target)) biography.open = true;
|
|
requestAnimationFrame(() => {
|
|
target.scrollIntoView({ block: 'start' });
|
|
target.focus({ preventScroll: true });
|
|
});
|
|
}
|
|
window.addEventListener('hashchange', revealFragment);
|
|
if (location.hash) revealFragment();
|
|
// Keep native fragment navigation: browser history, URL, and no-JS behavior.
|
|
document.addEventListener('click', (event) => {
|
|
if (
|
|
event.defaultPrevented ||
|
|
event.button !== 0 ||
|
|
event.metaKey ||
|
|
event.ctrlKey ||
|
|
event.shiftKey ||
|
|
event.altKey
|
|
)
|
|
return;
|
|
const link = event.target.closest('a[href^="#"]');
|
|
if (!link) return;
|
|
const target = fragmentTarget(link.hash);
|
|
if (target && biography?.contains(target)) biography.open = true;
|
|
});
|
|
const notice = document.querySelector('.notice');
|
|
let timer;
|
|
document.querySelectorAll('[data-copy]').forEach((button) => {
|
|
const fallback = button.parentElement.querySelector('.discord-fallback');
|
|
button.hidden = false;
|
|
fallback.hidden = true;
|
|
button.addEventListener('click', async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(button.dataset.copy);
|
|
notice.textContent = 'Discord username copied.';
|
|
} catch {
|
|
fallback.hidden = false;
|
|
notice.textContent = 'Select the username to copy it.';
|
|
}
|
|
clearTimeout(timer);
|
|
notice.classList.add('visible');
|
|
timer = setTimeout(() => notice.classList.remove('visible'), 3000);
|
|
});
|
|
});
|
|
})();
|