File size: 8,054 Bytes
6aee8f5 205158e 6aee8f5 ceb9831 f7f0a1a 6cfe16d f7f0a1a ceb9831 f7f0a1a 6cfe16d 6aee8f5 205158e 6aee8f5 6cfe16d 6aee8f5 205158e 6aee8f5 6cfe16d 6aee8f5 ceb9831 6aee8f5 c560adf f7f0a1a 6cfe16d f7f0a1a 6cfe16d f7f0a1a 6aee8f5 2b07a83 6cfe16d 6aee8f5 6cfe16d f7f0a1a 6cfe16d 6aee8f5 6cfe16d 6aee8f5 6cfe16d 6aee8f5 6cfe16d 6aee8f5 6cfe16d 6aee8f5 205158e 6cfe16d 6aee8f5 df68a62 f7f0a1a 6aee8f5 6cfe16d 6aee8f5 6cfe16d df68a62 6aee8f5 df68a62 6cfe16d df68a62 6cfe16d df68a62 6aee8f5 f7f0a1a 6cfe16d f7f0a1a c560adf f7f0a1a c560adf 2b07a83 c560adf f7f0a1a c560adf f7f0a1a 6aee8f5 205158e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title or "MAKER SPACE" }}</title>
<!-- Meta Tags para PWA (Progressive Web App) -->
<!-- Define cómo se ve y se comporta la app al instalarse en el móvil -->
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
<meta name="theme-color" content="#38bdf8">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='assets/apple-touch-icon.png') }}">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Maker Space">
<!-- Iconos y Estilos -->
<link rel="icon" href="{{ url_for('static', filename='assets/favicon.png') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Outfit:wght@500;700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Librería Socket.IO para comunicación en tiempo real -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
</head>
<body class="dark-theme">
<!-- Barra de Navegación Global -->
<nav class="navbar glass">
<div class="nav-container">
<a href="/" class="nav-logo">
<i class="fas fa-cube"></i>
<span>MAKER SPACE</span>
</a>
<!-- Botón Hamburguesa -->
<button class="hamburger" id="hamburger-btn" aria-label="Menu">
<i class="fas fa-bars"></i>
</button>
<div style="display: flex; gap: 1rem; align-items: center;">
<!-- Botón para instalar PWA (solo aparece si es instalable) -->
<button id="install-btn" class="btn-icon" style="display: none;">
<i class="fas fa-download"></i>
</button>
<!-- Botón para solicitar permisos de notificaciones -->
<button id="notif-btn" class="btn-icon">
<i class="fas fa-bell"></i>
</button>
</div>
</div>
</nav>
<!-- Sidebar Overlay - Movido fuera del nav para permitir blur global -->
<div class="nav-overlay" id="nav-overlay">
<div class="nav-sidebar glass">
<button class="close-btn" id="close-btn">×</button>
<div class="sidebar-links">
<a href="/" class="nav-item"><i class="fas fa-home"></i> INICIO</a>
<a href="/tutoria" class="nav-item"><i class="fas fa-book"></i> TUTORÍA</a>
{% if current_user.is_authenticated %}
<a href="/classroom" class="nav-item"><i class="fas fa-chalkboard-teacher"></i> AULAS</a>
<div style="margin-top: auto; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 1rem;">
<a href="/logout" class="nav-item" style="color: #ef4444;"><i class="fas fa-sign-out-alt"></i>
Salir</a>
</div>
{% else %}
<a href="/login" class="nav-item" style="color: #38bdf8;"><i class="fas fa-sign-in-alt"></i>
Acceder</a>
{% endif %}
</div>
</div>
</div>
<!-- Contenido Principal -->
<main class="content-wrapper">
<!-- Mostrar mensajes flash de Flask (Alertas de éxito/error) -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="notification glass {{ category }}" style="margin-bottom: 2rem;">
<i class="fas fa-exclamation-circle"></i> <span>{{ message }}</span>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Bloque donde se inserta el contenido de cada página hija -->
{% block content %}{% endblock %}
</main>
<!-- Contenedor flotante para notificaciones Socket.IO -->
<div id="notification-container"></div>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
<script>
/*
LÓGICA DE CLIENTE (JAVASCRIPT)
*/
// Inicializar conexión Socket.IO con el servidor
const socket = io();
socket.on('notification', (data) => {
// Recibir notificación del bot de Telegram
showNotification(data.text, data.color || 'blue');
});
// Función para mostrar notificaciones visuales en la web
function showNotification(text, color) {
const container = document.getElementById('notification-container');
const notif = document.createElement('div');
notif.className = `notification glass ${color}`;
notif.innerHTML = `<i class="fas fa-info-circle"></i> <span>${text}</span>`;
container.appendChild(notif);
// Lanzar notificación de sistema del navegador (PC y Móvil)
if ("Notification" in window && Notification.permission === "granted") {
navigator.serviceWorker.ready.then(registration => {
registration.showNotification("MAKER SPACE", {
body: text,
icon: "/static/assets/icon192x192.png",
vibrate: [200, 100, 200],
badge: "/static/assets/favicon.png"
});
});
}
// Desaparecer notificación después de 5 segundos
setTimeout(() => {
notif.style.opacity = '0';
setTimeout(() => notif.remove(), 500);
}, 5000);
}
// Lógica para el botón de activar notificaciones con feedback visual
const notifBtn = document.getElementById('notif-btn');
notifBtn.addEventListener('click', () => {
if ("Notification" in window) {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
notifBtn.style.color = '#10b981'; // Verde si activa
showNotification("Notificaciones activadas", "green");
} else if (permission === 'denied') {
notifBtn.style.color = '#ef4444'; // Rojo si deniega
alert("Has bloqueado las notificaciones. Actívalas en la configuración de tu navegador.");
}
});
}
});
// Registro del Service Worker para soporte PWA (Modo Offline)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js').then((reg) => {
console.log('SW Registered', reg);
}).catch((err) => {
console.log('SW Registration failed', err);
});
});
}
// Lógica del Menú Hamburguesa
const hamburgerBtn = document.getElementById('hamburger-btn');
const closeBtn = document.getElementById('close-btn');
const navOverlay = document.getElementById('nav-overlay');
function toggleMenu() {
navOverlay.classList.toggle('active');
document.body.classList.toggle('menu-open');
}
if (hamburgerBtn) hamburgerBtn.addEventListener('click', toggleMenu);
if (closeBtn) closeBtn.addEventListener('click', toggleMenu);
// Cerrar al hacer click fuera del sidebar
if (navOverlay) navOverlay.addEventListener('click', (e) => {
if (e.target === navOverlay) toggleMenu();
});
</script>
</body>
</html> |