/* eslint-disable react/prop-types */ /** * NotificationBar · Barra de anuncio global * * Se renderiza arriba del Header (dentro del mismo contenedor sticky). * Lee su configuración de `window.SITE_CONFIG.notificationBar`, que es * cargada por `home/data/site-config-loader.js` desde * `conocimiento/data/cms/site-config.json`. * * Soporta: * - enabled (admin toggle) * - dismissible (botón × y persistencia en localStorage por sesión) * - tone: navy | copper | cream * - badge + message + link * * El espacio para un futuro live-ticker está reservado en la estructura, * pero se activa con `marketTicker.enabled` (off por ahora). */ const { useState: uS_Nb, useEffect: uE_Nb } = React; function NotificationBar() { // Estado reactivo a la config (puede llegar después por fetch) const [cfg, setCfg] = uS_Nb(() => window.SITE_CONFIG?.notificationBar || null); uE_Nb(() => { const onCfgLoaded = (e) => setCfg(e.detail?.notificationBar || null); window.addEventListener('cms:config-loaded', onCfgLoaded); return () => window.removeEventListener('cms:config-loaded', onCfgLoaded); }, []); // Persistencia del dismiss: si el usuario cerró la barra con esta versión // del mensaje, no la mostramos hasta que cambie el mensaje (hash sencillo). const msgHash = cfg ? simpleHash(cfg.message || '') : ''; const dismissKey = `nb:dismiss:${msgHash}`; const [dismissed, setDismissed] = uS_Nb(() => { try { return !!localStorage.getItem(dismissKey); } catch (_) { return false; } }); uE_Nb(() => { // Cuando cambia el mensaje, volvemos a evaluar try { setDismissed(!!localStorage.getItem(dismissKey)); } catch (_) {} }, [dismissKey]); if (!cfg || !cfg.enabled || dismissed) return null; const tones = { navy: { bg: 'var(--color-navy-deep)', fg: '#fff', acc: 'var(--color-copper-fallback)', soft: 'rgba(255,255,255,0.7)' }, copper: { bg: 'var(--color-copper-fallback)', fg: '#fff', acc: '#fff', soft: 'rgba(255,255,255,0.85)' }, cream: { bg: 'var(--color-cream-soft, #F2F0E8)', fg: 'var(--color-navy-deep)', acc: 'var(--color-copper-fallback)', soft: 'rgba(33,52,64,0.7)' }, }; const t = tones[cfg.tone] || tones.navy; const onDismiss = () => { try { localStorage.setItem(dismissKey, '1'); } catch (_) {} setDismissed(true); }; // Espacio reservado para un futuro ticker (cfg.ticker), por ahora sólo anuncio. return (
{/* Lado izquierdo: pulso + badge */}
{cfg.badge && ( )}
{/* Mensaje (centrado) */}
{cfg.message} {cfg.linkLabel && cfg.linkHref && ( <> {' '} {cfg.linkLabel} → )}
{/* Dismiss button */} {cfg.dismissible && ( )}
); } function pulseDot(color) { return { width: 7, height: 7, borderRadius: 999, background: color, display: 'inline-block', boxShadow: `0 0 0 0 ${color}`, animation: 'nbPulse 900ms cubic-bezier(0.16,1,0.3,1) infinite', }; } function simpleHash(str) { let h = 0; for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) | 0; return Math.abs(h).toString(36); } // Animación de pulso del badge (se inyecta una vez) (function injectNbStyles() { if (document.getElementById('nb-styles')) return; const css = document.createElement('style'); css.id = 'nb-styles'; css.textContent = ` @keyframes nbPulse { 0% { box-shadow: 0 0 0 0 currentColor; opacity: 1; } 70% { box-shadow: 0 0 0 8px transparent; opacity: 0.4; } 100% { box-shadow: 0 0 0 0 transparent; opacity: 1; } } /* Mobile: barra más compacta, el link se va a línea nueva */ @media (max-width: 720px) { [data-component="notification-bar"] .container > div:nth-child(2) { font-size: 12px !important; text-align: left !important; padding-inline-end: 32px; } [data-component="notification-bar"] .container { padding-block: 8px !important; gap: 10px !important; } } `; document.head.appendChild(css); })(); window.NotificationBar = NotificationBar;