187 lines
5.7 KiB
JavaScript
187 lines
5.7 KiB
JavaScript
/* ==========================================================================
|
|
ECOSISTEMA IDE — Cambio in-place + deep link por hash
|
|
URLs válidas: #eco-excellence | #eco-projects | #eco-digital | #ecosistema
|
|
========================================================================== */
|
|
(function () {
|
|
'use strict';
|
|
|
|
const section = document.getElementById('ecosistema-ide');
|
|
if (!section) return;
|
|
|
|
const stateEcosystem = section.querySelector('[data-eco-state="ecosystem"]');
|
|
const stateUnit = section.querySelector('[data-eco-state="unit"]');
|
|
const panels = Array.from(section.querySelectorAll('[data-eco-panel]'));
|
|
const triggers = Array.from(section.querySelectorAll('[data-eco-open]'));
|
|
const backBtn = section.querySelector('[data-eco-close]');
|
|
const pills = Array.from(section.querySelectorAll('.eco-pill'));
|
|
|
|
const VALID_UNITS = ['excellence', 'projects', 'digital'];
|
|
const HASH_PREFIX = '#eco-';
|
|
const HASH_ECOSYSTEM = '#ecosistema';
|
|
|
|
let lastFocusedTrigger = null;
|
|
let currentUnit = null; // null = ecosistema
|
|
|
|
/* ---------- Utilidades de URL ---------- */
|
|
function unitFromHash() {
|
|
const h = window.location.hash;
|
|
if (h === HASH_ECOSYSTEM) return null;
|
|
if (h.startsWith(HASH_PREFIX)) {
|
|
const id = h.slice(HASH_PREFIX.length);
|
|
return VALID_UNITS.includes(id) ? id : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function hashForUnit(id) {
|
|
return id ? HASH_PREFIX + id : HASH_ECOSYSTEM;
|
|
}
|
|
|
|
/* ---------- Render (no toca la URL) ---------- */
|
|
function renderUnit(id) {
|
|
const panel = panels.find(p => p.dataset.ecoPanel === id);
|
|
if (!panel) return;
|
|
|
|
const origin = triggers.find(t => t.dataset.ecoOpen === id);
|
|
if (origin) lastFocusedTrigger = origin;
|
|
|
|
stateEcosystem.hidden = true;
|
|
stateUnit.hidden = false;
|
|
|
|
panels.forEach(p => {
|
|
const isActive = p.dataset.ecoPanel === id;
|
|
p.hidden = !isActive;
|
|
if (isActive) {
|
|
p.style.animation = 'none';
|
|
void p.offsetWidth; // reflow para reiniciar animación
|
|
p.style.animation = '';
|
|
}
|
|
});
|
|
|
|
triggers.forEach(t => {
|
|
t.setAttribute('aria-expanded', t.dataset.ecoOpen === id ? 'true' : 'false');
|
|
});
|
|
|
|
pills.forEach(pill => {
|
|
pill.classList.toggle('is-active', pill.dataset.ecoOpen === id);
|
|
});
|
|
|
|
const title = panel.querySelector('.eco-panel__title');
|
|
if (title) {
|
|
title.setAttribute('tabindex', '-1');
|
|
requestAnimationFrame(() => title.focus({ preventScroll: false }));
|
|
}
|
|
|
|
currentUnit = id;
|
|
}
|
|
|
|
function renderEcosystem({ restoreFocus = true } = {}) {
|
|
stateUnit.hidden = true;
|
|
stateEcosystem.hidden = false;
|
|
|
|
triggers.forEach(t => t.setAttribute('aria-expanded', 'false'));
|
|
pills.forEach(p => p.classList.remove('is-active'));
|
|
|
|
if (restoreFocus) {
|
|
if (lastFocusedTrigger && typeof lastFocusedTrigger.focus === 'function') {
|
|
lastFocusedTrigger.focus({ preventScroll: false });
|
|
} else if (triggers[0]) {
|
|
triggers[0].focus({ preventScroll: false });
|
|
}
|
|
}
|
|
|
|
currentUnit = null;
|
|
}
|
|
|
|
/* ---------- API con URL ---------- */
|
|
function openUnit(id, { push = true, scroll = true } = {}) {
|
|
if (!VALID_UNITS.includes(id)) return;
|
|
renderUnit(id);
|
|
|
|
if (push) {
|
|
const url = window.location.pathname + window.location.search + hashForUnit(id);
|
|
history.pushState({ eco: id }, '', url);
|
|
}
|
|
|
|
if (scroll) {
|
|
const rect = section.getBoundingClientRect();
|
|
if (rect.top < -80 || rect.top > 200) {
|
|
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
}
|
|
}
|
|
|
|
function closeUnit({ push = true, scroll = false } = {}) {
|
|
renderEcosystem();
|
|
if (push) {
|
|
const url = window.location.pathname + window.location.search + HASH_ECOSYSTEM;
|
|
history.pushState({ eco: null }, '', url);
|
|
}
|
|
if (scroll) {
|
|
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
}
|
|
|
|
/* ---------- Listeners de UI ---------- */
|
|
triggers.forEach(trigger => {
|
|
trigger.addEventListener('click', (e) => {
|
|
const id = trigger.dataset.ecoOpen;
|
|
if (!id) return;
|
|
// Academy es <a href="academy.html">: dejamos navegar
|
|
if (trigger.tagName === 'A' && trigger.getAttribute('href')) return;
|
|
e.preventDefault();
|
|
openUnit(id);
|
|
});
|
|
|
|
trigger.addEventListener('keydown', (e) => {
|
|
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
const id = trigger.dataset.ecoOpen;
|
|
if (!id) return;
|
|
if (trigger.tagName === 'A') return;
|
|
e.preventDefault();
|
|
openUnit(id);
|
|
});
|
|
});
|
|
|
|
if (backBtn) backBtn.addEventListener('click', () => closeUnit({ scroll: true }));
|
|
|
|
section.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && !stateUnit.hidden) closeUnit();
|
|
});
|
|
|
|
/* ---------- Sincronización con la URL ---------- */
|
|
function syncFromLocation() {
|
|
const id = unitFromHash();
|
|
if (id && id !== currentUnit) {
|
|
renderUnit(id);
|
|
} else if (!id && currentUnit !== null) {
|
|
renderEcosystem({ restoreFocus: false });
|
|
}
|
|
}
|
|
|
|
// Carga inicial: si el hash trae unidad, abrir + scroll
|
|
(function initFromUrl() {
|
|
const id = unitFromHash();
|
|
if (id) {
|
|
renderUnit(id);
|
|
requestAnimationFrame(() => {
|
|
section.scrollIntoView({ behavior: 'auto', block: 'start' });
|
|
});
|
|
} else {
|
|
renderEcosystem({ restoreFocus: false });
|
|
}
|
|
})();
|
|
|
|
// Cambios manuales del hash (pegar link, editar barra, links compartidos)
|
|
window.addEventListener('hashchange', syncFromLocation);
|
|
|
|
// Atrás/adelante del navegador
|
|
window.addEventListener('popstate', syncFromLocation);
|
|
|
|
/* ---------- API pública opcional ---------- */
|
|
window.EcoIDE = {
|
|
open: openUnit,
|
|
close: closeUnit,
|
|
current: () => currentUnit,
|
|
};
|
|
})(); |