118 lines
3.6 KiB
JavaScript
118 lines
3.6 KiB
JavaScript
const STORAGE_KEY = "bratskih-hub-preferences";
|
|
|
|
const defaultPreferences = {
|
|
theme: window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark",
|
|
view: "cards",
|
|
accent: "violet",
|
|
};
|
|
|
|
const preferences = {
|
|
...defaultPreferences,
|
|
...readPreferences(),
|
|
};
|
|
|
|
const services = document.querySelector(".services");
|
|
const themeButtons = document.querySelectorAll("[data-theme]");
|
|
const viewButtons = document.querySelectorAll("[data-view]");
|
|
const accentButtons = document.querySelectorAll("[data-accent]");
|
|
const cards = document.querySelectorAll(".service-card");
|
|
|
|
applyPreferences(preferences);
|
|
|
|
themeButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
preferences.theme = button.dataset.theme;
|
|
applyPreferences(preferences);
|
|
savePreferences(preferences);
|
|
});
|
|
});
|
|
|
|
viewButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
preferences.view = button.dataset.view;
|
|
applyPreferences(preferences);
|
|
savePreferences(preferences);
|
|
});
|
|
});
|
|
|
|
accentButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
preferences.accent = button.dataset.accent;
|
|
applyPreferences(preferences);
|
|
savePreferences(preferences);
|
|
});
|
|
});
|
|
|
|
cards.forEach((card) => {
|
|
let leaveTimer;
|
|
|
|
card.addEventListener("pointerenter", () => {
|
|
window.clearTimeout(leaveTimer);
|
|
card.classList.add("is-hovered");
|
|
});
|
|
|
|
card.addEventListener("pointermove", (event) => {
|
|
window.clearTimeout(leaveTimer);
|
|
card.classList.add("is-hovered");
|
|
|
|
const rect = card.getBoundingClientRect();
|
|
const x = event.clientX - rect.left;
|
|
const y = event.clientY - rect.top;
|
|
const xPercent = clamp((x / rect.width) * 100, 8, 92);
|
|
const yPercent = clamp((y / rect.height) * 100, 8, 92);
|
|
const rotateY = ((xPercent - 50) / 50) * 2.6;
|
|
const rotateX = ((50 - yPercent) / 50) * 2.6;
|
|
|
|
card.style.setProperty("--card-x", `${xPercent}%`);
|
|
card.style.setProperty("--card-y", `${yPercent}%`);
|
|
card.style.setProperty("--tilt-x", `${rotateX}deg`);
|
|
card.style.setProperty("--tilt-y", `${rotateY}deg`);
|
|
});
|
|
|
|
card.addEventListener("pointerleave", () => {
|
|
leaveTimer = window.setTimeout(() => {
|
|
card.classList.remove("is-hovered");
|
|
card.style.setProperty("--card-x", "50%");
|
|
card.style.setProperty("--card-y", "50%");
|
|
card.style.setProperty("--tilt-x", "0deg");
|
|
card.style.setProperty("--tilt-y", "0deg");
|
|
}, 80);
|
|
});
|
|
});
|
|
|
|
function applyPreferences(nextPreferences) {
|
|
document.documentElement.dataset.theme = nextPreferences.theme;
|
|
document.documentElement.dataset.accent = nextPreferences.accent;
|
|
|
|
services.classList.toggle("cards-view", nextPreferences.view === "cards");
|
|
services.classList.toggle("list-view", nextPreferences.view === "list");
|
|
|
|
setActiveButton(themeButtons, "theme", nextPreferences.theme);
|
|
setActiveButton(viewButtons, "view", nextPreferences.view);
|
|
setActiveButton(accentButtons, "accent", nextPreferences.accent);
|
|
}
|
|
|
|
function setActiveButton(buttons, key, value) {
|
|
buttons.forEach((button) => {
|
|
const isActive = button.dataset[key] === value;
|
|
button.classList.toggle("is-active", isActive);
|
|
button.setAttribute("aria-pressed", String(isActive));
|
|
});
|
|
}
|
|
|
|
function readPreferences() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function savePreferences(nextPreferences) {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(nextPreferences));
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
return Math.min(Math.max(value, min), max);
|
|
}
|