Files
MyWebsite/js/main.js
T

213 lines
6.6 KiB
JavaScript

// ============================================
// Theme Toggle - Dark/Light Mode
// ============================================
/**
* Initialize theme on page load
* Checks localStorage for saved preference, falls back to light mode
*/
function updateThemeControl(isDark) {
const themeToggle = document.getElementById("theme-toggle");
if (!themeToggle) return;
const language = document.documentElement.lang;
const labels = {
es: ["Activar modo claro", "Activar modo oscuro"],
ca: ["Activar el mode clar", "Activar el mode fosc"],
en: ["Enable light mode", "Enable dark mode"],
};
const currentLabels = labels[language] || labels.es;
themeToggle.setAttribute("aria-pressed", String(isDark));
themeToggle.setAttribute("aria-label", isDark ? currentLabels[0] : currentLabels[1]);
}
function initTheme() {
const savedTheme = localStorage.getItem("theme") || "light";
const isDark = savedTheme === "dark";
document.documentElement.classList.toggle("dark", isDark);
updateThemeControl(isDark);
}
/**
* Toggle between dark and light theme
* Saves preference to localStorage
*/
function toggleTheme() {
const html = document.documentElement;
const isDark = !html.classList.contains("dark");
html.classList.toggle("dark", isDark);
localStorage.setItem("theme", isDark ? "dark" : "light");
updateThemeControl(isDark);
}
// ============================================
// Smooth Scrolling
// ============================================
/**
* Scroll to projects section
*/
function scrollToProjects() {
const projectsSection = document.getElementById("projects");
if (projectsSection) {
projectsSection.scrollIntoView({ behavior: "smooth" });
}
}
/**
* Scroll to contact section
*/
function scrollToContact() {
const contactSection = document.getElementById("contact");
if (contactSection) {
contactSection.scrollIntoView({ behavior: "smooth" });
}
}
// ============================================
// Modal Management
// ============================================
/** Element that had focus before a modal was opened */
let _modalPreviousFocus = null;
/**
* Open modal by project ID.
* Saves the currently focused element and moves focus into the modal.
* @param {string} projectId - The project identifier
*/
function openModal(projectId) {
const modal = document.getElementById(projectId + "-modal");
if (modal) {
_modalPreviousFocus = document.activeElement;
modal.classList.remove("hidden");
modal.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])'
);
if (focusable.length > 0) {
focusable[0].focus();
} else {
modal.setAttribute("tabindex", "-1");
modal.focus();
}
}
}
/**
* Close modal by project ID.
* Restores focus to the element that was focused before the modal opened.
* @param {string} projectId - The project identifier
*/
function closeModal(projectId) {
const modal = document.getElementById(projectId + "-modal");
if (modal) {
modal.classList.add("hidden");
modal.setAttribute("aria-hidden", "true");
document.body.style.overflow = "auto";
if (_modalPreviousFocus) {
_modalPreviousFocus.focus();
_modalPreviousFocus = null;
}
}
}
// ============================================
// Laboratorio Tabs Management
// ============================================
/**
* Initialize tabs behavior for the Laboratorio section
*/
function initTabs() {
const tabButtons = document.querySelectorAll(".tab-btn");
const tabPanes = document.querySelectorAll(".tab-pane");
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const targetTab = btn.getAttribute("data-tab");
tabButtons.forEach((tabButton) => {
const selected = tabButton === btn;
tabButton.setAttribute("aria-selected", String(selected));
tabButton.tabIndex = selected ? 0 : -1;
tabButton.classList.toggle("bg-primary", selected);
tabButton.classList.toggle("text-white", selected);
tabButton.classList.toggle("shadow-md", selected);
tabButton.classList.toggle("shadow-primary/25", selected);
tabButton.classList.toggle("text-slate-600", !selected);
tabButton.classList.toggle("dark:text-slate-400", !selected);
tabButton.classList.toggle("hover:bg-slate-100", !selected);
tabButton.classList.toggle("dark:hover:bg-white/5", !selected);
});
tabPanes.forEach((pane) => {
const selected = pane.id === `tab-${targetTab}`;
pane.classList.toggle("hidden", !selected);
pane.classList.toggle("block", selected);
pane.setAttribute("aria-hidden", String(!selected));
});
});
btn.addEventListener("keydown", (event) => {
if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
event.preventDefault();
const currentIndex = Array.from(tabButtons).indexOf(btn);
const direction = event.key === "ArrowRight" ? 1 : -1;
const nextIndex = (currentIndex + direction + tabButtons.length) % tabButtons.length;
tabButtons[nextIndex].focus();
tabButtons[nextIndex].click();
});
});
const selectedTab = document.querySelector('.tab-btn[aria-selected="true"]');
tabButtons.forEach((btn) => {
btn.tabIndex = btn === selectedTab ? 0 : -1;
});
}
// ============================================
// Event Listeners
// ============================================
/**
* Close modal when ESC key is pressed
*/
document.addEventListener("keydown", function (event) {
const modal = document.querySelector('[id$="-modal"]:not(.hidden)');
if (modal && event.key === "Tab") {
const focusable = Array.from(modal.querySelectorAll(
'button, [href], input, select, textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])'
));
if (focusable.length > 0) {
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
}
if (event.key === "Escape") {
const modals = document.querySelectorAll(
'[id$="-modal"]:not(.hidden)'
);
modals.forEach((openModalElement) => {
const projectId = openModalElement.id.replace("-modal", "");
closeModal(projectId);
});
}
});
/**
* Initialize theme and tabs
*/
initTheme();
initTabs();